A Parameter Toolbox

The last little project I did, parts of it below, took about 12-15 hours in Visual Studio (COM) and on completion I dumbed it down and converted it to Excel VBA-Autocad, about 3-4 hours for that. The code does not have to rewritten. The improvements of VB.net have to be undone.

Visual studio is a vastly better programming environment than VBA. But the problem is distribution. If the distributed XLSM file has the wrong version of autocad referenced, Excel will open the dialog to change it when first opened. With an EXE its just a dead end. Netload does seem to have some flexibility in this area. I have been working on it. I think I have some success driving parts in autocad from a form with a draw button and textbox number input with autocad.net, so that is the next direction.

Nevertheless I am using Visual Studio to automate Autocad with COM (ActiveX) and, so far, it works great. Essentially I am getting full functionality just by pasting from excel VBA and correcting the syntax changes. But I can’t share it with any other autocad version.

Below are some thoughts on developing a Visual Studio toolbox template and typical methods for multi-view 2D parametrics, with COM, not dotnet.

The basic starting technique is to sketch the part a view at a time on an XY grid. On the sketch, draw a vertical line at each X coordinate needed, X1, X2 etc. Do the same thing for the Y axis. Now link those to the parameters.

For instance a sheetmetal formed pan with a double bend at each side – You will have LENGTH, WIDTH, EDGE and FLANGE. The entire thing is described with 4 variables.

to draw a face view –

X0 = 0
X1 = FLANGE
X2 = WIDTH – FLANGE
X3 = WIDTH

The Y values are the same except LENGTH is substituted for WIDTH.

Then put those together to create point objects – an array of 3 doubles. Its much simpler to create the x, y values first then create the point objects.

in this simplest case, I have a box sub and the creation of the points is buried in there.

a slightly more complicated side view starts to show how this helps manage the complexity.

While that is an ugly point list, hard to read, it was easy to write. The figure is double line, in case its hard to tell. I am hard coding the thickness at 1/16 inch but that could also be a variable. Start at X1,Y1 and go counter-clockwise around.
In a multi-view drawing, how do we get the side view out to the side? we draw it in place by just adding a displacement to the X values. If the view is above, we add to the Y values. If its up and over, we add to both.

The displacement will vary on a lot of factors, mainly the dimensions of the front view and the dimension space.

Without dimensions, its just a sketch. The main programming toolbox for simple 2D parametrics contains wrappers or helpers for

Line
Polyline
Circle
Slot (in this particular example)
A point creator (essential with VBA but not hardly necessary with VB.NET)
Selection Sets (for making a Block)
Mtext
Linear Dimension, horizontal and vertical
Making a Block

Make the form look the way you want, change all the textbox label text at the same time with the idea they will all become global variables. Move things around, then change all the textbox names at the same time. Declare global variables for everything on the screeen, and make a sub to Get_All_Vars. Load defaults either by typing values into the properties box or in form_load.

I have one button to draw all views, with check boxes to turn on or off. the button is simple.

   Private Sub Btn_Draw1_Click(sender As Object, e As EventArgs) Handles Btn_Draw1.Click
        get_all_vars()  'get all vars from form
        draw_init()     'set layer, dimstyle

        If g_v1 Then view1()
        If g_v2 Then view2()
        If g_v3 Then view3()
        If g_v4 Then view4()
        If g_v5 Then view5()

        acadApp.Update()
    End Sub


    Sub draw_init()
        pt0 = Pt(0, 0, 0)
        acadDoc.SetVariable("clayer", "0")
        acadDoc.SetVariable("textstyle", "ArialN")

        'g_sc is from form
        Dim str As String = "PP-" & g_sc
        acadDoc.ActiveDimStyle = acadDoc.DimStyles.Item(str)
        acadDoc.SetVariable("LTSCALE", 0.5 * g_sc)
    End Sub

Get_all_vars and draw_init are in the button, so the user can change the form and draw. Everything read in from the form is saved to a global variable. This makes it easier, there is no decision making over how to pass necessary variables. This is a small program.

Another expediency is the loading of drawing elements. I like to be able to start a program like this from a completely blank drawing. I load in a template drawing when the form loads. The template drawing has layer, dimension style, and text style information only. I have a half dozen dimension styles, all identical except for the dimension scale, and I name them pp-12, pp-16, pp-24 etc and set to the proper one from the scale on the form.

    Sub Insert_delete()
        Dim strpath As String = "c:\prog\acad\"
        Dim strfile As String = "pnl_template.dwg"
        pt0 = Pt(0, 0, 0)
        Dim blockrefObj As AcadBlockReference
        blockrefObj = acadDoc.ModelSpace.InsertBlock(pt0, strpath & strfile, 1, 1, 1, 0)
        blockrefObj.Delete()
    End Sub

