ExtentElem and Square Face Dimensioning References

Alexander Ignatovich, @CADBIMDeveloper, aka Александр Игнатович, answered several interesting questions in the Revit API discussion forum and elsewhere:

ExtentElem and Duplicate Legend Components Remastered

In Alexander's own words:

I recently developed a bunch of automation tools for legend views.

I faced and solved a tricky thing I want to share.

I had to copy legend components from view A to view B. I looked at ElementTransformUtils.CopyElements with partial success. However, instead of copying all legend components between the views, Revit created a new view for them.

My code at that moment looked like this:

  var collector = new FilteredElementCollector( 
    doc, legendView.Id );

  var elementsIds = collector
    .WhereElementIsNotElementType()
    .ToElementIds();

  ElementTransformUtils.CopyElements( legendView, 
    elementsIds, destLegendView, Transform.Identity, 
    new CopyPasteOptions() );

I searched for a solution and found the old blog post on duplicating a legend component.

That solution puts elements into a group, places a group instance and then ungroups it. This method still remains working with little modification, such as:

Once I had that working, I was pretty sure that it included unnecessary overhead, so I continued my research.

Later I realized that the list of element ids included an ExtentElem element.

This needs to be removed from elementsIds to make this code work as expected.

As always, when using a filtered element collector, the question arises on how to identify it.

In this case, all other elements have a valid category, and this one does not, so we can use:

  var elementsIds = collector
    .WhereElementIsNotElementType()
    .Where( x => x.Category != null ) // I don't want to use name, but I've found that all other use elements in legend view has category
    .Select( x => x.Id )
    .ToList();

So, this code remains simple and clear. The ExtentElem is a common problem and its id should not be passed to the CopyElements method.

Since I don't see this type of element mentioned by The Building Coder, I thought it worthwhile to point out.

Very many thanks to Alexander for this deep research and valuable insight!

Obtaining Generic Model Square Face References for Dimensioning

Next, Alexander implemented an add-in to help answer the Revit API discussion forum forum thread on obtaining references from edges in Python to put together a routine that will automatically create dimensions on a square face of a generic model. i.e. the user selects the face and dimensions appear:

Question: I've got some of the code working, obtaining the edges from a user selected 3D face, this returns the 4 edges (which are Edge class objects), it should simply be a case of obtaining the edge's reference that can be used to create the dimensions.

The Edge class has a Property which should return a reference to the edge - but when I run the code, it returns Null - and I can't see why.

This is the code:

 # Dynamo
import clr
clr.AddReference('RevitAPI')
clr.AddReference('RevitAPIUI')
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI import *

clr.AddReference("RevitServices")
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument
uiapp = DocumentManager.Instance.CurrentUIApplication
app = uiapp.Application
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument

#The inputs to this node will be stored as a list in the IN variables.
dataEnteringNode = IN
selobject       = UnwrapElement(IN[0])  # Object to select

#Get user to pick a face
selob = uidoc.Selection.PickObject(Selection.ObjectType.PointOnElement, "Pick something now")

#Get Id of element thats picked
selobid = selob.ElementId

#Get element thats picked
getob = doc.GetElement(selobid)

#Get face thats picked
getface = getob.GetGeometryObjectFromReference(selob)

#Get edges of face (returns a list the first object is the list of edges)
edgeloops = getface.EdgeLoops

#Select the first edge
dimedge1 = edgeloops[0][0]

#Select the third edge (the one opposite the first)
dimedge2 = edgeloops[0][2]

#Obtain a reference of the first edge
edgeref1 = dimedge1.Reference

#Obtain a reference of the thord edge
edgeref2 = dimedge2.Reference

#Assign your output to the OUT variable.
OUT = [selob, selobid, getob, getface, edgeloops, dimedge1, dimedge2, edgeref1, edgeref2]

If you execute it, you'll see that edgeref1 and edgeref2 variables both contain Null.

Any idea why?

Answer: I found the solution for your problem.

The main idea is to retrieve the element geometry with the option set to ComputeReference = True and then find the appropriate face by reference.

Sorry, I don't know Python too much, so I created an add-in in C# for you. You may get it from the PutRevitDimensionsToSquareFaces add-in GitHub repository.

It includes a lot of tricks with Revit references to make this work as expected with families.

Initially, I only tested it only with floors; now it works with family instances too.

Many thanks to Alexander for this work!

Jeremy adds: I added a readme and license to the code for him, because:

I myself use the MIT License for my samples, "a lax, permissive non-copyleft free software license. For substantial programs, it is better to use the Apache 2.0 license since it blocks patent treachery".

My samples are not substantial.

Creating a Line Perpendicular to Another

Finally, a trivial question to round this off, though very useful for the geometrically challenged, from the Revit API discussion forum forum thread on how to create a line based on an angle:

Question: I have a line element in Revit.

I want to add a second line perpendicular to it.

The second line start point can be any point on the line which is 90 degrees from the first line axis.

How can I create the second line based on this angle?

Here is an example of the line I need:

Perpendicular line

I have been playing around with the methods CreateTransformed, CreateRotationAtPoint and CrossProduct, but I still don't get the result I want.

Answer: It is simple. Just determine a) the line direction and b) the line sketch plane normal.

Then, normal.CrossProduct(direction) defines your second line direction, or maybe you need -1*normal.CrossProduct(direction).

Take any point on first line, for example, pt1 = fistLine.Evaluate(0.5, true), set pt2 = pt1 + length * direction, create the second line using Line.CreateBound(pt1, pt2), then create a model curve on the first line's sketch plane, based on this second line.

Thanks again to Alexander for this succinct explanation!