Interlude in C#

I normally do not like clever wordplay in headlines. I am learning Autocad.Net, started with VB.net, but changing to C#. Its a learning curve, plus test/debug are start/close autocad. You cannot edit and re-compile the file if it is loaded in autocad. Steep curve, slow mechanics.

I saw a post on autocad.net forum to ignore myfirst plug-in and do not load wizards, learn how to do it manually. I completely agree. For the step by step instructions, you need the Managed .Net Developer’s Guide. Its online, but better to download it because its the primary learning source.

The front page of the autocad developer center is the best place to start

You want the ObjectARX SDK, because it has the file Managed net developer guide – arxmgd_dev.chm – and the managed net reference guide – arxmgd.chm. ARX and managed net (which is autocad.net) are entertwined. A typical managed net class in the reference guide has the verbage “This .NET class wraps the (similar) ObjectARX class.” To get the SDK you will have to make two clicks and register by entering your name and email. The ARX SDK also has dotNET samples.

Anytime you download a CHM file you have to right click in explorer and Unblock on the properties page.

Also on the developer home page is an autocad.net training lab, intended to be a two-day course.

In the managed net developer guide, there is a complete step by step to run code with manual setup. Once you gather that, all the code in the developer guide runs as-is by pasting directly into visual studio.

In addition you can google autocad.net autodesk university. There are a few good sources, not as many as i would like.

Almost everyone is posting code for autocad.net in C#. The developer guide has sample code in VB.net and C#. I think microsoft has said VB.net will be slow-walked or frozen altogether.

Autocad.net has a transaction model that must be looked at very hard. It’s the front end of every sample code in the dev guide. After that, I find passing objects by passing their objectID – but its not a long variable, its a class – the hardest to quickly size up. VBA seems wonderfully lean, I normally hate business bullshit, but autocad.net has a wealth of new objects, like matrices and vectors. Forms work fine if they are modal. Palettes are the other technique. Adesk U has some papers on forms and palettes.

The interlude will play while I try to get comfortable.

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.

A Replacement for VB Evaluate(string)

To graph a function we need to evaluate a string into its math equivalent. Excel VBA has the Application.Evaluate method which takes a variant for an argument and will evaluate a wide range of math symbols, with or without quotations – whatever is between the parentheses. In Visual Studio that doesn’t exist. We want to type a function (the right side of Y = some expression of X) into a textbox on a form, save it to a string variable, then calculate the value to a double for a range of X values. For instance,

strfunc = “X^3 – 3*X^2 + 2*x + 2”

A loop calculates X and Y values starting at X minimum, incrementing by a small X increment, ending at X maximum, and saving all the values to an array to be an argument for AddLightWeightPolyline.

the number of points to be stitched into a graph is calculated –
numpts = CInt((Xmax – Xmin) / Xinc) ‘number of line segments

there is always one more point than line segments
numpts = numpts + 1

the array of doubles is sized –
ReDim Pt(0 To numpts * 2 – 1) ‘store x and y for one pt

after the number of points has been calculated, and the array is ready, the loop is

For i = 1 To numpts
x = Xmin + ((i - 1) * Xinc)
y = eval_func(strfunc, x)
pt(i * 2 - 2) = x
pt(i * 2 - 1) = y
Next i


X is recalulated each time through the loop. Rather than adding the increment to a running sum, its more accurate to use the loop counter to multiply the increment, because of the imprecision of doubles. With odd numbers you might eventually run one cycle short or long. The loop counter should always be an integer. Calculation of Y is handed off to a function with the function string and the current value of X. The function substitutes the numerical value of X whereever X the symbol appears.

Debug.Print strfunc
strfunc = Replace(strfunc, “x”, x, , , vbTextCompare)
Debug.Print strfunc

the first time thru where X is at the starting value of, say, -6, the string and the modified string look like –

X^3 – 3*X^2 + 2*x + 2
-6^3 – 3*-6^2 + 2*-6 + 2

then the final scalar value is calculated and the value returned as Y.

dbl_result = Evaluate(strfunc)

This code was being run from Excel. Evaluate was a method of the top level Application.

For Visual Studio VB.Net we need a replacement for Evaluate.

A lot of people have asked this question. There are quite a few homebuilt parser class solutions. Another method is to link to another language and use its Eval method. A quick easy solution was found, credit to user kleinma

https://www.vbforums.com/showthread.php?412232-RESOLVED-02-03-Question-about-using-VBScript-(Eval)-in-VB-NET

As kleinma says, “Set a reference in your app to “Microsoft Script Control 1.0″ It is under the COM tab in references, not in the .NET tab”

A lot of testing will show if its a decent fix or just the first try.

    Public Xmin As Double
Public Xmax As Double
Public Xinc As Double
Public Ylim As Double
Public g_stop As Boolean
Public g_err_counter As Integer

Function calc_xy(strfunc As String) As Double()
'returns an array of x and y doubles
'Xmax, Xmin, Xinc, Ylim are all assumed to have values
Dim x, y As Double
Dim i, numpts As Integer
Dim Pt() As Double

g_err_counter = 0
g_stop = False

numpts = CInt((Xmax - Xmin) / Xinc) 'number of line segments
numpts = numpts + 1   'one more pt than line segment
ReDim Pt(0 To numpts * 2 - 1) 'store x and y for one pt

For i = 1 To numpts
x = Xmin + ((i - 1) * Xinc)
y = eval_func(strfunc, x)

If g_stop Then  'if eval_func set g_stop then
MsgBox("errorcounter at 3 - exiting")
Exit For
End If
Pt(i * 2 - 2) = x
Pt(i * 2 - 1) = y
Next i

Return Pt
End Function

Function eval_func(ByVal strfunc As String, x As Double) As Double
'returns a double
'on error returns Ylim but sets a flag to stop after 3 errors
'which would indicate a bad formula
'one or two error is ok because havent solved the mid-graph asymptote yet
Dim result As Double
strfunc = Replace(strfunc, "x", x.ToString, , , vbTextCompare)

Dim SC As New MSScriptControl.ScriptControl
SC.Language = "VBSCRIPT"
Try
result = Convert.ToDouble(SC.Eval(strfunc))
Catch ex As Exception
MessageBox.Show(ex.GetType.ToString & " " & ex.Message)
g_err_counter = g_err_counter + 1
If g_err_counter > 2 Then
g_stop = True
End If
result = Ylim
End Try

Return result
End Function


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.

An Issue with Regions

