Alexander Ignatovich, @CADBIMDeveloper, aka Александр Игнатович, answered several interesting questions in the Revit API discussion forum and elsewhere:
ExtentElem
and duplicate legend components remasteredIn 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:
Ungroup
to UngroupMembers
.NewGroup
method.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!
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.
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:
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!