[V8i SS10 VBA]Advice sought for transforming an Element in a 3D file using provided datapoints

I have a working project which allows the alignment (moving and rotation) and optional scaling of certain element types on the XY plane within a 2D file, I am now in the process of developing it to work in the 3D file. The 2D mode requires an element to be selected and then the user has to provide 2 pairs of source and destination points. I have already added the functionality of the user being given the choice to enter a 3rd pair of source and destination points should a 3D manipulation be required. With 3No. supplied destination datapoints I am able to construct 2 vectors and thus calculate the cross product which I then convert to a unit vector. For a visual validation that the what is being calculated is correct,  I have generated a string (from debug output in the immediate window) which can be copied and pasted in the keyin window with the line tool active to graphically represent the unit vector. With this I can measure the line element is perpendicular to the target surface.

Pictures help, this is my test geometry.

The pyramid is the source element, the wedge is destination element (specifically its sloping top face).
The numbers are the sequence of user-provided datapoints
Constructed Vectors:

  • U between datapoints 2 and 4
  • V between datapoints 2 and 6
  • W the cross product of U and W

Currently I am only able to align points 1 & 3 with 2 & 4 however, as there is a reliance in 2D mode on the method Matrix3dFromAxisAndRotationAngle (with the world Z-axis), if the wedge base was not planar with the world XY plane, the pyramid would not align correctly so I believe the operations for 2D mode and 3D mode will need a different approach.

Where I am currently struggling is determining the appropriate steps to employ and the correct available methods to utilise. I've been looking through the VBA help at the various Point3D/Matrix3D/Plane3D/Transform3D/Vector3D methods however there are so many that have names which sound applicable but I'm admittedly unsure which are relevant to what I am trying to achieve.

I'd appreciate any help on the steps I should be considering and what methods I should be using.