A problem has found me with creating Regions, probably the first one that has come up with accessing Autocad COM objects from Visual Studio VB.Net. There is probably a fix, but for the time the workaround lets me go on. It involves arrays of autocad objects. It is not a general problem that involves all arrays of autocad object types, just the problem visual studio is having reading the output of Addregion. I will document it here including the similar but fixable problem of output of arrays of visual studio data types.

The fixable problem is like this –

sample VBA code from autodesk to create polylines (with intermediate steps removed)

Dim pline2DObj As AcadLWPolyline
Dim get2Dpts As Variant
get2Dpts = pline2DObj.Coordinates

on paste, visual studio informs
‘Variant is no longer a supported type; use the ‘Object’ type instead.

changing get2Dpts to Object does return to the variable without error. At edit time, Visual Studio is recognizing a generic object not an array coming out of the Coordinate property.

Dim get2Dpts As Object

but generates the error if an attempt is made to read the variable
get2Dpts(0)
Option Strict On disallows late binding.

but at runtime, in the immediate window,
? typename(get2dpts)
“Double()”
? typename(get2dpts(0))
“Double”

so visual studio recognizes the returned object as an array of doubles during execution but not before. Turning Option Strict Off (My Project, Compile tab) solves the problem but i have learned a lot about how variables work with it on and dont accept that as a real solution.

more to the point change the variable declaration to an array of doubles.

Dim get2Dpts() As Double

now we get the error at

get2Dpts = pline2DObj.Coordinates
Option Strict On disallows implicit conversions from ‘Object’ to ‘Double()’.

we can fix that and we are done (and the immediate window returns the same object types as before).

get2Dpts = CType(pline2DObj.Coordinates, Double())

now for the currently unfixable problem involving an array of autocad objects instead of an array of doubles –
lets navigate to the autodesk ActiveX Reference Guide and paste in the Sub Example_AddRegion, make the usual fixes and hook up autocad,

    Sub Example_AddRegion()
' creates a region from an arc and a line.
Connect_acad()

Dim curves(1) As AcadEntity
Dim centerPoint(2) As Double
Dim radius, startAngle, endAngle As Double

centerPoint(0) = 5.0# : centerPoint(1) = 3.0# : centerPoint(2) = 0#
radius = 2.0#
startAngle = 0
endAngle = 3.141592
curves(0) = acadDoc.ModelSpace.AddArc(centerPoint, radius, startAngle, endAngle)

' Define the line
curves(1) = acadDoc.ModelSpace.AddLine(curves(0).startPoint, curves(0).endPoint)

' Create the region
Dim regionObj As Variant
regionObj = acadDoc.ModelSpace.AddRegion(curves)
acadApp.ZoomAll()
End Sub


Besides the expected Variant problem, i get an error on AddArc return but not AddLine.

Option Strict On disallows implicit conversions from ‘AcadArc’ to ‘AcadEntity’.

fix with

curves(0) = CType(acadDoc.ModelSpace.AddArc(centerPoint, radius, startAngle, endAngle), AcadEntity)

and now the error goes down to addline, fix the same way, except it still does not like the array indexed inside the Addline parameter list.

curves(1) = CType(acadDoc.ModelSpace.AddLine(curves(0).startPoint, curves(0).endPoint), AcadEntity)

i could probably nest two more CTYPE inside, but lets back up and simplify.

       Dim arcobj As AcadArc
arcobj = acadDoc.ModelSpace.AddArc(centerPoint, radius, startAngle, endAngle)

Dim lineobj As AcadLine
lineobj = acadDoc.ModelSpace.AddLine(arcobj.StartPoint, arcobj.EndPoint)

curves(0) = CType(arcobj, AcadEntity)
curves(1) = CType(lineobj, AcadEntity)


now the errors are reduced to the variant where AddRegion returns an array of region objects. I cannot solve this at present.

first move

Dim regionObj() As AcadRegion
regionObj = acadDoc.ModelSpace.AddRegion(curves)

this gives the expected error –
Option Strict On disallows implicit convsersions from ‘Object’ to ‘AcadRegion()’.

based on the experience with the Coordinate property of Polyline returning an array of doubles, this should work,

Dim regionobj() As AcadRegion
regionobj = CType(acadDoc.ModelSpace.AddRegion(curves), AcadRegion())

but it errors out at Ctype AFTER it creates the Region, it cannot pass the array of Regions to the object. the error is –

System.InvalidCaseException: ‘Unable to cast object of type ‘System.Object[]’ to type ‘Autocad.AcadRegion[]’.’

Lets change the Dim statement to Object. We get no errors and a successful run,
Dim regionObj As Object
regionObj = acadDoc.ModelSpace.AddRegion(curves)

but we are not accessing the returned variable. any attempt to use properties of a region fail at the intellisense level, there are no properties other than a few generic object properties.

does not happen – regionobj.EntityTransparency = “50”

in runtime at the immediate window –

? typename(regionobj)
“Object()”
?typename(regionobj(0))
“IAcadRegion”

compare to

? typename(get2dpts)
“Double()”
? typename(get2dpts(0))
“Double”

this doesnt help, i still get ‘unable to cast’ exception
Dim regionobj() As IAcadRegion
regionObj = CType(acadDoc.ModelSpace.AddRegion(curves), IAcadRegion())

visual studio only recognizes an array of primitive objects coming out of addregion. I have tried various ways to read the array into an autocad variable and failed.
the workaround is to create the region but not capture it to a variable. use a subroutine to create a new selection set, get the last item in the drawing, read that into a proper variable.

        Dim regionobj As AcadRegion
acadDoc.ModelSpace.AddRegion(curves)

Dim ss As AcadSelectionSet
ss = Add_ss("newss")
ss.Select(AcSelect.acSelectionSetLast)

regionobj = CType(ss.Item(0), AcadRegion)
regionobj.EntityTransparency = "50"


at edit, visual studio recognizes an object coming out of Coordinates property, but at runtime it recognizes the object is actually an array of doubles, and its able to cast the object to an array of doubles at edit.

at edit, visual studio recognizes an object coming out of AddRegion, but at runtime it recognizes the object as an array of objects, and its not able to cast the object to an array of AcadRegions at edit.

thats it. i can not say now there are no problems or how wide it is. its a possibility i did not hit the right combination of keys, or the solution may be some kind of library link to give visual studio enough information to handle the output of addregion.

    Sub Example_AddRegion()
' creates a region from an arc and a line.
Connect_acad()

Dim curves(1) As AcadEntity
Dim centerPoint(2) As Double
Dim radius, startAngle, endAngle As Double

centerPoint(0) = 5.0# : centerPoint(1) = 3.0# : centerPoint(2) = 0#
radius = 2.0#
startAngle = 0
endAngle = 3.141592