The first thing that has to be done is make a connection to autocad. you cannot make any autocad moves before connecting. Start_cad is called from Form_Load. It has to be first and it doesnt need to occur everytime the draw button is pushed.

 Public Sub start_cad()
        m_ProgID = "AutoCAD.Application.22"
        Call Connect_acad(m_ProgID)

        Insert_delete()

    End Sub

    Public Sub Connect_acad(strProgID As String)
        acadApp = Nothing
        'Dim strProgId As String = "AutoCAD.Application.22"

        Try         '' Get a running instance of AutoCAD
            'acadApp = GetObject(, strProgId)
            acadApp = CType(GetObject(, strProgId), AcadApplication)

        Catch
            Try     '' Create a new instance of AutoCAD
                acadApp = CType(CreateObject(strProgId), AcadApplication)

            Catch ex As Exception
                MsgBox(ex.Message)
                Exit Sub
            End Try
        End Try

        acadApp.Visible = True  '' Display the application
        ' MsgBox("Now running " & acadApp.Name & " version " & acadApp.Version)

        'load whatever globals needed based on acadapp
        acadDoc = acadApp.ActiveDocument
        acadMs = acadApp.ActiveDocument.ModelSpace
        ThisDrawing = acadApp.ActiveDocument

    End Sub

the actual drawing subs are called View1, View2 etc. that is where we declare x and y values and branch off to the wrappers and helpers.


    Function Line1(pnt1() As Double, pnt2() As Double, Optional strlayer As String = "none") As AcadLine
        ' line wrapper absolute pt args with optional layer
        Dim lineobj As AcadLine
        lineobj = acadDoc.ModelSpace.AddLine(pnt1, pnt2)

        If strlayer <> "none" Then
            lineobj.Layer = strlayer
        End If
        g_pt = pnt2
        g_line = lineobj
        Return lineobj
    End Function

  Sub Mtxt1(ptx() As Double, dblwidth As Double, str As String, Optional height As Double = 0.125, Optional layer As String = "0")
        'uses optional height , layer
        g_mtxt = acadDoc.ModelSpace.AddMText(ptx, dblwidth, str)
        g_mtxt.Layer = layer
        g_mtxt.Height = height
    End Sub

    Sub dimh(pt1() As Double, pt2() As Double, dimlocpt() As Double, Optional stylename As String = "none")
        Dim dimObj As AcadDimRotated
        dimObj = acadDoc.ModelSpace.AddDimRotated(pt1, pt2, dimlocpt, 0)
        dimObj.Layer = "Dim"

        If stylename <> "none" Then
            dimObj.StyleName = stylename
            dimObj.Update()
        End If
        g_dim = dimObj
    End Sub

    Sub dimv(pt1() As Double, pt2() As Double, dimlocpt() As Double, Optional stylename As String = "none")
        Dim dimObj As AcadDimRotated
        dimObj = acadDoc.ModelSpace.AddDimRotated(pt1, pt2, dimlocpt, PI / 2)
        dimObj.Layer = "Dim"

        If stylename <> "none" Then
            dimObj.StyleName = stylename
            dimObj.Update()
        End If
        g_dim = dimObj
    End Sub

I have found it easier to have separate subs for vertical and horizontal dims. With dimensions the endpoints are pretty straightforward, but the dimension line also has to be located. In old lisp routines, I always queried the dimstyle to see what the dimscale was. a half inch is a good distance to use between the part and the dimension line and between dimension lines. this is scaled times the overall scale factor, which is a textbox on the form. the location of the side views also use the scale to know how much space the dimensions take.

The dimension lines can be located with a Polarpoint type function. This works just like autocad’s built-in Utility.PolarPoint


    Function ppt(pnt() As Double, ang As Double, dist As Double) As Double()
        Dim pnt1() As Double
        Dim x, y As Double
        x = dist * Math.Cos(Deg2rad(ang))
        y = dist * Math.Sin(Deg2rad(ang))
        pnt1 = Pt(pnt(0) + x, pnt(1) + y, 0)
        Return pnt1
    End Function

if we are dimensioning a box with corners at pt0,1,2,3
g_sc is a global scale variable from form (default 16).

Dim spc As Double = 0.5 * g_sc

