Detailed 2D Text and Other Element Geometry

Determining the extents of a text element has been a recurring and challenging task with several tricky solutions suggested in the past, e.g.:

The 2D custom exporter provides a basis for a much more powerful approach to address this, as already discussed once way back then in using a custom exporter for 2D:

Retrieve Dimension Text Height and Width

The latest question in this series asks how to determine the height and width of the dimension text:

Question: When dimension text overlaps, I want to move one of the dimensions to avoid the overlap. My idea is to calculate the rectangular border of the text through the position of the text and the width and height of the text, and then judge whether the rectangular borders intersect. So, how to calculate the width and height of dimension text?

Answer: That should be possible using the approaches described in these two other recent threads:

Look at these two recent threads here in the forum:

Determine Text Font Geometry

Haroon Haider describes his successful approach to access text font geometry in the Revit API discussion forum thread on converting text to geometry when performing a 2D view export:

Question: We have been able to get access to the lines, grids, annotations and other basic elements of a 2D view using the IExportContext2D interface. Is there a way to convert the annotation text from strings to geometry using Revit API similar to how you can do in AutoCAD? I'm looking to get something like this:

Text font geometry

Answer: I ended up getting what I need by processing a TextNode from the export context method OnText call. There is enough information in the text node class to be able to convert it to a GraphicsPath and pull out the geometry from there.

Many thanks to Haroon for sharing this!

Retrieve 2D Geometry of any Element

Richard RPThomas108 Thomas shares a generic solution to retrieve the 2D geometry of any element in the thread on view reference location:

Question: I am making a simple tool to add a leader annotation symbol to a View Reference, but have run into a bit of an issue. In RevitLookup, it doesn't appear that the location of a View Reference is defined anywhere in the API. Looking through the API docs as well, I can't find a class for View Reference either, which makes me think that its properties aren't accessible. Am I missing something? Obviously, a simple workaround is to just have the user click a start and end point, but ideally I'd like to have the start be defined by the View Reference location.

Answer: The Revit API supports exactly (and only) two data types for the Location property: LocationPoint and LocationCurve. Some elements have different kinds of location definitions that do not fit into either of these two. In that case, they are not accessible in the API, just as you surmised. Is the view reference location visible in any way in the end user interface? What does it look like in that context? Is there any reason why it might be considered something that does not fit into a point or a curve?

In general, classes that show up as Element in RevitLookup have no specific API functionality beyond the element base class.

As the location of the element it not exposed, nor is the geometry, you have two options:

I would do the latter as it gives you more control.

Note that this will happen regardless of what you set for RenderNodeAction in OnElementBegin2D. The best way to avoid calling OnText for text elements you are not interested in is to do as follows:

To export text, you also need to set Export2DIncludingAnnotationObjects to true.

I'm finding the best approach is to implement IExportContext2D via a general class and then inherit that. This avoids having to implement all the members each time and you can build in some filtering similar to above:

Imports Autodesk.Revit.DB

Public Class RT_ExportContext2d_Limited
  Implements IExportContext2D

  Private IntDoc As Document = Nothing
  Private FilterEIDs As List(Of ElementId) = Nothing
  Private IntCurrentEID As ElementId = ElementId.InvalidElementId

  Public Property DefaultAction As RenderNodeAction = RenderNodeAction.Proceed

  Public Sub New(D As Document, Optional TargetElementIds As List(Of ElementId) = Nothing)
    FilterEIDs = TargetElementIds
    IntDoc = D
  End Sub

  Public Overridable Function Start() As Boolean Implements IExportContext.Start
    Return True
  End Function
  Public Overridable Sub Finish() Implements IExportContext.Finish
  End Sub
  Public Overridable Function IsCanceled() As Boolean Implements IExportContext.IsCanceled
    Return False
  End Function

