[V8i VBA] Finding Tangent Vector of point on an element

I have this cell:

and upon placement in a model its orientation is set using matrixidentity and looks like this:

Whereas I want the cells to be placed with a CCW rotation so that they are perpendicular to the element the are placed upon like so:

I've been looking into old posts on rotating lines/cells perpendicular to an element. Typical responses that I see are calculate the tangent vector of the point on the element by:

  1. Converting the element to a B-spline
  2. Use BsplineCurve.EvaluatePointTangent

I am unfamiliar with this method and any examples I have found don't make a lot of sense to me. In particular I am not totally sure what values I should be using for the arguments Tangent and Parameter. I understand Tangent is a point3d expression but I dont know what value I need to provide it. The only point3d I know I am using is that for the insertion point of the cells. As for parameter,  I think I read in a thread that  Parameter is a value between 0 and 1 which corresponds as a percentage (expressed as a decimal) of the point on the overall length of the target element measured from the startpoint e.g. 0 = startpoint and 1= endpoint. As that is my understanding(rightly or wrongly), I have assigned a double variable which calculates: distance of point on element from start point/BsplineCurve.ComputeCurveLength. It gives the correct percentages (expressed as a decimal) yet I am at a loss what to do next. The VBA help states BsplineCurve.EvaluatePointTangent should be assigned to a point3d. I have created a new point3d variable but unsure how I extract the tangent vector and then use it apply the required rotation as the only properties it gives me are .X .Y & .Z values. I'm a bit in the dark on this topic and have probably made some incorrect assumptions so any pointers would be appreciated.

Parents
  • Unknown said:
    I want the cells to be placed with a CCW rotation so that they are perpendicular to the element

    Is your DGN model 2D or 3D?  In 2D perpendicular means 'either side of' whereas in 3D there are infinite possibilities.

     
    Regards, Jon Summers
    LA Solutions

  • Artur Goldsweer confirmed that VBA's EvaluatePointTangent method is broken.  Even better, Artur proposed and tested a workaround using MDL function mdlElmdscr_pointAtDistance.

    I've updated the VBA project described in article MicroStation VBA: Place Points along Line.  The code now creates a visible tangent at each point placed on a line.

     
    Regards, Jon Summers
    LA Solutions

  • Unknown said:
    I wrote the following test snippet to verify the correction of method EvaluatePointTangent

    Thanks, but what version of MicroStation is that?  Several of us found that EvaluatePointTangent is not working correctly with V8i.

     
    Regards, Jon Summers
    LA Solutions

  • V8i version. I read your code and seems find the calculation of u is incorrect. For a linear curve, your calculation is correct but for non-linear curve, you can't use part-len/total-len to get u value.



  • Unknown said:
    V8i version. I read your code and seems find the calculation of u is incorrect. For a linear curve, your calculation is correct but for non-linear curve, you can't use part-len/total-len to get u value.

    Hi Yongan,

    The obvious follow-up question is then, how do you calculate u in the case of a non-linear curve?

  • There are plenty methods under BsplineCurve. If you know the distance from start point and want to get the u value of it, you can call EvaluatePointAtDistance.

    The Point3d and Parameter (u value) are all returned value.



  • Hello Barry,

    I wrote an example for your purpose. It works well.

    Sub PlaceCellAlongBsplineCurve()
        Dim oBCurveElem As BsplineCurveElement
        Dim oBCurve As BsplineCurve
        Set oBCurveElem = ActiveModelReference.GetElementByID(DLongFromLong(7514))
        Set oBCurve = oBCurveElem.ExtractBsplineCurve
        
        '---- 1. Get 1/10 points and parameters(u) of BCurve ------------
        Dim pts(10) As Point3d
        Dim u(10) As Double
        Dim i As Integer
        i = 0
        For d = 0# To oBCurveElem.Length Step oBCurveElem.Length / 10#
           pts(i) = oBCurve.EvaluatePointAtDistance(u(i), d)
           i = i + 1
        Next
        
        '---- 2. Place cell at 1/10 points of BCurve ------------
        AttachCellLibrary "linepa.cel"
        
        Dim oCell As CellElement
        Dim tangent As Point3d
        Dim matrix As Matrix3d
        For i = 0 To 10
           oBCurve.EvaluatePointTangent tangent, u(i)
           matrix = Matrix3dFromRotationBetweenVectors(Point3dFromXYZ(0, 1, 0), tangent)
           Set oCell = CreateCellElement2("LT1", pts(i), Point3dFromXYZ(1, 1, 1), True, matrix)
           ActiveModelReference.AddElement oCell
        Next
    End Sub

    The created drawing is as below:



  • Hi Yongan, I tried the above code in a fresh macro but it doesn't work, d isn't declared ( I think its mean to be double?) and I don't know what element type ID 7514 is so I get an error on Set oBCurveElem = ActiveModelReference.GetElementByID(DLongFromLong(7514)). I note in a previous post you mentioned type 510 is a bsplinecurve, I tried using that number instead and have a bspline in a test file but I get type mismatch error.
  • 7514 is element id of my test BsplineCurve because I worked from home computer. You certainly should change this value to match your DGN file.

    Now I back my office, and use 510 as my BsplineCurve elemment Id. Also I add d as Double definition.

    Option Explicit
    Sub PlaceCellAlongBsplineCurve()
        Dim oBCurveElem As BsplineCurveElement
        Dim oBCurve As BsplineCurve
        Set oBCurveElem = ActiveModelReference.GetElementByID(DLongFromLong(510))
        Set oBCurve = oBCurveElem.ExtractBsplineCurve
         
        '---- 1. Get 1/10 points and parameters(u) of BCurve ------------
        Dim pts(10) As Point3d
        Dim u(10) As Double
        Dim i As Integer
        Dim d As Double
        i = 0
        For d = 0# To oBCurveElem.Length Step oBCurveElem.Length / 10#
           pts(i) = oBCurve.EvaluatePointAtDistance(u(i), d)
           i = i + 1
        Next
         
        '---- 2. Place cell at 1/10 points of BCurve ------------
        AttachCellLibrary "linepa.cel"
         
        Dim oCell As CellElement
        Dim tangent As Point3d
        Dim matrix As Matrix3d
        For i = 0 To 10
           oBCurve.EvaluatePointTangent tangent, u(i)
           matrix = Matrix3dFromRotationBetweenVectors(Point3dFromXYZ(0, 1, 0), tangent)
           Set oCell = CreateCellElement2("LT1", pts(i), Point3dFromXYZ(1, 1, 1), True, matrix)
           ActiveModelReference.AddElement oCell
        Next
    End Sub

    Attached is my test DGN file.

    TestBspline.dgn



    Answer Verified By: Barry Lothian 

  • Ah I see now, for some reason I thought the elementID was an generic element type code not a unique code for that element. I'll put it down to posting at 2am...

    I inputted the correct elementID this time and can confirm it works as you said. I'll likely spend some time changing some values so I get a proper understanding what each line of code is doing so thanks for taking the time to provide an example.
  • Just when I thought everything was fine...

    With a test b-spline I am finding odd behaviour with the last cell being placed away from the element as per the attached video.

    Any thoughts?

    video link

  • Unknown said:
    With a test b-spline I am finding odd behaviour with the last cell being placed away from the element

    Unknown said:

        Dim pts(10) As Point3d
        Dim u(10) As Double
        Dim d As Double
        i = 0
        For d = 0# To oBCurveElem.Length Step oBCurveElem.Length / 10#
           pts(i) = oBCurve.EvaluatePointAtDistance(u(i), d)
           i = i + 1
        Next

    The loop depends on an accurate measurement.  It's possible, due to floating-point comparison roundoff, that there are not exactly ten iterations of that loop.  Add this in the loop to check what value i ends up...

    Debug.Print "i=" & CStr(i)

    Unknown said:
        For i = 0 To 10
           oBCurve.EvaluatePointTangent tangent, u(i)
           matrix = Matrix3dFromRotationBetweenVectors(Point3dFromXYZ(0, 1, 0), tangent)
           Set oCell = CreateCellElement2("LT1", pts(i), Point3dFromXYZ(1, 1, 1), True, matrix)
           ActiveModelReference.AddElement oCell
        Next

    In this loop the comparison is exact, because i is an integer variable.  However, we're now iterating from 0 to 10, which is eleven steps.  The array u() is dimensioned to ten.  This looks like an off-by-one error.  VBA arrays, by default, start at 1.

     
    Regards, Jon Summers
    LA Solutions

Reply
  • Unknown said:
    With a test b-spline I am finding odd behaviour with the last cell being placed away from the element

    Unknown said:

        Dim pts(10) As Point3d
        Dim u(10) As Double
        Dim d As Double
        i = 0
        For d = 0# To oBCurveElem.Length Step oBCurveElem.Length / 10#
           pts(i) = oBCurve.EvaluatePointAtDistance(u(i), d)
           i = i + 1
        Next

    The loop depends on an accurate measurement.  It's possible, due to floating-point comparison roundoff, that there are not exactly ten iterations of that loop.  Add this in the loop to check what value i ends up...

    Debug.Print "i=" & CStr(i)

    Unknown said:
        For i = 0 To 10
           oBCurve.EvaluatePointTangent tangent, u(i)
           matrix = Matrix3dFromRotationBetweenVectors(Point3dFromXYZ(0, 1, 0), tangent)
           Set oCell = CreateCellElement2("LT1", pts(i), Point3dFromXYZ(1, 1, 1), True, matrix)
           ActiveModelReference.AddElement oCell
        Next

    In this loop the comparison is exact, because i is an integer variable.  However, we're now iterating from 0 to 10, which is eleven steps.  The array u() is dimensioned to ten.  This looks like an off-by-one error.  VBA arrays, by default, start at 1.

     
    Regards, Jon Summers
    LA Solutions