Dim arcobj As AcadArc
arcobj = acadDoc.ModelSpace.AddArc(centerPoint, radius, startAngle, endAngle)

Dim lineobj As AcadLine
lineobj = acadDoc.ModelSpace.AddLine(arcobj.StartPoint, arcobj.EndPoint)

curves(0) = CType(arcobj, AcadEntity)
curves(1) = CType(lineobj, AcadEntity)

Dim regionobj As AcadRegion
acadDoc.ModelSpace.AddRegion(curves)

Dim ss As AcadSelectionSet
ss = Add_ss("newss")
ss.Select(AcSelect.acSelectionSetLast)

regionobj = CType(ss.Item(0), AcadRegion)
regionobj.EntityTransparency = "50"

acadApp.ZoomAll()
End Sub


Transformation Matrices – 2D Demo

The theory of matrices was first developed (or so I read) in relation to geometric transformations.

The best single reference that explained Transformation Matrices to me esp with regards to graphics programming was Rod Stephens book Visual Basic Graphics Programming, which is 20+ years old. It’s a comprehensive but clear introduction. There are also many linear algebra math videos on Khan, Youtube etc that i am just starting to explore. Stephens multiplies his vector from the left of the matrix. Everyone else does it from the right, so i cannot use his examples as-is, but looking at his methods has been very helpful.

A vector is also a matrix, and its a point. Multiplying a square matrix by a column vector yields another column vector. Thats how the point gets transformed. The transformation matrix can contain numbers for scaling, translating (moving) and rotation.

The basic idea can be shown with a 2D autocad implementation and then extend to full 3D. Matrix math, and multiplication of matrices is somewhat hard to grasp, but programming it from scratch is good reinforcement. The basic 3D matrix is a 4×4 grid of doubles. The 2D matrix is 3×3.

The first sub initializes an identity matrix, which is the base. The subs that create Translation, Scaling and Rotation numbers will modify the identity matrix. To combine these operations into one, multiplication of two 3×3 matrices is done. Finally to apply the matrix to a point, the matrix is multiplied by the 1×3 point vector.

In reality, we are going to use arrays as matrices, and the array of a 1×3 is actually just a 3, but we are going to treat it as a column. VB.net arrays start with index zero. Most matrix references or maybe all use 1 as the base number.

Here is the 3×3 initial matrix with value 1 on the main diagonal and zero elsewhere. Its a function that returns the result and does not take any arguments.

show_mat(M) is a sub to show the matrix in the immediate window screen for checking.

    Public Function m3x3_ID() As Double(,)
Dim M(0 To 2, 0 To 2) As Double
Dim i, j As Integer

For i = 0 To 2
For j = 0 To 2
If i = j Then
M(i, j) = 1
Else
M(i, j) = 0
End If
Next
Next

show_mat(M)
Return M
End Function

Public Sub show_mat(M(,) As Double)
Dim i, j As Integer
Dim str As String = ""
For i = 0 To UBound(M, 1)
For j = 0 To UBound(M, 2)
If j = 0 Then
str = str & vbCrLf
End If
str = str & M(i, j) & vbTab
Next
Next

Debug.Print(str)
End Sub


We need a sub to multiply a 3×3 matrix by a 1×3 vector. MxV. This is a simple program that takes the Matrix and Vector as arguments and returns a Vector.
For 2D the Z value is not required but is required by autocad, leading to a bit of complication we wont have with 3D.

    Function MxV(M(,) As Double, V() As Double) As Double()
Dim V2(2) As Double

V2(0) = M(0, 0) * V(0) + M(0, 1) * V(1) + M(0, 2) * 1
V2(1) = M(1, 0) * V(0) + M(1, 1) * V(1) + M(1, 2) * 1
V2(2) = M(2, 0) * V(0) + M(2, 1) * V(1) + M(2, 2) * 1

'need to zero this out for 2D demo purpose
V2(2) = 0

show_pt(V2)
Return V2

End Function



Matrices are combined by multiplication. I still have to show myself why that works, when rotation and scaling use the same cells. The multiplication of two 3×3 matrices is row by column. You could do it with brute force, but studying the concise math definition –

c(ij) = a(i1)b(1j) + a(i2)b(2j) + a(i3)b(3j)

– shows the basic i and j loop variables and the third variable just goes from 1 to 3. Modifying slightly for our 0 to 2 array gives a 3 variable loop. Matrices A and B are 3×3. The order matters. Multiplication is not commutative. A movement then a rotation is different than a rotation then a movement. The function returns the new matrix.


Function MxM(A(,) As Double, B(,) As Double) As Double(,)
Dim C(2, 2) As Double
Dim i, j, n As Integer
Dim value As Double

For i = 0 To 2
For j = 0 To 2
value = 0
For n = 0 To 2
value = value + A(i, n) * B(n, j)
Next
C(i, j) = value
Next
Next

show_mat(C)
Return C
End Function



Converting Autocad VBA ActiveX to Visual Studio

The Autocad ActiveX and VBA Developer’s Guide, Work in Three Dimensional Space (chap 8 in the old print version), is available with 10 sample code programs here

also in your autocad installation as a CHM file, (very easy to work with)
ProgramFiles\Common Files\Autodesk Shared\acadauto.chm

these sample programs paste in to Visual Studio with a few but not too many problems. Below is a screenshot of the first one. On the left is the virgin code. On the right is the corrected version which runs as intended.

Visual Studio throws up 8 errors on paste. (see ThisDrawing discussion below)
Let and Set assignment is no longer supported. You simply delete them.
acRed and acBlue is not declared. Hovering over the word acRed, Visual Studio suggests the fix, add the namespace ACAD_COLOR
Variant is no longer a supported type: Use the Object type instead.

Changing Variant to Object does eliminate the error on that line, but creates errors where it is accessed. Arrays are an area where dot.net has fixed logical inconsistencies in VBA. Autocad declared a return value of variant because static arrays could not accept an assigment. Dynamic arrays could accept assignment, but that was not in the very first version of VBA, and apparently never made it into the Autocad ActiveX implementation. There is no longer a distinction in dot.net between dynamic and static arrays.

We know the point data for polylines is a Double, and we know the autocad Coordinates method will return a variable number of points, so lets declare the variable as we would a double type dynamic array.

Dim get2Dpts() as Double

now we get an error on assigment –
get2Dpts = pline2DObj.Coordinates
Option Strict On disallows implicit conversions from ‘Object’ to ‘Double’.

anytime you get that and you know your variables are convertible the solution is Ctype(var, type)
get2Dpts = CType(pline2DObj.Coordinates, Double())

