Andrew Truckle
Andrew Truckle

Reputation: 19197

Is there an alternative way to dynamically create this array of double values?

I am developing a VBA macro to use in AutoCAD. At the moment it converts a circle into a 3D polyline and in itself it is working perfectly. It is just the start and I will be able to put some flesh on the final routine.

This is the VBA macro:

 Sub CircleToPolyline()
    Dim objSel As AcadEntity
    Dim myCircle As AcadCircle
    Dim pickedPoint As Variant

    ' Get the user to select a circle
    ' Eventually we will use a Selection Set with Filtering to pick them all in the drawing
    Call ThisDrawing.Utility.GetEntity(objSel, pickedPoint, "Select Circle:")
    If objSel.ObjectName <> "AcDbCircle" Then GoTo SKIP

    Set myCircle = objSel
    Dim dAngle As Double, dAngleStep As Double, dMaxAngle As Double

    dAngle = 0# ' We always start at 0 degrees / radians
    dAngleStep = 0.17453293 ' This is 10 degrees in radians
    dMaxAngle = 6.28318531 ' This is 360 degrees in radians
    ' So our polyline will always have 36 vertices

    Dim ptCoord() As Double
    Dim ptProject As Variant
    Dim i As Integer

    i = 0
    While dAngle < dMaxAngle
        ReDim Preserve ptCoord(0 To i + 2) ' Increase size of array to hold next vertex

        ' Calculate the next coordinate on the edge of the circle
        ptProject = ThisDrawing.Utility.PolarPoint(myCircle.center, dAngle, myCircle.Radius)

        ' Add to the coordinate list
        ptCoord(i) = ptProject(0)
        ptCoord(i + 1) = ptProject(1)
        ptCoord(i + 2) = ptProject(2)

        ' Increment for next coordinate/angle on the circle edge
        dAngle = dAngle + dAngleStep
        i = i + 3
    Wend

    ' Create the 3D polyline
    Dim oPolyline As Acad3DPolyline
    Set oPolyline = ThisDrawing.ModelSpace.Add3DPoly(ptCoord)
    oPolyline.Closed = True
    oPolyline.Update

SKIP:

 End Sub

I am just wondering if there are any alternative methods for managing my dynamic array (ptCoord)? For example, is there any way that I can just add the ptProject into a dynamic list and then just use this list in the Add3dPoly routine?

The thing is, PolarPoint returns a variant. And ptCoord is a array of doubles (which is what Add3dPoly expects). This is why I have done it like this. I have not used variants (except for handling return values).

My code is quite simple and sufficient, but if it can be further simplified I would be interested in knowing (given the context of VBA and AutoCAD environment).

I hope my question is clear. Thank you.

Upvotes: 0

Views: 1028

Answers (3)

user3598756
user3598756

Reputation: 29421

you can skip arrays dimming altogether by use of AppendVertex() method

Option Explicit

Sub CircleToPolyline()

    Dim myCircle As AcadCircle
    Dim circleCenter As Variant, circleRadius As Double
    Dim dAngle As Double, dAngleStep As Double, dMaxAngle As Double
    Dim oPolyline As Acad3DPolyline

    'Get the user to select a circle
    Set myCircle = GetCircle(circleCenter, circleRadius)
    If myCircle Is Nothing Then Exit Sub

    dAngle = 0# ' We always start at 0 degrees / radians
    dAngleStep = 0.17453293 ' This is 10 degrees in radians
    dMaxAngle = 6.28318531 ' This is 360 degrees in radians

    Set oPolyline = GetStarting3dPoly(circleCenter, circleRadius, dAngle, dAngleStep) ' Create the 3D polyline with first two points
    Do While dAngle + dAngleStep <= dMaxAngle
        dAngle = dAngle + dAngleStep ' Increment for next coordinate/angle on the circle edge
        oPolyline.AppendVertex ThisDrawing.Utility.PolarPoint(circleCenter, dAngle, circleRadius) 'append a new vertex
    Loop

    'finish the polyline
    oPolyline.Closed = True
    oPolyline.Update

End Sub


Function GetStarting3dPoly(circleCenter As Variant, circleRadius As Double, dAngle As Double, dAngleStep As Double) As Acad3DPolyline
    Dim ptCoord(0 To 5) As Double
    Dim ptCoords As Variant

    ptCoords = ThisDrawing.Utility.PolarPoint(circleCenter, dAngle, circleRadius)
    ptCoord(0) = ptCoords(0)
    ptCoord(1) = ptCoords(1)
    ptCoord(2) = ptCoords(2)

    dAngle = dAngle + dAngleStep
    ptCoords = ThisDrawing.Utility.PolarPoint(circleCenter, dAngle, circleRadius)
    ptCoord(3) = ptCoords(0)
    ptCoord(4) = ptCoords(1)
    ptCoord(5) = ptCoords(2)

    Set GetStarting3dPoly = ThisDrawing.ModelSpace.Add3DPoly(ptCoord)
End Function


Function GetCircle(circleCenter As Variant, circleRadius As Double) As AcadCircle
    Dim objSel As AcadEntity
    Dim pickedPoint As Variant

    ' Get the user to select a circle
    ' Eventually we will use a Selection Set with Filtering to pick them all in the drawing
    ThisDrawing.Utility.GetEntity objSel, pickedPoint, "Select Circle:"
    If objSel.ObjectName = "AcDbCircle" Then
        Set GetCircle = objSel
        circleCenter = objSel.Center
        circleRadius = objSel.Radius
    End If
End Function

as you see I also extracted some actions from the main code and confined them into functions, so to improve further enhancing of your code and its functionalities

Upvotes: 1

Ambie
Ambie

Reputation: 4977

It is feasible to allocate a chunk of memory and write the sequential results of each of your PolarPoint calls to it. You could then copy that memory to your ptCoord array in one call. However, the APIs are very awkward, there'd be a lot of fiddling with pointers (never straightforward in VBA) and most memory coding errors result in a complete Excel crash. For 108 data points it doesn't seem worth the effort.

I'd say your notion of iterating each of the result arrays and writing them individually to ptCoord is as good a way as any.

Your comments

'We always start at 0 degrees / radians, and 'So our polyline will always have 36 vertices

suggest your ptCoord array will have a fixed dimension (ie 36 * 3). If that's the case couldn't you just dimension the array once? Even if you want to vary the number of degrees to draw through, you could still dimension your array at (n * 3) without having to ReDim Preserve on every iteration.

A snippet of your code could therefore become:

Dim alpha As Double
Dim index As Integer
Dim i As Integer
Dim ptCoord(0 To 107) As Double
Dim ptProject() As Double
Dim pt As Variant
...
For i = 0 To 35
    ptProject = ThisDrawing.Utility.PolarPoint(myCircle.center, dAngle, myCircle.Radius)
    For Each pt In ptProject
        ptCoord(index) = pt
        index = index + 1
    Next
    alpha = alpha + 0.174532925199433
Next

Upvotes: 2

Gary Evans
Gary Evans

Reputation: 1890

Your code appears good to me, I was going to suggest a two dimensional array: -

Dim ptCoord(2,0)
...
ptCoord(0,0) = ptProject(0)
ptCoord(1,0) = ptProject(1)
ptCoord(2,0) = ptProject(2)

ReDim Preserve ptCoord(2,1)
ptCoord(0,1) = ptProject(0)
ptCoord(1,1) = ptProject(1)
ptCoord(2,1) = ptProject(2)

The second dimension in a two dimensional array can be dynamically re-dimensioned. But I'm not sure this will save you anything and it may not work with Add3DPoly.

You could use UBound to save on the i variable.

ReDim Preserve ptCoord(UBound(ptCoord,1)+3)

In the above I haven't declared the lower/base (0 To) as 0 is the default base, I have then used UBound (Upper bound) to get the size of the array and added 3 to that to make it 3 larger.

UBound([Array],[Dimension])

Array being the array you want to check

Dimension being the dimension you want to check the size on, it has a base of 1 not 0 (so the first dimension is 1 not 0, the second is 2 not 1, and so on...)

You can omit Dimension and the first will be assumed.

To access it without i you could use: -

ptCoord(UBound(ptCoord,1)-2) = ptProject(0)
ptCoord(UBound(ptCoord,1)-1) = ptProject(1)
ptCoord(UBound(ptCoord,1)) = ptProject(2)

Upvotes: 1

Related Questions