Children
  • Hello Jon,

    Good detective. I revised my demo code as below (Also adding statement "Option Base 0" to ask VBA's array's index begins from 0):

    Option Explicit
    Option Base 0
    Sub PlaceCellAlongBsplineCurve()
        Dim oBCurveElem As BsplineCurveElement
        Dim oBCurve As BsplineCurve
        Set oBCurveElem = ActiveModelReference.GetElementByID(DLongFromLong(820))
        Set oBCurve = oBCurveElem.ExtractBsplineCurve
         
        '---- 1. Get 1/10 points and parameters(u) of BCurve ------------
        Dim pts(10) As Point3d
        Dim u(10) As Double
        Dim i As Integer
        Dim d As Double
        For i = 0 To 10
           d = oBCurveElem.Length * i / 10
           pts(i) = oBCurve.EvaluatePointAtDistance(u(i), d)
        Next
         
        '---- 2. Place cell at 1/10 points of BCurve ------------
        AttachCellLibrary "linepa.cel"
         
        Dim oCell As CellElement
        Dim tangent As Point3d
        Dim matrix As Matrix3d
        For i = 0 To 10
           oBCurve.EvaluatePointTangent tangent, u(i)
           matrix = Matrix3dFromRotationBetweenVectors(Point3dFromXYZ(0, 1, 0), tangent)
           Set oCell = CreateCellElement2("LT1", pts(i), Point3dFromXYZ(1, 1, 1), True, matrix)
           ActiveModelReference.AddElement oCell
        Next
    End Sub



  • Morning Yongan / Jon

    I see the problem, your revised code works. I note the final point at the end of the line (viewed at maximum zoom) isn't precisely at the end of the line but debug.print shows that's because U = 0.99..... so it seems to be a floating point accuracy error.

    I've gone back to my previous macro and attempted to update the code using the above principles. I've been able to place my cells at specified distances and perpendicular to the element however the example appears to have pts as an array of Point3D and U as an array of double. My issue is that I am placing cells a distances along the element (not at subdivisions along the element) and the number of the array needs to be flexible instead of hardcoded. The size of the array needs to match the number of rows(lines) in a CSV file (minus 1 if array starts at 0). For my test I hard-coded a long constant with a value in that matches the number of rows in a test CSV file so that I could determine if the code works, which it does. What changes do I need to have these arrays dynamic?

    Partial relevant code:

    'Temporary constant, I have 4 rows in a CSV containing a single distance value.
    Const lTotalPoints As Long = "4" '-----Code from sub which determines element type and checks cell placement distance from origin is less than length of element----- .... Case msdElementTypeBsplineCurve origin = m_oLine.AsBsplineCurveElement.PointAtDistance(m_strData(N, 0)) valid = m_strData(N, 0) < m_oLine.AsBsplineCurveElement.length End Select If valid Then PlaceCellAlongBsplineCurve cellName, N nPlaced = 1 + nPlaced End If Sub PlaceCellAlongBsplineCurve(ByVal cellName As String, ByVal N As Long) Dim oBCurve As BsplineCurve Dim graphicGroup As Long graphicGroup = Application.UpdateGraphicGroupNumber Set oBCurve = New BsplineCurve oBCurve.FromElement m_oLine '---- 1. Get points at specified distances along BCurve and parameters(u) of BCurve ------------ Dim pts(lTotalPoints) As Point3d Dim u(lTotalPoints) As Double Dim i As Integer Dim d As Double For i = 0 To lTotalPoints d = m_strData(N, 0) Debug.Print "i=" & CStr(i) & " d=" & CStr(d) pts(N) = oBCurve.EvaluatePointAtDistance(u(N), d) Next '---- 2. Place cell at distances along BCurve ------------ Dim oCell As CellElement Dim tangent As Point3d Dim matrix As Matrix3d oBCurve.EvaluatePointTangent tangent, u(N) matrix = Matrix3dFromRotationBetweenVectors(Point3dFromXYZ(0, 1, 0), tangent) Set oCell = CreateCellElement2(cellName, pts(N), Point3dFromXYZ(1, 1, 1), True, matrix) oCell.graphicGroup = graphicGroup ChainageValues oCell, m_strData, N ActiveModelReference.AddElement oCell End Sub
  • Unknown said:
    What changes do I need to have these arrays dynamic?

    You've drifted away from the thread topic of tangents to VBA basics.

    When you declare an array with a size (e.g. Dim numbers(5) As Double) then the array size is fixed.

    If you declare an array with no size (e.g. Dim numbers() As Double) then the array has no size.  You can resize the array at run-time using the VBA Redim statement.  Lookup Redim in VBA help or search the VBA web for more information.

     
    Regards, Jon Summers
    LA Solutions

  • The topic was purposely made a discussion rather than a specific question due to the nature of the task specified in the original post. The subject matter may be familiar to you but its new ground to me so one solution often leads to further questions. Any divergence from the topic title through post progression is still relevant to the original topic as the questions form part of my desired outcome to place cells perpendicular to a target element a specific distances.

    You are correct regarding arrays, I realised myself after reviewing other code in the macro that uses dynamic arrays and easily solved my problem.

        Dim pts()                             As Point3d
        Dim u()                                As Double
        Dim i                                    As Integer
        Dim d                                  As Double
    d = m_strData(N, 0) ReDim pts(0 To UBound(m_strData)) ReDim u(0 To UBound(m_strData)) pts(N) = oBCurve.EvaluatePointAtDistance(u(N), d)