pt0 = Pt(0,0,0)
pt1 = Pt(pnl_wid, 0, 0)
pt2 = Pt(pnl_wid, pnl_len, 0)
pt3 = Pt(0, pnl_len, 0)

dimh(pt0, pt1, ppt(pt0, 270, 2 * spc))
dimv(pt1, pt2, ppt(pt1, 0, 2 * spc))
dimh(pt3, pt2, ppt(pt3, 90, 2 * spc))
dimv(pt0, pt3, ppt(pt0, 180, 2 * spc))

Since these are overall dimensions, that puts them two spaces away so we can get another line in closer for details.

A basic tool for putting in a variable number of holes or slots at regular intervals and centering them on an any-size edge, is here,
L = N * C + 2R

I like to block drawing elements that are supposed to stay together. The front view of these panels are often put together side by side to form walls. if a block is made the assembly can be put together with ease. making a block in autocad activex is usually described as drawing in a BlockSpace, just like you would otherwise draw in a ModelSpace. That sounds fine in theory, but in practice it presents a problem to the wrapper helpers who all have something like

lineobj = acadDoc.ModelSpace.AddLine(pnt1, pnt2)

built into them. In Lisp i did have a switch on the argument list to draw in either ms or bs. in vb i am not sure that could be made to work so easily. Fortunately there is another method, and it allows much more flexibility.

the gist of the code is this

 Dim B1 As AcadBlock
 B1 = acadDoc.Blocks.Item(strName)
 Dim items(i) As AcadEntity
'populate a selection set
'transfer the selection set to array Items
 acadDoc.CopyObjects(items, B1)

With that method we can draw in modelspace, select the items into a selection set with crossing or window, and send the selection set to the block making routine.

When making blocks there are 3 possible scenarios,
– the block name is not found and there are no complications, the new block is made, the items erased that formed the block and the block inserted,
– the block name is found but the block is not currently inserted, the old block definition can be deleted and proceed as above.
– the block name is found and there is an insert on the drawing, a message box is raised telling the user the block name is in use, the individual items are left on the screen, but there is no attempt to block.

Make_Block is a boolean function, so it indicates to the calling program whether it was successful or not. if succesful the calling program deletes the entities and inserts the block, if not then it does nothing.

the Try Catch error checking in VB.NET made this part of the code simpler than VBA.

    Function Add_ss(strName As String) As AcadSelectionSet
        'adds new empty named selection set
        Dim ss As AcadSelectionSet
        Try
            ss = acadDoc.SelectionSets.Item(strName)
            ss.Clear()
        Catch
            'accessing ss created an error
            ss = acadDoc.SelectionSets.Add(strName)
        End Try
        Return ss
    End Function

    '' function ss_x - return selection set from crossing window
    Function ss_x(ss As AcadSelectionSet, pt1() As Double, pt2() As Double) As AcadSelectionSet
        'items not visible do not select
        acadApp.Update()
        acadApp.ZoomAll()
        ss.Select(AcSelect.acSelectionSetCrossing, pt1, pt2)
        Return ss
    End Function

 Sub make_block_assy(strblk As String, pnt1() As Double, pnt2() As Double)
        Dim sset As AcadSelectionSet
        sset = Add_ss("SSBLOCK")
        sset = ss_x(sset, pt1, pt2)

        Dim result As Boolean
        result = Make_blk(sset, strblk)

        If result Then
            sset.Erase()
            sset.Clear()
            sset.Delete()
            g_entity = CType(acadDoc.ModelSpace.InsertBlock(pt0, strblk, 1, 1, 1, 0), AcadEntity)
            g_entity.Layer = "0"
        End If
    End Sub


    ''  function make_blk 
    ''  input - acadselectionset, str name of block
    ''  if new block name - make block, return true
    ''  if old block name with no inserts - delete old reference then make block, return true
    ''  if old block name with inserts - return false
    Function Make_blk(ss As AcadSelectionSet, strName As String) As Boolean
        Dim result As Boolean
        Dim B1 As AcadBlock
        Dim i As Integer

        Try
            B1 = acadDoc.Blocks.Item(strName)
            Try
                ' Found block, try to delete"
                B1.Delete()
            Catch ex As Exception
                ' delete generated an error, do nothing and exit function 
                MsgBox("looks like there is an insert, I did not make a new block")
                result = False
                Return result
            End Try
        Catch ex2 As Exception
            ' no block found, adding
        End Try

        B1 = acadDoc.Blocks.Add(pt0, strName)
        i = ss.Count - 1
        Dim items(i) As AcadEntity
        Dim item As AcadEntity

        'loop ss and add acad entity objects to array
        For i = 0 To ss.Count - 1
            item = ss.Item(i)
            items(i) = item
        Next i

        'populate block with items in the array
        acadDoc.CopyObjects(items, B1)
        result = True
        Return result
    End Function

I left out a few details, I have 5 views drawn, the 6th would be a flat pattern layout, before bending.

3 Equations in 3 Unknowns

“The central problem of linear algebra is the solution of simultaneous linear equations. The most important case is when the number of unknowns equals the number of equations… we begin in three dimensions.” Gilbert Strang – Linear Algebra and its Applications

Ax + By + Cz = D is a linear equation which can be represented by a plane. The intersection of 3 planes at a single point is the solution of the 3 equations used to plot the planes.

its hard to show the intersection of 3 planes with a static image. the Region object is trimmable with the command SurfTrim.
this is the very first example set of equations from Strang’s (old version) book.

A plane is defined by 3 points. But the equation for the plane is more directly derived from a single point and a vector that is orthogonal (perpendicular) to the plane. The dot product of two vectors is

\mathbf{a} \cdot \mathbf{b} = |\mathbf{a}| |\mathbf{b}| \cos \theta

so if the angle between the vectors is 90 degrees, the scalar dot product is zero. The dot product also has the alternative computation that if vector a = (a1, a2, a3) and b = (b1, b2, b3) then

\mathbf{a} \cdot \mathbf{b} = (a1 \ast b1) + (a2 \ast b2) + (a3 \ast b3)

if N is the vector perpendicular to the plane,

\mathbf{N} =   \begin {pmatrix}  A \\  B \\  C \\  \end{pmatrix}

if P1 is the specific point in the plane, and P is a generic point in the plane,

\mathbf{P1} =   \begin {pmatrix}  x1 \\  y1 \\  z1 \\  \end{pmatrix}

\mathbf{P} =   \begin {pmatrix}  x \\  y \\  z \\  \end{pmatrix}

then a vector from P1 to P is (P – P1) and the dot product of N and this line is zero.

\mathbf{N} \cdot (\mathbf{P} - \mathbf{P1}) = 0

according to the rules, that can be written as

\mathbf{N} \cdot \mathbf{P} = \mathbf{N} \cdot \mathbf{P1}

which becomes

Ax + By + Cz = A(x1) + B(y1) + C(z1)

the numbers on the right have specific values, because we are using a specific point and a specific vector, so we can just call those D

Ax + By + Cz = D

which is the standard equation for a plane. The constants A B and C are from the normal vector.

In linear algebra, we are usually given 3 of these equations, and asked to solve it, which means find the intersection of the planes, if it exists. To plot from this equation, Ax + By + Cz = D, where A,B,C and D have actual values, to autocad or any other graphic method, we need three points. About the only way to do that i know is to substitute values. In the most general case where neither A, B or C is zero, the plane intersects all 3 axes, and the 3 intersection points can be found by solving the equation with two variables of x, y and z set to zero. Where one variable of A, B, and C is set to zero, the plane is parallel to the missing axis variable. Where two variables are zero the plane is parallel to both. Its a little tedious, but you just set up tables and solve the equation in the abstract with the missing variables visualizing the plane. Then a select case statement will be the gateway to the actual drawing of the plane.

The other method needed – besides plotting given only the standard equation – is deriving the equation from 3 given points. For that the cross product is essential. 3 non-linear points form a triangle, from which you can see 2 vectors in the plane. The cross product of 2 vectors is a third vector that is orthogonal (perpendicular) to both of them. the cross product gives you the normal vector, with ABC direction numbers. Then you use one of the 3 points, the normal vector, and derive the equation.

all this is covered in the latter third of a full calculus book, the chapter on vectors in space, or 3D vectors, or geometry of space. its also in analytic geometry books and implied or explained in linear algebra books. I am making a linear algebra homework tool.

In the most general case where neither A, B, or C is zero, the axis intercepts are calculated, a triangle can be drawn, and it can be filled with a Region object. If D is zero the plane goes thru the origin, and that is also a special case, the 3 intercepts are the same.