Autocad VBA sample code references a variable called ThisDrawing which is always available in the VBA environment. From Visual Studio, I run a sub called Connect_Acad that makes the connection and uses a global AcadDocument variable which can be named ThisDrawing and that is what i have done here. It is added to the top of the sample code.

Make those changes and it runs as is.

There is a simplification on the array assignments available in Visual Studio. In VBA I made subroutines to try to make the awkward point data assigments flow easier. VB.Net has a good new array feature called an initialization list.

you can change all this –

Dim points2D(0 To 5) As Double
Dim points3D(0 To 8) As Double

‘ Define three 2D polyline points
points2D(0) = 1 : points2D(1) = 1
points2D(2) = 1 : points2D(3) = 2
points2D(4) = 2 : points2D(5) = 2

‘ Define three 3D polyline points
points3D(0) = 1 : points3D(1) = 1 : points3D(2) = 0
points3D(3) = 2 : points3D(4) = 1 : points3D(5) = 0
points3D(6) = 2 : points3D(7) = 2 : points3D(8) = 0

to this –

‘ Define three 2D polyline points
Dim points2D() As Double = {1, 1, 1, 2, 2, 2}

‘ Define three 3D polyline points
Dim points3D() As Double = {1, 1, 0, 2, 1, 0, 2, 2, 0}

side by side screenshots –

*******************
NewUCS
*******************
9 errors on paste
Set
Method arguments must be enclosed in parentheses.
acWorld and acUCS is not declared.

fix those and down to 4 all due to Variant.

Dim WCSPnt As Variant
Dim UCSPnt As Variant

Lets try that again where we change the variant array to its target double array.
Dim WCSPnt() As Double
Dim UCSPnt() As Double

Again we get an error where Autocad Utility Getpoint is returning an Object, originally it was a Variant. We know its a double array so we can use the Ctype function.

The other and last error though is on the boolean argument in Utility.TranslateCoordinates.
UCSPnt = ThisDrawing.Utility.TranslateCoordinates(WCSPnt, AcCoordinateSystem.acWorld, AcCoordinateSystem.acUCS, False)
Strict On disallows implicit conversions from ‘Boolean’ to ‘Integer’.

That makes me want to look at the ActiveX Reference Guide under Objects Utility TranslateCoordinates. Its a flag possible values are True or False but its a Long type not Boolean. it affects whether the first argument is treated as an absolute point value or a vector. i dont quite know why its implemented like that but i can go with the value zero to get the intent.

That allows another error which has been in the queue on the same line. which is fixed by the familiar Ctype function.
And now it runs.

Arrays in VB.Net are all normally zero-based (with some exceptions). So VBA declarations such as Dim pt (0 to 2) as double can be changed to Dim pt(2) as Double. But we can also use the initialization list to clean up.

change this –
Dim origin(0 To 2) As Double
Dim xAxisPnt(0 To 2) As Double
Dim yAxisPnt(0 To 2) As Double

origin(0) = 4 : origin(1) = 5 : origin(2) = 3
xAxisPnt(0) = 5 : xAxisPnt(1) = 5 : xAxisPnt(2) = 3
yAxisPnt(0) = 4 : yAxisPnt(1) = 6 : yAxisPnt(2) = 3

to this –
Dim origin() As Double = {4, 5, 3}
Dim xAxisPnt() As Double = {5, 5, 3}
Dim yAxisPnt() As Double = {4, 6, 3}


Sub Ch8_NewUCS()
Call Connect_acad()
Dim ucsObj As AcadUCS

Dim origin() As Double = {4, 5, 3}
Dim xAxisPnt() As Double = {5, 5, 3}
Dim yAxisPnt() As Double = {4, 6, 3}

' Add the UCS to the UserCoordinatesSystems collection
ucsObj = ThisDrawing.UserCoordinateSystems.Add(origin, xAxisPnt, yAxisPnt, "New_UCS")
' Display the UCS icon
ThisDrawing.ActiveViewport.UCSIconAtOrigin = True
ThisDrawing.ActiveViewport.UCSIconOn = True

' Make the new UCS the active UCS
ThisDrawing.ActiveUCS = ucsObj
MsgBox("The current UCS is : " & ThisDrawing.ActiveUCS.Name & vbCrLf & " Pick a point in the drawing.")

' Find the WCS and UCS coordinate of a point
Dim WCSPnt() As Double
Dim UCSPnt() As Double

WCSPnt = CType(ThisDrawing.Utility.GetPoint(, "Enter a point: "), Double())
UCSPnt = CType(ThisDrawing.Utility.TranslateCoordinates(WCSPnt, AcCoordinateSystem.acWorld, AcCoordinateSystem.acUCS, 0), Double())

MsgBox("The WCS coordinates are: " & WCSPnt(0) & ", " & WCSPnt(1) & ", " & WCSPnt(2) & vbCrLf &
"The UCS coordinates are: " & UCSPnt(0) & ", " & UCSPnt(1) & ", " & UCSPnt(2))
End Sub


*******************
TranslateCoordinates
*******************
8 errors on paste
Set, Arguments not enclosed in parentheses, unfound enumerations that need namespaces, and use of Variant arrays.

Dim firstVertex As Variant
firstVertex = plineObj.Coordinate(0)

looking up Polyline Object in the ActiveX Rerference Guide then clicking on Coordinate says the property value is a two or three element array of doubles.

change to –
Dim firstVertex() As Double
firstVertex = CType(plineObj.Coordinate(0), Double())

and
Dim coordinateWCS As Variant
coordinateWCS = ThisDrawing.Utility.TranslateCoordinates(firstVertex, AcCoordinateSystem.acOCS, AcCoordinateSystem.acWorld, False, plineNormal)

change to –
Dim coordinateWCS() As Double
coordinateWCS = CType(ThisDrawing.Utility.TranslateCoordinates(firstVertex, AcCoordinateSystem.acOCS, AcCoordinateSystem.acWorld, 0, plineNormal), Double())

    Sub Ch8_TranslateCoordinates()
Call Connect_acad()
Dim plineObj As AcadPolyline
Dim points() As Double = {1, 1, 0, 1, 2, 0, 2, 2, 0, 3, 2, 0, 4, 4, 0}

' Create a light weight Polyline object in model space
plineObj = ThisDrawing.ModelSpace.AddPolyline(points)

' Find the X and Y coordinates of the first vertex of the polyline
Dim firstVertex() As Double
firstVertex = CType(plineObj.Coordinate(0), Double())

' Find the Z coordinate for the polyline using the elevation property
firstVertex(2) = plineObj.Elevation