Parents
  • this is my test geometry

    Good illustration!  If only all questions were as clear.

    I believe the operations for 2D mode and 3D mode will need a different approach

    2D is simply a special case of 3D.  If you can get things working in 3D then correct behaviour in 2D should follow automatically.

    There are so many [methods] that have names which sound applicable

    I agree.  I have to study the method names closely to figure out when one might be applicable.

    Matrix3dFromAxisAndRotationAngle

    Stop thinking in angles, whether degrees or radians.  Angles are for humans.  Think in rotation and transformation matrices (Matrix3d and Transform3d). 

    It can help to think in pure transforms rather than those supplied by the API.  When you've figured out what transforms to apply then you can better interpret the VBA API.  If you like printed material, take a look at some relevant books.

     
    Regards, Jon Summers
    LA Solutions

  • 2D is simply a special case of 3D.  If you can get things working in 3D then correct behaviour in 2D should follow automatically.

    I presume you have got your 2D and 3D mixed up as the 2D mode works fine.

    Matrix3dFromAxisAndRotationAngle

    Stop thinking in angles, whether degrees or radians.  Angles are for humans.  Think in rotation and transformation matrices (Matrix3d and Transform3d). 

    I'm a bit confused and unsure what you mean by this or if you have an issue with that method? I don't believe I said anything about thinking angles? To clarify, the reason I mentioned Matrix3dFromAxisAndRotationAngle is because its a method used in my 2D operation code. This code originated from an example within the VBA Help (Search for Combining Transforms Example). I highlighted it because I didn't think it would provide the correct result in the 3D mode because its constrained by the 1st parameter which is a world axis, perhaps it will if the method is used with the other 2 Axis but I've not tested that yet. Though the 2nd parameter is an angle, its not a user input but calculated using vectors.

    It can help to think in pure transforms rather than those supplied by the API.  When you've figured out what transforms to apply then you can better interpret the VBA API.  If you like printed material, take a look at some relevant books.

    I'll have to take your word for it. Perhaps you need a solid grounding and familiarity with geometric/vector algebra but not everyone has that. Books are all fine and well but there is a distinct separation between what the theory that books show and how operations are done in VBA. For example, I've looked at Essential Mathematics for Computer Graphics some years ago and recall reading how transform operations should be done by moving an object to the 0,0,0 origin, perform the required operation (rotate/mirror/shear/scale etc...) and then transform back yet I don't believe a single example in the VBA help demonstrates that principle (at least I've not seen any that show that) . In fact the method Transform3dFromMatrix3dAndFixedPoint3d which I have also used, relies on a user defined origin. I can only assume that hidden within deep with the code of that method it does in fact transform to and from the world origin but its not exposed to the user. What's also different is the manual approach of working with matrices which I don't think anyone would want to write out manually in the IDE but instead should be using the appropriate available method from the API. Knowing which to use is the challenge, perhaps theory helps you make that decision but it would be beneficial if a better resources were available exampling the useage of every method type and some simple graphics to reinforce certainly wouldn't go amiss either.

  • One more comment: Maybe I am wrong, but I think one from problem at the background is that an effort is to solve everything and to find the right solution at once.

    My approach, because I am lazy and I know I am constantly failing and making mistakes, is to go step by step: Try to implement code, that based on selected element and one point identification, will move the element to model origin. In the next step, I would add 2nd point, which will define orientation for rotation to X axis. After that, 3rd point can be added to define orientation to Y, so the element will be in "basic position".

    It not only allows to focus always on one problem only, but also allows to learn (or refresh in my case ;-) the matrix operations. Even more, because rotation is involved and the rotation has always to be done in basic position (because done around 0,0,0), the code will be correct and can be reused, so it's not "test code" only.

    Regards,

      Jan

  • Hi Jan,

    One more comment: Maybe I am wrong, but I think one from problem at the background is that an effort is to solve everything and to find the right solution at once.

    If you are thinking that I may be looking for a 'magic solution' instead of a stepped approach then no I believe its unlikely that such a thing exists. I fully expect to have to perform operations in stages by multiplying matrices and combining transforms as exampled in the VBA help.

    My approach, because I am lazy and I know I am constantly failing and making mistakes, is to go step by step: Try to implement code, that based on selected element and one point identification, will move the element to model origin. In the next step, I would add 2nd point, which will define orientation for rotation to X axis. After that, 3rd point can be added to define orientation to Y, so the element will be in "basic position".

    It not only allows to focus always on one problem only, but also allows to learn (or refresh in my case ;-) the matrix operations. Even more, because rotation is involved and the rotation has always to be done in basic position (because done around 0,0,0), the code will be correct and can be reused, so it's not "test code" only.

    If I follow you correctly, what you are suggesting sounds like the theoretical approach I described previously and I believe I do see it mentioned with the VBA API Transform PDF you linked though not in the VBA help file for some some reason. Again this appears to be a clear difference between 3D and 2D where I've not had to translate to and from the origin.

  • and I believe I do see it mentioned with the VBA API Transform PDF you linked though not in the VBA help file for some some reason

    The document is a mixture of what is in VBA help already (but sometimes formulated / described in a different way) and summary of general mandatory knowledge of matrix algebra, where is no reason to have it in VBA doc, because it's not API.

    Again this appears to be a clear difference between 3D and 2D where I've not had to translate to and from the origin.

    When matrices are used to describe transformations like rotation or scaling, it's always done against the origin. There is no difference between 2D and 3D, and as Jon wrote, 2D is just a subset of 3D operations ... so all 2D operations can be expressed using the same 3D matrices (where some values / rows / cells) are 0.

    Regards,

      Jan

  • There is no difference between 2D and 3D

    I don't doubt what you say but I don't think that you are following the point I am trying to make.

    Consider this example procedure from the VBA help:

    Sub ScaleRotateAndMove(ele As Element, dblAngle As Double, _
        pntFixed As Point3d, pntDistance As Point3d, pntScaleFactors As Point3d)
    
        Dim mtrxRotation As Matrix3d
        Dim mtrxScale As Matrix3d
        Dim mtrxCombined As Matrix3d
        Dim trns As Transform3d
        Dim trnsMove As Transform3d
        
        '  Create the rotation matrix
        mtrxRotation = Matrix3dFromAxisAndRotationAngle(2, dblAngle)
        
        '  Create a scaling matrix
        With pntScaleFactors
            mtrxScale = Matrix3dFromScaleFactors(.X, .Y, .Z)
        End With
        
        ' Multiply the matrices to create a matrix that scales and rotates
        mtrxCombined = Matrix3dFromMatrix3dTimesMatrix3d(mtrxRotation, mtrxScale)
        
        '  Create a transform to rotate and scale about a fixed point
        trns = Transform3dFromMatrix3dAndFixedPoint3d(mtrxCombined, pntFixed)
        
        '  Create a transform for moving the element
        With pntDistance
            trnsMove = Transform3dFromXYZ(.X, .Y, .Z)
        End With
        
        '  Multiply the 2 transformation matrices to create a transformation
        '  matrix that rotates, scales, and moves the element. Be certain to get the
        '  arguments to Transform3dFromTransform3dTimesTransform3d in he 
        '  proper order since reversing the order of the arguments creates 
        '  a transform that moves the elements, and then rotates and scales
        '  the moved element.
        trns = Transform3dFromTransform3dTimesTransform3d(trnsMove, trns)
        ele.Transform trns
    End Sub

    At which point do you see written in code, the translation to and from the world origin? The answer is of course you don't because as I said in my reply to Jon, I can only assume that takes place within the Transform3dFromMatrix3dAndFixedPoint3d Method. The difference I am highlighting is that you are suggesting manually coding translation of an object to and from the world origin (which I have no quandry with) yet I explicity did not have to do that for the 2D mode of my project. This makes me wonder if there are method(s) available which would save coding the translation to and from the world origin that could be used for the 3D operation. It not, so be it and I will proceed on that basis and use the world origin.

  • but I don't think that you are following the point I am trying to make.

    I think I understand your point, but it's where we pass and have different perspectives.

    My experience tells me that the crucial is matrix algebra knowledge and understanding how operations must be ordered and how they can be merged together, regardless the final code is 2D only or general for 2D/3D. When all operations are defined, sometimes something useful in VBA can be found, or, more often, some operations can be combined and not expressed in the code individually.

    At which point do you see written in code, the translation to and from the world origin?

    I did not wrote anything about written code. What I wrote - and it's again the matrix algebra is priority for me and how VBA is used can only follow that - is that some operations (rotation, scaling) have always be done around the origin. Whether any method hides this fact, because does some transformations automatically, does not change or break this rule, it's only a help at API level.

    And it's clearly expressed in the method description: The resulting transformation will leave the fixed point unchanged and apply whatever effects are contained in the matrix as if the fixed point is the origin.

    The difference I am highlighting is that you are suggesting manually coding translation of an object to and from the world origin

    Well, I did not write exactly that ;-) What I wrote at first is that in general it's transformation between 2 system, defined by 2 sets of points. To implement it step by step I recommend as the safest way how to stay away from many traps in 3D transformations, because every step can be evaluated and tested (which is the most important and crucial requirement in software development). And as I repeated several times, when the whole process is functional, it's typically clearer what transformations can be merged together. Maybe, at the end, there will be one or two matrices only. But, it's beyond my mental abilities to try to implement such transformation it directly.

    which would save coding the translation to and from the world origin that could be used for the 3D operation.

    For me, the code simplicity (for reading and understanding) is by magnitude more important than code length, so personally, it's not the priority to save a few lines.

    Regards,

      Jan

  • It not, so be it and I will proceed on that basis and use the world origin.

    This is the standard way and as you can see in the linked PDF (and many other tutorials), when "fixed point" is used anywhere, the movement is always done at background (translation and inverse translation are applied).

    which would save coding the translation to and from the world origin

    Is it really so important? Even when written in very verbose way, I do not see anything that wastes coding (time) and should be removed to save something.

    Of course, the code can be refactored to create the final transformation directly. But I am not sure whether it leads to higher quality code and easier maintenance.

    transformation example.zip

    Regards,

      Jan

    Answer Verified By: Barry Lothian 

  • Hi Jan,

    For me, the code simplicity (for reading and understanding) is by magnitude more important than code length, so personally, it's not the priority to save a few lines.
    Is it really so important? Even when written in very verbose way, I do not see anything that wastes coding (time) and should be removed to save something.

    I fully agree with that sentiment and I have no issue writing out longer code when required. The situation I was seeking to avoid is for example, writing out a long function to perform a specific task only for someone to look at the code and question why I bothered to take such a long-winded approach when I could have just used <insert name of your appropriate obscure method of choice> instead when of course my answer would be, I didn't use it because I had no prior experience with it so was unaware of its relevance.

    I appreciate the example and have briefly tested its functionality; it certainly gives the desired result. I wasn't looking for a coded solution but I find I learn best by reviewing and dissecting examples so it will be extremely valuable to review and learn from so I thank you for taking the time to put the project together. I can already see you have taken a different approach to what I have and I will likely keep my 'user experience'. One thing I have immediately noticed which I had not seen before is using a class as a member variable. I've also been using different classes sequentially yet you are creating an class instance within the instance of a different class before its complete - Every day is a school day but reviewing the code is a job for tomorrow!

  • I didn't use it because I had no prior experience with it so was unaware of its relevance.

    I think it's a natural feature of any software development, especially when product complex API (and e.g. not only general OS API) is used and these things happens :-)

    But it's not automatically bad and wasted time, because when the functionality is implemented in own way, its side-effect is much better understanding of the problem.

    When matrices operations are discussed, not always these shortcuts are the best way: when e.g. operations in dynamics (where only a minimum things should be done) has to be optimized, it's better to split them to "pre-calculated" operations and a few steps, that always have to be done in dynamics (like movement to a cursor location). Also hidden traps exist there: When only "in place" methods are used (when they exist), in fact it can lead to more operations internally, because e.g. rotate-in-place and scale-in-place can be realized as move to origin, rotate, move back, and again move to origin, scale, move back.

    I wasn't looking for a coded solution

    Well, in fact it was not my original intention too :-)

    But when I though about the workflow more, I always ended with assumption it's about only 4 (or 5, when scaling is applied) steps, so the code should be simple.

    One thing I have immediately noticed which I had not seen before is using a class as a member variable.

    That's a normal way when anything more complex is written. Unfortunately, (not only) VBA tutorials and examples show how to solve something, but not how to write the code right (in a context of code structure and its maintenance). The basic rule is simple (to be formulated), but often complex in reality (when implemented): One sub / function/ class has to do one thing only and has to have single responsibility. So, in my code, IPrimitiveCommadEvents class interacts with MicroStation (primarily collects input), whereas "the engine class" is responsible for all calculations.

    With regards,

      Jan

  • I have immediately noticed which I had not seen before is using a class as a member variable

    Here's another example, where a reference to a UserForm (a UserForm is a VBA class) is passed to a class that Implements ILocateCommandEvents.

    As Jan writes, classes are generally very useful.  Unfortunately VBA is not a good example of an object-oriented language, because its classes are broken.  For example, you can't inherit a VBA class, so you can't write a MySolidsHandler class and then inherit it to write a MySlabHandler and MyConeHandler.  That's one reason there are so many similar-sounding VBA methods with wacky names.

    The good news is that CONNECT provides a first-class .NET API.  With CONNECT you can write a VB.NET or C# class that behaves as most of us expect. 

     
    Regards, Jon Summers
    LA Solutions

  • The basic rule is simple (to be formulated), but often complex in reality (when implemented): One sub / function/ class has to do one thing only and has to have single responsibility. So, in my code, IPrimitiveCommadEvents class interacts with MicroStation (primarily collects input), whereas "the engine class" is responsible for all calculations.

    I too have 2No. Classes however after reviewing my own code I think I will separate those into 3.

    Here's another example, where a reference to a UserForm (a UserForm is a VBA class) is passed to a class that Implements ILocateCommandEvents.

    Now that you mention it I have seen using referencing a Userform like that previously though I guess I did not think it was considered a class. Jan using a Class Module I suppose is no different but I didn't make the connection which is why it immediately caught my eye.

    The good news is that CONNECT provides a first-class .NET API.  With CONNECT you can write a VB.NET or C# class that behaves as most of us expect. 

    I expect it will be quite some time (years if recent history is a valid indicator) until CONNECT is in state to be worth repurchasing a SELECT subscription. Until then, it won't be on my radar.

    So I have now had a chance to review Jan's example, specfically the TransformationEngine class as I am comfortable with all other aspects of the project. I went step by step through each procedure and inserted elm.Rewrite at the end of each for the purpose of being able to visualise each step (it would not be included in final code as nobody wants to see the element transforming all over).

    What I learned:

    1. Transform3dFromPoint3d - I learned that this creates a transform which performs a point to point translation however I would not have understood that from the Method's description in the heading of the VBA help. I find many of these to be quite ambigous if you are not specifically au fait with the terminology used.
    2. Matrix3dRotationFromPoint3dOriginXY - This really is the magic solution that I was looking for all along. I always knew the operation I was trying to perform was translating one XY plane to another but I simply didn't know how to achieve that. My initial thought was to use Plane3D with Transform3dFromWorldToPlane3d as I had the origin and the normal (which is why I calculated the Cross Product!) however I realised that I had no way of knowing the rotation of the Plane3D around the Cross Product/Plane Normal and also that a source element may not be initially aligned to a World Axis so I came to a dead end. Again, the heading in the VBA help for Matrix3dRotationFromPoint3dOriginXY still doesn't make much sense to me however closer reading of the description of its parameters however does, so its unfortunate I didn't notice this method before.
    3. Transform3dFromMatrix3d - I learned that this creates a transform which performs not only scaling with a scaling matrix but also rotation using a rotation matrix instead of a World Axis and calculated or user-supplied angle which Matrix3dFromAxisAndRotationAngle uses.

    The only other minor aspect I noticed is you used Point3dDistance whereas I have been using Point3dMagnitude essentially yielding the same result.

    I now have enough understanding how to proceed and should be able to complete the project without issue.

    Thanks again to both of you for you assistance.

Reply
  • The basic rule is simple (to be formulated), but often complex in reality (when implemented): One sub / function/ class has to do one thing only and has to have single responsibility. So, in my code, IPrimitiveCommadEvents class interacts with MicroStation (primarily collects input), whereas "the engine class" is responsible for all calculations.

    I too have 2No. Classes however after reviewing my own code I think I will separate those into 3.

    Here's another example, where a reference to a UserForm (a UserForm is a VBA class) is passed to a class that Implements ILocateCommandEvents.

    Now that you mention it I have seen using referencing a Userform like that previously though I guess I did not think it was considered a class. Jan using a Class Module I suppose is no different but I didn't make the connection which is why it immediately caught my eye.

    The good news is that CONNECT provides a first-class .NET API.  With CONNECT you can write a VB.NET or C# class that behaves as most of us expect. 

    I expect it will be quite some time (years if recent history is a valid indicator) until CONNECT is in state to be worth repurchasing a SELECT subscription. Until then, it won't be on my radar.

    So I have now had a chance to review Jan's example, specfically the TransformationEngine class as I am comfortable with all other aspects of the project. I went step by step through each procedure and inserted elm.Rewrite at the end of each for the purpose of being able to visualise each step (it would not be included in final code as nobody wants to see the element transforming all over).

    What I learned:

    1. Transform3dFromPoint3d - I learned that this creates a transform which performs a point to point translation however I would not have understood that from the Method's description in the heading of the VBA help. I find many of these to be quite ambigous if you are not specifically au fait with the terminology used.
    2. Matrix3dRotationFromPoint3dOriginXY - This really is the magic solution that I was looking for all along. I always knew the operation I was trying to perform was translating one XY plane to another but I simply didn't know how to achieve that. My initial thought was to use Plane3D with Transform3dFromWorldToPlane3d as I had the origin and the normal (which is why I calculated the Cross Product!) however I realised that I had no way of knowing the rotation of the Plane3D around the Cross Product/Plane Normal and also that a source element may not be initially aligned to a World Axis so I came to a dead end. Again, the heading in the VBA help for Matrix3dRotationFromPoint3dOriginXY still doesn't make much sense to me however closer reading of the description of its parameters however does, so its unfortunate I didn't notice this method before.
    3. Transform3dFromMatrix3d - I learned that this creates a transform which performs not only scaling with a scaling matrix but also rotation using a rotation matrix instead of a World Axis and calculated or user-supplied angle which Matrix3dFromAxisAndRotationAngle uses.

    The only other minor aspect I noticed is you used Point3dDistance whereas I have been using Point3dMagnitude essentially yielding the same result.

    I now have enough understanding how to proceed and should be able to complete the project without issue.

    Thanks again to both of you for you assistance.

Children
  • I expect it will be quite some time (years if recent history is a valid indicator) until CONNECT is in state to be worth repurchasing a SELECT subscription.

    It's a topic for another discussion, but it really depends on projects types, requirements and context. Some my customers migrated and have no reason to go back (typically because specific CE features or data/format support), some are still finding bugs and obstacles, or are happy with V8i simply because new CE features do not represent benefits.

    For me, as developer ... I do not want to go back to V8. And every time I have to maintain a code, I always realize how better CE API is (for many people, unfortunately, it requires to understand OOP concepts ;-)  And with great support of EC data (as I am more and more focused on data analysis, transforming and maximizing data value), CE is the only choice.

    nd inserted elm.Rewrite at the end of each for the purpose of being able to visualise each step

    Which is exactly what I did, and removed all of them at the end ;-)

    however I would not have understood that from the Method's description in the heading of the VBA help

    The source of the problem is that the description is typically written with an assumption a read knows matrix algebra, because with this knowledge it's clear: The last column in transformation matrix defines translation and when the rest is identity matrix, no other transformation is defined.

    From simple user's perspective: When Point3d is used as the input, nothing else than translation (Point3d is used there more like a vector) can be defined.

    My initial thought was to use Plane3D with Transform3dFromWorldToPlane3d

    I think such solution should work also, but probably leads to similar code size: It's probably able to define both translation and rotation, so it's shorter, but to define the plane requires some extra code.

    Matrix3dRotationFromPoint3dOriginXY

    I agree, because, again, it requires very good knowledge what particular rows and columns in matrices express.

    I learned that this creates a transform which performs not only scaling with a scaling matrix but also rotation using a rotation matrix

    I would prefer different formulation. Whereas rotation matrix is able to define rotation(s) only, transformation matrix can handle any transformation (translation, rotation, scaling, mirroring and skewing). Because rotation is used often, it's just the transformation of data structure to transformation format, where it can be combined with other transformations.

    The only other minor aspect I noticed is you used Point3dDistance whereas I have been using Point3dMagnitude essentially yielding the same result.

    Yes, when evaluated in terms of result value. But in math, there are different things: the distance is is between two points, whereas size (magnitude) is a feature of vector (in this function, represented by Point3d). So what to use is about what data is available ;-)

    Regards,

      Jan

  • So I've spent this morning tearing apart my original code and reoriganising my classes so that I now have 3:

    • The first allows to the user to select an element which is checked if its a permitted element type
    • The second deals with user input
    • The third deals with transformations

    Referring back to Wednesday's discussion, specifically

    There is no difference between 2D and 3D, and as Jon wrote, 2D is just a subset of 3D operations ... so all 2D operations can be expressed using the same 3D matrices (where some values / rows / cells) are 0.

    I realised that I hadn't yet tested the 2D mode of operation (where only 2 source and destination points are provided) with the transformations which work in the 3D mode. My aim is to try and have both 2D and 3D modes using the same code where possible. I also had to consider that I have in the past encountered a 2D DGN file that contained geometry that was not planar to the world XY plane (probably due to imported DWG survey data) so I wanted to ensure that in 2D mode, elements not planar to the world XY plane will be made so, after the transformation. Upon testing the initial transformation code, the results were amusing with geoemtry ending up in unexpected rotations but with a little thought, I was able to rectify to the problem with some additional code which is only called if working in 2D mode:

    ' ---------------------------------------------------------------------
    Private Sub FakeYAxisIn2dMode()
    Dim dAngle As Double
    dAngle = Pi / 2
        If m_b3dOperation = False Then
            m_pt3Origin = RotatePointAroundPoint2D(m_pt1Origin, m_pt2Origin, dAngle)
            Debug.Print "m_pt3Origin=" & CDec(m_pt3Origin.X) & "," & CDec(m_pt3Origin.Y) & "," & CDec(m_pt3Origin.Z)
            m_pt3Target = RotatePointAroundPoint2D(m_pt1Target, m_pt2Target, dAngle)
            m_pt1Target.Z = 0
            m_pt2Target.Z = 0
            m_pt3Target.Z = 0
            Debug.Print "m_pt3Target=" & CDec(m_pt3Target.X) & "," & CDec(m_pt3Target.Y) & "," & CDec(m_pt3Target.Z)
        End If
    End Sub
    ' ---------------------------------------------------------------------
    Private Function RotatePointAroundPoint2D(ByRef Origin As Point3d, ByRef pt1 As Point3d, ByVal dAngle As Double) As Point3d
    Dim pt2_x As Double
    Dim pt2_y As Double
    
    pt2_x = Cos(dAngle) * (pt1.X - Origin.X) - Sin(dAngle) * (pt1.Y - Origin.Y) + Origin.X
    pt2_y = Sin(dAngle) * (pt1.X - Origin.X) + Cos(dAngle) * (pt1.Y - Origin.Y) + Origin.Y
    RotatePointAroundPoint2D.X = pt2_x
    RotatePointAroundPoint2D.Y = pt2_y
    RotatePointAroundPoint2D.Z = Origin.Z
    Debug.Print "After=(" & CDec(RotatePointAroundPoint2D.X) & "," & CDec(RotatePointAroundPoint2D.Y) & "," & CDec(RotatePointAroundPoint2D.Z) & ")"
    End Function
    ' ---------------------------------------------------------------------

    Everything now appears to be working as intended:

  • Everything now appears to be working as intended

    Excellent:  More helpful illustrations!

    Five Gold Stars

     
    Regards, Jon Summers
    LA Solutions