If you are given the std equation, you have the normal vector, but if you are given the 3 points in the plane, you need the normal vector. That is a pretty simple operation with the cross product. the cross product is a pretty fussy formula, but once it is captured to a sub its easy to use.

    Function find_norm(ptA() As Double, ptB() As Double, ptC() As Double) As Double()
        'given 3 points, not co-linear, find norm
        Dim AB() As Double
        Dim AC() As Double
        Dim norm() As Double

        AB = Minusv(ptB, ptA)
        AC = Minusv(ptC, ptA)
        norm = Cross_Product(AB, AC)

        Return norm
    End Function

    Function Cross_Product(t() As Double, v() As Double) As Double()
        Dim x, y, z As Double
        x = t(1) * v(2) - t(2) * v(1)
        y = t(2) * v(0) - t(0) * v(2)
        z = t(0) * v(1) - t(1) * v(0)
        Cross_Product = Pt(x, y, z)

        Return Cross_Product
    End Function

now having 3 points and a normal vector, its optional, but i find the centroid of the triangle as a convenient reference point. if the vertexes are F, G, H, the centroid is very simply (F + G + H) / 3. Now using a vector from the centroid to a vertex, and having the normal, you can once again use the cross product to find a vector orthogonal to both of them. For the purpose of creating a UCS (User Coordinate System) in the plane. Once you have created the UCS, its a simple matter to enlarge your plane, using a Circle center at the centroid, or a square or rectangle.

its a work in progress, here is current state of form. its not really easy to pick off the intersections graphically. the regions can be trimmed but i am going to look at other objects and methods.

How Do I Access Autocad Com Objects From Visual Studio?

I have stopped ignoring visual studio. I am having no issues accessing Com ActiveX objects from VB.Net with nearly the same code that works in VBA. I am not sure what all the naysaying is about. Every thread i have ever seen on running Com objects from Visual Studio says, Don’t do it, It’s a Bad Idea.

It seems to work fine.

as time goes by I may update this, but at this point –

Right click References in Solution Explorer, Add Reference, add the autocad type library.
To get the Autocad namespace to work across all modules, double click My Project in Solution Explorer and add Autocad to Imported Namespaces.

Get a book or study online the differences in syntax. They are pretty minor. The code editor is much improved. Arrays are a little simplified. Indexing is a little different, no static type. There is no variant type, but there is an object type that microsoft uses to read and write arrays to excel spreadsheets. (microsoft has a good one page link to connect to excel com objects HERE) Autodesk has no such single page that i can find, but here are some useful links.

http://help.autodesk.com/view/OARX/2019/ENU/?guid=GUID-BFFF308E-CC10-4C56-A81E-C15FB300EB70

http://help.autodesk.com/view/OARX/2019/FRA/?guid=GUID-C8C65D7A-EC3A-42D8-BF02-4B13C2EA1A4B

http://help.autodesk.com/view/OARX/2019/ENU/?guid=GUID-927E71C2-E515-438E-9D7A-246D97BEF93F

http://help.autodesk.com/view/OARX/2019/ENU/?guid=GUID-D1E1CE43-F42B-4D8E-9B42-FDA9D8CCB6B9

All methods to be capitalized. All arguments in parentheses. No set statements.
Sometimes you have to figure out how to get the namespace shortcuts to work. The error reporting in vs really helps.
The Try, Catch, End Try error system works more cleanly.
Many more tools to use with collections and array types.
There is a stricter approach to datatypes, a setting to relax it, but i have Option Strict On. this is where a book will help. 2012 books are cheap.

So far i have pasted in VBA code, made changes per notes above, and the same objects work with the same logic – lines, polylines, dimensions, blocks, ucs, text, selection sets.

Code below is for version 2018 (“Autocad.Application.22”)

Module Module_Connect_Acad

    Public acadApp As AcadApplication
    Public acaddoc As AcadDocument
    Public acadms As AcadModelSpace

    Public Sub Connect_acad()
        acadApp = Nothing
        Dim strProgId As String = "AutoCAD.Application.22"

        Try         '' Get a running instance of AutoCAD
            'acadApp = GetObject(, strProgId)
            acadApp = CType(GetObject(, strProgId), AcadApplication)

        Catch
            Try     '' Create a new instance of AutoCAD
                acadApp = CType(CreateObject(strProgId), AcadApplication)

            Catch ex As Exception
                MsgBox(ex.Message)
                Exit Sub
            End Try
        End Try

        acadApp.Visible = True  '' Display the application
        ' MsgBox("Now running " & acadApp.Name & " version " & acadApp.Version)

        'load whatever globals needed based on acadapp
        acaddoc = acadApp.ActiveDocument
        acadms = acadApp.ActiveDocument.ModelSpace
    End Sub

End Module