' Change the normal for the pline so that the difference between the coordinate systems is obvious.
Dim plineNormal() As Double = {0#, 1.0#, 2.0#}
plineObj.Normal = plineNormal

Dim coordinateWCS() As Double
coordinateWCS = CType(ThisDrawing.Utility.TranslateCoordinates(firstVertex, AcCoordinateSystem.acOCS, AcCoordinateSystem.acWorld, 0, plineNormal), Double())

' Display the coordinates of the point
MsgBox("The first vertex has the following coordinates:" & vbCrLf & "OCS: " & firstVertex(0) & ", " &
firstVertex(1) & ", " & firstVertex(2) & vbCrLf & "WCS: " & coordinateWCS(0) & ", " & coordinateWCS(1) & ", " & coordinateWCS(2))
End Sub


*************
Create3DMesh
************
here is one we can clean up quite a bit. it pastes in with only two errors, both trivial. It runs just by deleting the word Set, adding AcadApp. in front of ZoomAll and adding Connect_acad as the first line .
Here is the original VBA from Autocad.

Sub Ch8_Create3DMesh()
Dim meshObj As AcadPolygonMesh
Dim mSize, nSize, Count As Integer
Dim points(0 To 47) As Double

' create the matrix of points
points(0) = 0: points(1) = 0: points(2) = 0
points(3) = 2: points(4) = 0: points(5) = 1
points(6) = 4: points(7) = 0: points(8) = 0
points(9) = 6: points(10) = 0: points(11) = 1
points(12) = 0: points(13) = 2: points(14) = 0
points(15) = 2: points(16) = 2: points(17) = 1
points(18) = 4: points(19) = 2: points(20) = 0
points(21) = 6: points(22) = 2: points(23) = 1
points(24) = 0: points(25) = 4: points(26) = 0
points(27) = 2: points(28) = 4: points(29) = 1
points(30) = 4: points(31) = 4: points(32) = 0
points(33) = 6: points(34) = 4: points(35) = 0
points(36) = 0: points(37) = 6: points(38) = 0
points(39) = 2: points(40) = 6: points(41) = 1
points(42) = 4: points(43) = 6: points(44) = 0
points(45) = 6: points(46) = 6: points(47) = 0

mSize = 4: nSize = 4

' creates a 3Dmesh in model space
Set meshObj = ThisDrawing.ModelSpace. _
Add3DMesh(mSize, nSize, points)

' Change the viewing direction of the viewport
' to better see the cylinder
Dim NewDirection(0 To 2) As Double
NewDirection(0) = -1
NewDirection(1) = -1
NewDirection(2) = 1
ThisDrawing.ActiveViewport.Direction = NewDirection
ThisDrawing.ActiveViewport = ThisDrawing.ActiveViewport
ZoomAll
End Sub


code that runs in visual studio

   Sub Ch8_Create3DMesh()
Call Connect_acad()
Dim meshObj As AcadPolygonMesh
Dim mSize, nSize, Count As Integer

Dim points() As Double =
{0, 0, 0,
2, 0, 1,
4, 0, 0,
6, 0, 1,
0, 2, 0,
2, 2, 1,
4, 2, 0,
6, 2, 1,
0, 4, 0,
2, 4, 1,
4, 4, 0,
6, 4, 0,
0, 6, 0,
2, 6, 1,
4, 6, 0,
6, 6, 0}

mSize = 4 : nSize = 4
' creates a 3Dmesh in model space
meshObj = ThisDrawing.ModelSpace.Add3DMesh(mSize, nSize, points)

' Change the viewing direction of the viewport
Dim NewDirection() As Double = {-1, -1, 1}
ThisDrawing.ActiveViewport.Direction = NewDirection
ThisDrawing.ActiveViewport = ThisDrawing.ActiveViewport
acadApp.ZoomAll()
End Sub


*************
GetUCSMatrix
************
sample code from object UCS method GetUCSMatrix in ActiveX Reference Guide

Here is the corrected code. I have added a loop to read and print the matrix values.
The orig program creates a new UCS, makes it active, then draws a circle. That illustrates that coordinate input from ActiveX is always interpreted as World coordinates (VBA ignores the current UCS). The program then runs

newMatrix = UCSobj.GetUCSMatrix
circleobj.Transformby(newMatrix)

which moves the circle from its world coordinates to the equivalent UCS coordinates.

We never have to examine the Matrix or know what its dimensions are. I added a loop to read it. The matrix is a square 4×4. It contains point values as columns. The bottom row seems to be unused.

col1 is the same data as the variable UCSXDIR, its the X vector from the UCS origin.
col2 is the same data as the variable UCSYDIR, its the Y vector from the UCS origin
col3 is the Z vector, which is obtainable from the XY vectors by the right hand rule.
col4 is the origin.

if we were writing our own matrix we would do

dim Matrix(3 , 3) as Double

or

dim Matrix( , ) as Double =
{ {1,0,0,2},
{ 0,1,0,2},
{ 0,0,1,0},
{ 0,0,0,1} }

these are direction vectors.
UCSXDIR = 1,0,0
UCSYDIR = 0,1,0
Z DIR = 0,0,1

absolute point
UCSORG = 2,2,0

this is not the whole story, Transformation Matrices are a fascinating topic in linear algebra. TransformBy has some useful examples, including scaling, which occurs on the main diagonal. ARX Developers Guide (search on transformation matrix) has better help page than ActiveX. First 3 columns are both rotation and scaling. last column is translation. Transformation matrixes can be combined so translation, scaling and rotation are all accomplished in a single operation.

TransformBy is a method of the elementary AcadEntity, meaning it is enabled for every object in the drawing.

Sub Example_GetUCSMatrix()
' This example creates a new UCS and finds the UCS matrix for it.
' It then creates a circle using WCS coordinates and
' transforms the circle for the UCS.
Call Connect_acad()

' Define a new UCS and turn on the UCS icon at the origin.
Dim ucsObj As AcadUCS
Dim origin() As Double = {2, 2, 0}
Dim xAxisPoint() As Double = {3, 2, 0}
Dim yAxisPoint() As Double = {2, 3, 0}

'Dim xAxisPoint() As Double = {4, 4, 0}
'Dim yAxisPoint() As Double = {0, 4, 0}

ucsObj = ThisDrawing.UserCoordinateSystems.Add(origin, xAxisPoint, yAxisPoint, "UCS1")
ThisDrawing.ActiveUCS = ucsObj

Dim vportObj As AcadViewport
vportObj = ThisDrawing.ActiveViewport
vportObj.UCSIconOn = True
vportObj.UCSIconAtOrigin = True
ThisDrawing.ActiveViewport = vportObj

' Create a circle using WCS coordinates
Dim circleObj As AcadCircle
Dim center() As Double = {1, 1, 0}
Dim radius As Double = 0.5
circleObj = ThisDrawing.ModelSpace.AddCircle(center, radius)
acadApp.ZoomAll()

' Get the UCS transformation matrix
Dim TransMatrix(,) As Double
TransMatrix = CType(ucsObj.GetUCSMatrix(), Double(,))

Dim str As String
Dim r, c As Integer
For r = 0 To UBound(TransMatrix, 1)
For c = 0 To UBound(TransMatrix, 2)
str = "r = " & CStr(r) & " c = " & CStr(c) & "   " & CStr(TransMatrix(r, c))
Debug.Print(str)
Next
Next

' Transform the circle to the UCS coordinates
MsgBox("Transform the circle.", , "GetUCSMatrix Example")
circleObj.TransformBy(TransMatrix)
circleObj.Update()
MsgBox("The circle is transformed.", , "GetUCSMatrix Example")
acadApp.Update()
End Sub


Autocad Object Types in Visual Studio

The basic object, then AcadObject, then AcadEntity, then the specialized specific top objects. This is the object inheritance hierarchy in autocad. Most objects contained by the document derive from the most general AcadObject. Hence most objects have those basic properties, which include Handle, ObjectName, ObjectID and OwnerID. All the graphic elements, things you can see, like lines and circles, further derive from the AcadEntity object. The quickest way to survey this in the ActiveX Reference Guide clicking on the graphical Object Model. The dwg document object itself does not inherit from AcadObject, but most of what it contains does.

TreeView and ListView controls in Visual Studio are a little changed in Visual Studio from VB6. TreeView no longer uses a key or a relationship parameter in the argument list. There is a tag property that takes the place of key. Building a hierarchy is done by saving the node to a variable, then using its Nodes.Add method to create a new node directly as a child node.

To connect TreeView click event to the ListView display, MS uses the tag property, illustrated in this link.

The handle property of all AcadObjects is a string. It is permanent from session to session. The ObjectID serves the same purpose, (perhaps it preceded the handle historically) but its a Long integer, and it may change from session to session. OwnerID is the long integer container object. There is no owner handle. Once you have the handle captured to a string, or the ID to a long integer, you convert the string or long to an object with a method from AcadDocument, HandleToObject(string) or ObjectIDToObject(long). I keep Option Strict On to be constantly reminded when I am trying to pass variables from one type to another.

HandleToObject returns a generic object. But if it has a handle, its an AcadObject. So it can be immediately converted to AcadObject. In general, non-graphic elements derive from AcadObject only, and graphic elements derive from both AcadObject and AcadEntity. Everything in the drawing including Blocks collection and ModelSpace derive from AcadObject.

To further identify what type of object you have, use VB method TypeName and TypeOf

Dim obj As Object
Dim acad_obj As AcadObject
obj = acadDoc.HandleToObject(strhandle)
acad_obj = CType(obj, AcadObject)

Debug.Print(TypeName(obj))



Will return the typename and

  Case TypeOf acad_obj Is AcadBlock
MsgBox("AcadBlock")
blk = CType(obj, AcadBlock)
Debug.Print(blk.Name & " " & blk.Count.ToString)


TypeOf will provide a boolean true or false to convert obj to its most specific type so you can use those methods, such as Name of the Block.

To populate a TreeView with an unknown number of levels, a recursive function is made with the parent node passed as one of the parameters.

If you are looking at a Block structure, the top level is a Block, and the embedded blocks are BlockReference.

ModelSpace and PaperSpace are blocks in the Blocks collection, they are also provided as shortcut properties of the Document, so they can be found either way, but usually using the property of the Document.

Button6 is the Block button on the form, it should say Blocks, it gets the entire Blocks collection. clicking on a node in the tree loads one level into ListView.

    Private Sub Button6_Click(sender As Object, e As EventArgs) Handles Button6.Click
Connect_acad()
ListView1.Clear()
Dim acadblks As AcadBlocks
Dim blk As AcadBlock
acadblks = acadDoc.Blocks

Dim strhandle As String
strhandle = acadblks.Handle
listview_by_handle(strhandle)

'start treeview
TreeView1.Nodes.Clear()

Dim rootnode As TreeNode
rootnode = New TreeNode(acadblks.ObjectName)
rootnode.Tag = acadblks.Handle
TreeView1.Nodes.Add(rootnode)

For Each blk In acadblks
pop_tree(blk, rootnode)
Next
End Sub


ListViewByHandle is used to get the whole Blocks collection but its also called by a click event in the Tree to load the item selected. It has to have a fairly comprehensive Select Case to handle entities, blocks, blockrefs and block collection. I don’t try to do any levels in the ListView, so for instance, if you click on a block, it writes the block handle, name etc on the first line, then all the first level constituents under it.

Sub listview_by_handle(strhandle As String)
ListView1.Clear()
Dim item As ListViewItem
Dim obj As Object
Dim acad_obj As AcadObject
Dim blk As AcadBlock
Dim blkref As AcadBlockReference
Dim acadblks As AcadBlocks

With ListView1
.Size = New Size(850, 500)
.Columns.Add("ObjectName", 180)
.Columns.Add("TypeName", 180)
.Columns.Add("ObjectID", 100)
.Columns.Add("OwnerID", 100)
.Columns.Add("Handle", 100)
.Columns.Add("NAME", 180)
.View = View.Details
End With

obj = acadDoc.HandleToObject(strhandle)
' Debug.Print(TypeName(obj))
acad_obj = CType(obj, AcadObject)

item = New ListViewItem(acad_obj.ObjectName)
item.SubItems.Add(TypeName(acad_obj))
item.SubItems.Add(CType(acad_obj.ObjectID, String))
item.SubItems.Add(CType(acad_obj.OwnerID, String))
item.SubItems.Add(acad_obj.Handle)

Select Case True
Case TypeOf acad_obj Is AcadBlocks
' MsgBox("AcadBlocks")
acadblks = acadDoc.Blocks
item.SubItems.Add("")
ListView1.Items.Add(item)

For Each blk In acadblks
item = New ListViewItem(blk.ObjectName)
item.SubItems.Add(TypeName(blk))
item.SubItems.Add(CType(blk.ObjectID, String))
item.SubItems.Add(CType(blk.OwnerID, String))
item.SubItems.Add(blk.Handle)
item.SubItems.Add(blk.Name)
ListView1.Items.Add(item)
Next

Case TypeOf acad_obj Is AcadBlock
'MsgBox("AcadBlock")
blk = CType(obj, AcadBlock)
Debug.Print(blk.Name & " " & blk.Count.ToString)

item.SubItems.Add(blk.Name)
ListView1.Items.Add(item)

For Each acad_obj In blk
item = New ListViewItem(acad_obj.ObjectName)
item.SubItems.Add(TypeName(acad_obj))
item.SubItems.Add(CType(acad_obj.ObjectID, String))
item.SubItems.Add(CType(acad_obj.OwnerID, String))
item.SubItems.Add(acad_obj.Handle)
If TypeOf acad_obj Is AcadBlockReference Then
blkref = CType(acad_obj, AcadBlockReference)
item.SubItems.Add(blkref.Name)
Else
item.SubItems.Add("")
End If
ListView1.Items.Add(item)
Next

Case TypeOf acad_obj Is AcadBlockReference
'MsgBox("AcadBlockReference")
blkref = CType(obj, AcadBlockReference)
Debug.Print("blkreference " & blkref.Name)

item.SubItems.Add(blkref.Name)
ListView1.Items.Add(item)

Case TypeOf acad_obj Is AcadEntity
'MsgBox("AcadEntity")
ListView1.Items.Add(item)

End Select
End Sub


pop_tree is the recursive routine that makes the tree. It passes a block type parameter and the node that is parent to the block. It immediately adds a node for the block, then it surveys the block and if the constituent is a blockreference, it recurses using the new node as parent. if the constituent is not a block, it creates a new node and makes the entry. it uses the tag to save the handle so when the tree is clicked, the handle is retrieved, and the listview can be populated from the handle.

    Sub pop_tree(ByVal blk As AcadBlock, ByVal parentnode As TreeNode)
Dim acad_ent As AcadEntity
Dim anode As New TreeNode
anode = parentnode.Nodes.Add(blk.Name)
anode.Tag = blk.Handle

For Each acad_ent In blk
If TypeOf acad_ent Is AcadBlockReference Then
Dim blkref As AcadBlockReference
blkref = CType(acad_ent, AcadBlockReference)
blk = acadDoc.Blocks.Item(blkref.Name)
pop_tree(blk, anode)
Else
Dim newnode As New TreeNode
newnode = anode.Nodes.Add(acad_ent.ObjectName)
newnode.Tag = acad_ent.Handle
End If
Next
End Sub


the treeview mouseclick event

    Private Sub TreeView1_NodeMouseClick(sender As Object, e As TreeNodeMouseClickEventArgs) Handles TreeView1.NodeMouseClick
Dim newSelected As TreeNode = e.Node
'Dim newSelected As TreeNode = TreeView1.SelectedNode
Dim strhandle As String
strhandle = CType(newSelected.Tag, String)
listview_by_handle(strhandle)
End Sub
End Class


Autocad Document Object in Visual Studio

Moving up the object model tree, AcadApplication has a property Documents which returns a collection of the open Documents in Autocad. Clicking Application in the clickable Object Model, in the ActiveX Reference Guide, then Documents, leads us to the Documents property. There is some sample code, but no list of properties and methods, which is confusing if we are looking for the Documents object. Sometimes the Property returns an Object with the same name and sometimes the name is different. The property of AcadApplication called Documents returns an AcadDocuments object but you look it up as Documents. Go back to the object model and directly click on Documents, or navigate the tree view in the Objects section to Documents.

clickable object model

The Documents (plural) collection has the minimum number of methods and properties, it is almost the same list as the plain VBA Collection object.
DOCS.ADD starts a new drawing
DOCS.ITEM switches between dwgs already open.
DOCS.OPEN opens an existing drawing.
DOCS.COUNT returns number of open drawings.

Clicking on the ADD Method in the Documents Collection bumps you to the generic ADD page under Methods and you have to find the text that applies to the DOCS object.

ADD takes a name parameter that is the template file to use for a new drawing. if no path is used, it looks in the template folder. you can use either a DWT or a DWG file. if the file is not in the template folder you have to give a complete path.

OPEN is a method in both Documents and Document. It works about the same in each, but Documents has a ReadOnly option that Document does not have.

CLOSE also is a method in both DOCS and DOC. in DOCS it will close all. It has a flag to save changes.

ITEM finds open drawings by either index number or name. The name is the name on the drawing tab plus the extension.

Everything you need to open, create and save drawings is under DOCS or DOC.

the DOCUMENT (single) object is the gateway to all the autocad drawing objects. It has many more properties and methods, including SAVE for previously saved files and SAVEAS for new drawings. It also has a property SAVED to indicate unsaved changes.

Every object in autocad, both the drawing entities and the drawing properties, inherit a prototype object called AcadObject. The actual drawing entities further inherit an object called AcadEntity. In the Objects section of the ActiveX Reference Guide you can find these two prototype objects and their lists of methods and properties under IAcadObject and IAcadEntity. AcadObject has a brief list of methods. AcadEntity has an extensive list of properties and methods. There is also a list of the containers or collection names where these objects are kept – ModelSpace, PaperSpace, Block, SelectionSet and Group. Its the ADDxxx Methods of these primary collection objects that create the drawing. Click on ModelSpace in the object model to see them.

Autocad Application Object in Visual Studio

The root or the trunk of the autocad COM object model is the AcadApplication object. It has to be obtained first to get anything else. If Autocad is running and you know it is running it can be obtained with GetObject.

        Dim app As AcadApplication
app = CType(GetObject(, "Autocad.Application.22"), AcadApplication)


Autocad.Application.22 is a version specific program ID. you can also use a version independent string, Autocad.Application, but if you have more than one autocad version loaded, it will load the last one used and saved, probably.

here is the Autocad Application object Help page.

the clickable object model is easier to use as a reference base.

I cannot tell that any updates have been made for a long time so the exact year does not matter.

the easiest reference of all and its exactly the same material are the ActiveX API documentation found locally in these folders:

%ProgramFiles%\Common Files\Autodesk Shared\acad_aag.chm
%ProgramFiles%\Common Files\Autodesk Shared\acadauto.chm

In Visual Studio with Strict Typing On I added the Ctype function to get rid of the error.

‘app = GetObject(, “Autocad.Application.22”)
‘Option Strict On disallows implicit conversions from ‘Object’ to ‘AcadApplication’

In autocad VBA help, the root object is “ThisDrawing.Application”. This is because ThisDrawing is provided by the implementation as always available. ThisDrawing is AcadApplication.ActiveDocument. You can run all the sample code without changing that part of it by declaring an AcadDocument variable called ThisDrawing.

        Dim ThisDrawing As AcadDocument
ThisDrawing = app.ActiveDocument


The only subtle difference, is that your ThisDrawing will not automatically follow a shifting ActiveDocument. Its only current if the ActiveDocument is not changed.

In the clickable object model, objects are noted with an oval, collections are noted with a rectangle. To get to the drawing objects, the path is Application – Documents – Document. ActiveDocument is a property of the Application, a shortcut to the main drawing entities.

here is an Application test file where i demo’ed many of the application methods and properties. most of the actual code is pasted from the help, going thru the application page and clicking on the method or property. i commented out much of it after i ran it once.

 Sub App_Test1()

Dim app As AcadApplication
app = CType(GetObject(, "Autocad.Application.22"), AcadApplication)

Dim ThisDrawing As AcadDocument
ThisDrawing = app.ActiveDocument

Debug.Print("AcadApplication Name: " & app.Name)
Debug.Print("AcadApplication FullName: " & app.FullName)
Debug.Print("AcadApplication Path: " & app.Path)
Debug.Print("AcadApplication Version: " & app.Version)

Debug.Print("AcadApplication HWND: " & app.HWND)
Debug.Print("AcadApplication LocaleId: " & app.LocaleId)

Debug.Print("AcadApplication Caption: " & app.Caption)
Debug.Print("AcadApplication Height: " & app.Height)
Debug.Print("AcadApplication Width: " & app.Width)
Debug.Print("AcadApplication WindowLeft: " & app.WindowLeft)
Debug.Print("AcadApplication WindowTop: " & app.WindowTop)
Debug.Print("AcadApplication WindowState: " & app.WindowState)

Debug.Print("AcadApplication Visible: " & app.Visible)

'GetInterfaceObject
'not New AutoCAD.AcadAcCmColor
Dim col As AcadAcCmColor
'col = app.GetInterfaceObject("AutoCAD.AcCmColor.22")
'Option Strict On disallows implicit conversions from 'Object' to 'AcadAcCmColor'
col = CType(app.GetInterfaceObject("AutoCAD.AcCmColor.22"), AcadAcCmColor)
Debug.Print("GetInterfaceObject ColorMethod: " & col.ColorMethod.ToString())

'Quit
'Closes the drawing file And exits the AutoCAD application.
'app.Quit()

'ZoomWindow
'Dim pt1() As Double
'Dim pt2() As Double
'pt1 = Pt(1.3, 7.8, 0)
'pt2 = Pt(13.7, -2.6, 0)
'app.ZoomWindow(pt1, pt2)

'ZoomScaled
'Dim scalefactor As Double
''help exampe - Dim scaletype As Integer
'Dim scaletype As AcZoomScaleType
'scalefactor = 2
'scaletype = AcZoomScaleType.acZoomScaledRelative
'app.ZoomScaled(scalefactor, AcZoomScaleType.acZoomScaledRelative)
'app.ZoomScaled(scalefactor, scaletype)

'ZoomAll
'ZoomExtents
'ZoomPickWindow
'ZoomPrevious
app.ZoomExtents()
app.ZoomPickWindow()
app.ZoomAll()
app.ZoomPrevious()

'ZoomCenter
'Dim zcenter() As Double
'Dim magnification As Double
'zcenter = Pt(3, 3, 0)
'magnification = 10
'app.ZoomCenter(zcenter, magnification)

'Update
app.Update()

'' This example uses MenuBar to obtain a reference to the AutoCAD File menu.
'' It then creates a new menu item and inserts it at the bottom of the File menu.
'' The menu item will be automatically removed when AutoCAD is restarted
'Dim menu As AcadPopupMenu, newMenuItem As AcadPopupMenuItem
'Dim openMacro As String
'' Use MenuBar property to obtain reference to the AutoCAD File menu
'Menu = ThisDrawing.Application.MenuBar.Item("&File")
'' Add a menu item to the new menu and
'' assign an Open macro (VBA equivalent of: "ESC ESC _open ")
'openMacro = Chr(3) & Chr(3) & Chr(95) & "open" & Chr(32)
'' Add a menu separator
'Menu.AddSeparator(menu.Count + 1)
'' Add new menu item to File menu
'newMenuItem = menu.AddMenuItem(menu.Count + 1, "NEW MENU ITEM", openMacro)
'MsgBox("A new menu item has been added to the File menu!")

' This example uses MenuGroups to obtain a reference to the AutoCAD main menu.
' It then creates a new menu called TestMenu and inserts a menu item
' into it. The menu is then displayed on the menu bar.
' The menu item will be automatically removed when AutoCAD is restarted
'Dim currMenuGroup As AcadMenuGroup
'Dim newMenu As AcadPopupMenu
'' Use MenuGroups property to obtain reference to main AutoCAD menu
'currMenuGroup = ThisDrawing.Application.MenuGroups.Item("ACAD")
'' Create the new menu in this group
'newMenu = currMenuGroup.Menus.Add("TestMenu")
'' Add a menu item to the new menu and
'' assign an Open macro (VBA equivalent of: "ESC ESC _open ")
'openMacro = Chr(3) & Chr(3) & Chr(95) & "open" & Chr(32)
'newMenuItem = newMenu.AddMenuItem(newMenu.Count + 1, "Open", openMacro)
'' Display the menu on the menu bar
'newMenu.InsertInMenuBar(ThisDrawing.Application.MenuBar.Count + 1)
'MsgBox("A new menu has been added to the ACAD menu system!")

'Preferences
' This example returns the current setting of
' LogFilePath from the preferences object.
Dim preferences As AcadPreferences
preferences = ThisDrawing.Application.Preferences
' Retrieve the current LogFilePath value
MsgBox("The current value for LogFilePath is " & preferences.Files.LogFilePath, , "Preferences Example")

'Documents
' This example obtains a reference to the Documents collection
' and displays information about the loaded documents.
Dim Document As AcadDocument
Dim msg As String
msg = vbCrLf

'' Cycle through the Documents collection and retrieve the names
'' of the loaded documents
For Each Document In app.Documents
msg = msg & Document.Name & vbCrLf
Next

'' Display loaded document information
If app.Documents.Count > 0 Then
MsgBox("The loaded documents are: " & msg)
Else
MsgBox("There are no loaded documents!")
End If

' This example reads and displays the current window state of the AutoCAD application.
Dim CurrentState As String = ""
' Use the "WindowState" variable to determine the window state of AutoCAD
Select Case app.WindowState
Case AcWindowState.acMin : CurrentState = "Minimized"
Case AcWindowState.acMax : CurrentState = "Maximized"
Case AcWindowState.acNorm : CurrentState = "Normal Size"
End Select

' Display window state
MsgBox("AutoCAD is now: " & CurrentState)

End Sub