#Region "OnBegins"
  Public Overridable Function OnViewBegin(node As ViewNode) As RenderNodeAction Implements IExportContext.OnViewBegin
    Return DefaultAction
  End Function
  Public Function OnElementBegin2D(node As ElementNode) As RenderNodeAction Implements IExportContext2D.OnElementBegin2D
    IntCurrentEID = node.ElementId

    If FilterEIDs IsNot Nothing Then
      If FilterEIDs.Contains(node.ElementId) Then
        Return RenderNodeAction.Proceed
      Else
        Return RenderNodeAction.Skip
      End If
    Else
      Return RenderNodeAction.Proceed
    End If

    OnElementBegin2D_Overridable(node)
  End Function
  Public Overridable Sub OnElementBegin2D_Overridable(node As ElementNode)
  End Sub

  Public Function OnElementBegin(elementId As ElementId) As RenderNodeAction Implements IExportContext.OnElementBegin
    'Never called for 2D export
    Return DefaultAction
  End Function
  Public Function OnInstanceBegin(node As InstanceNode) As RenderNodeAction Implements IExportContext.OnInstanceBegin
    Return DefaultAction
  End Function
  Public Function OnFaceBegin(node As FaceNode) As RenderNodeAction Implements IExportContext.OnFaceBegin
    Return DefaultAction
  End Function
  Public Overridable Function OnFaceEdge2D(node As FaceEdgeNode) As RenderNodeAction Implements IExportContext2D.OnFaceEdge2D
    Return DefaultAction
  End Function

  Public Function OnLinkBegin(node As LinkNode) As RenderNodeAction Implements IExportContext.OnLinkBegin
    Return DefaultAction
  End Function

#End Region

#Region "OnFunctions_Returns"
  Public Overridable Function OnFaceSilhouette2D(node As FaceSilhouetteNode) As RenderNodeAction Implements IExportContext2D.OnFaceSilhouette2D
    Return DefaultAction
  End Function
  Public Overridable Function OnCurve(node As CurveNode) As RenderNodeAction Implements IExportContextBase.OnCurve
    Return DefaultAction
  End Function
  Public Overridable Function OnPolyline(node As PolylineNode) As RenderNodeAction Implements IExportContextBase.OnPolyline
    Return DefaultAction
  End Function
#End Region

#Region "OnEnds_NoReturns"
  Public Sub OnElementEnd2D(node As ElementNode) Implements IExportContext2D.OnElementEnd2D
    IntCurrentEID = Nothing
    OnElementEnd2D_Overridable(node)
  End Sub
  Public Overridable Sub OnElementEnd2D_Overridable(node As ElementNode)
  End Sub

  Public Overridable Sub OnViewEnd(elementId As ElementId) Implements IExportContext.OnViewEnd
  End Sub
  Public Sub OnElementEnd(elementId As ElementId) Implements IExportContext.OnElementEnd
  End Sub

  Public Overridable Sub OnInstanceEnd(node As InstanceNode) Implements IExportContext.OnInstanceEnd
  End Sub
  Public Overridable Sub OnLinkEnd(node As LinkNode) Implements IExportContext.OnLinkEnd
  End Sub
  Public Overridable Sub OnFaceEnd(node As FaceNode) Implements IExportContext.OnFaceEnd
  End Sub
#End Region

#Region "OnSubs_NoReturns"
  Public Overridable Sub OnLineSegment(segment As LineSegment) Implements IExportContextBase.OnLineSegment
  End Sub

  Public Overridable Sub OnPolylineSegments(segments As PolylineSegments) Implements IExportContextBase.OnPolylineSegments
  End Sub

  Public Sub OnText(node As TextNode) Implements IExportContextBase.OnText
    'For tags etc. some calls to this will be during on instance
    'e.g. fixed text within family
    'Some call will be outside OnInstance but before OnElementEnd2D of associated element
    'e.g. tag values (variable text).

    If IntCurrentEID = ElementId.InvalidElementId Then Exit Sub Else
    OnText_Overridable(node)
  End Sub

  Public Overridable Sub OnText_Overridable(node As TextNode)
  End Sub

  Public Overridable Sub OnRPC(node As RPCNode) Implements IExportContext.OnRPC
  End Sub
  Public Overridable Sub OnLight(node As LightNode) Implements IExportContext.OnLight
  End Sub
  Public Overridable Sub OnMaterial(node As MaterialNode) Implements IExportContext.OnMaterial
  End Sub
  Public Overridable Sub OnPolymesh(node As PolymeshTopology) Implements IExportContext.OnPolymesh
  End Sub
#End Region

End Class

A lot of the members are not called for the 2D scenario.

Here is a correction required to OnText, i.e., check the list of filtered contains the current id.

 Public Sub OnText(node As TextNode) Implements IExportContextBase.OnText
    If FilterEIDs IsNot Nothing Then
      If FilterEIDs.Contains(IntCurrentEID) = False Then
        Exit Sub
      End If
    End If

    OnText_Overridable(node)
End Sub

The best way to understand the order of the exporter method calls is to log them.

Response: Thanks so much! That's a clever implementation of IExportContext2d. Appreciate the help!

Many thanks to Richard for sharing this!