Planar Face Transform

Here is another contribution by Joe Offord of

Enclos
,
who already shared valuable insights on accessing

curtain wall geometry
,

speeding up the interactive selection process
,

mirroring in a new family and changing the active view
.

Above all, he implements all his stuff in VB, so this is also something for you VB aficionados.

This time, Joe looks at a command that redraws a planar face’s edges in a drafting view.
The issue is somewhat related to the discussion of

polygon transformations
.

However, instead of using rotations and translations, which can be difficult to determine in 3D, Joe wrote a utility function that re-maps the global coordinate system to the planar face’s coordinate system using vectors and origins.

The command prompts the user to select a planar face on an elements, creates a new drafting view, retrieves the edges of the selected face, and transforms them from the 3D space to the drafting view.

To test this, I selected the following slightly lopsided wall in 3D:

Wall in 3D

The command generated a new drafting view showing the wall profile edges like this:

Transformed wall profile in drafting view

Here is the Execute method implementation for the mainline of the command:


  Public Function Execute( _
    ByVal commandData As ExternalCommandData, _
    ByRef message As String, _
    ByVal elements As ElementSet) As Result Implements IExternalCommand.Execute
 
    Dim uiapp As UIApplication = commandData.Application
    Dim app As Application = uiapp.Application
    Dim uidoc As UIDocument = uiapp.ActiveUIDocument
    Dim doc As Document = uidoc.Document
    Dim sel As Selection = uidoc.Selection
 
    Try
      Dim ref As Reference = sel.PickObject( _
        ObjectType.Face, "Select a face")
 
      Dim elem As Element = doc.GetElement(ref)
 
      Dim gObj As GeometryObject _
        = elem.GetGeometryObjectFromReference(ref)
 
      Dim face As PlanarFace = TryCast(gObj, PlanarFace)
 
      If face Is Nothing Then
        MsgBox("Not a planar face")
      End If
 
      Dim v As ViewDrafting = Nothing
 
      Dim tr As New Transaction(doc, "Draw Face")
      tr.Start()
 
      Try
        v = doc.Create.NewViewDrafting 'create a new view
        v.Scale = 48 '1/4" = 1'-0"
 
        'this transform re-orients the global 
        'coordinate system to the face's coordinate system
        Dim trans As Transform _
          = Util.PlanarFaceTransform(face)
 
        For Each eArr As EdgeArray In face.EdgeLoops
          For Each e As Edge In eArr
            Dim c As Curve = e.AsCurveFollowingFace(face)
            c = c.Transformed(trans) 'orient the curve on the XY plane
            doc.Create.NewDetailCurve(v, c) 'draw the curve
          Next
        Next
 
        tr.Commit()
      Catch ex As Exception
        tr.RollBack()
        MsgBox("Error: " + ex.Message)
      End Try
 
      If tr.GetStatus = TransactionStatus.Committed Then
        uidoc.ActiveView = v
      End If
 
    Catch ex As Exception
      MsgBox("Error: " + ex.Message)
    End Try
 
    Return Result.Succeeded
 
  End Function

The transformation from the planar face coordinate system is created by the following two utility methods:


  ''' <summary>
  ''' Return a transform that changes a x,y,z 
  ''' coordinate system to a new x',y',z' system
  ''' </summary>
  Public Shared Function TransformByVectors( _
    ByVal oldX As XYZ, _
    ByVal oldY As XYZ, _
    ByVal oldZ As XYZ, _
    ByVal oldOrigin As XYZ, _
    ByVal newX As XYZ, _
    ByVal newY As XYZ, _
    ByVal newZ As XYZ, _
    ByVal newOrigin As XYZ) As Transform
 
    ' [new vector] = [transform]*[old vector]
    ' [3x1] = [3x4] * [4x1]
    '
    ' [v'x]   [ i*i'  j*i'  k*i'  translationX' ]   [vx]
    ' [v'y] = [ i*j'  j*j'  k*j'  translationY' ] * [vy]
    ' [v'z]   [ i*k'  j*k'  k*k'  translationZ' ]   [vz]
    '                                               [1 ]
    Dim t As Transform = Transform.Identity
 
    Dim xx As Double = oldX.DotProduct(newX)
    Dim xy As Double = oldX.DotProduct(newY)
    Dim xz As Double = oldX.DotProduct(newZ)
 
    Dim yx As Double = oldY.DotProduct(newX)
    Dim yy As Double = oldY.DotProduct(newY)
    Dim yz As Double = oldY.DotProduct(newZ)
 
    Dim zx As Double = oldZ.DotProduct(newX)
    Dim zy As Double = oldZ.DotProduct(newY)
    Dim zz As Double = oldZ.DotProduct(newZ)
 
    t.BasisX = New XYZ(xx, xy, xz)
    t.BasisY = New XYZ(yx, yy, yz)
    t.BasisZ = New XYZ(zx, zy, zz)
 
    ' The movement of the origin point 
    ' in the old coordinate system
 
    Dim translation As XYZ = newOrigin - oldOrigin
 
    ' Convert the translation into coordinates 
    ' in the new coordinate system
 
    Dim translationNewX As Double _
      = xx * translation.X _
        + yx * translation.Y _
        + zx * translation.Z
 
    Dim translationNewY As Double _
      = xy * translation.X _
        + yy * translation.Y _
        + zy * translation.Z
 
    Dim translationNewZ As Double _
      = xz * translation.X _
        + yz * translation.Y _
        + zz * translation.Z
 
    t.Origin = New XYZ( _
      -translationNewX, _
      -translationNewY, _
      -translationNewZ)
 
    Return t
 
  End Function
 
  Public Shared Function PlanarFaceTransform( _
    ByVal face As PlanarFace) As Transform
 
    Return Util.TransformByVectors( _
      XYZ.BasisX, _
      XYZ.BasisY, _
      XYZ.BasisZ, _
      XYZ.Zero, _
      face.Vector(0), _
      face.Vector(1), _
      face.Normal, _
      face.Origin)
  End Function

Here is
PlanarFaceTransform.zip including
the complete source code and Visual Studio solution of this command.

Many thanks to Joe for sharing this!


Comments

2 responses to “Planar Face Transform”

  1. Hi Jeremy,
    I’m looking to code a routine to generate a partition/curtain wall that is not vertical. My approach was to generate in a vertical plane derived from an edge, then rotate it into it’s orientation about this linear edge. But it seems walls can only be rotated in plan as it doesn’t move when I call ElementTransformUtils.RotateElement with a non vertical line. Can you please confirm? Also, if I wish to detach the top of the wall from a level, can you please demonstrate what you would set this parameter to? BuiltInParameter.WALL_TOP_IS_ATTACHED
    If there are any other API methods to generate non vertical walls, would be greatly appreciated to find out about them.
    Cheers, Jon
    Thanks in advance,
    Jon

  2. Dear Jon,
    Nope, I cannot confirm the rotation issue off-hand – it would require some research – so you would have to submit an ADN DevHelp Online case for that.
    Regarding your second question:
    If you have a parameter taking an ElementId value and wish to set it to undefined or not connected, you can simply use ElementId.InvalidElementId:
    http://thebuildingcoder.typepad.com/blog/2011/08/set-underlay-display-property-to-none.html
    Cheers, Jeremy.

Leave a Reply

Discover more from Autodesk Developer Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading