ExtentElemand duplicating legend components
- Obtaining generic model square face references for dimensioning
- Preparing family with reference planes for dimensioning
- Creating a line perpendicular to another
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:
- Rename method
- Pass a generic list of element ids instead of a list of elements to the
- Open and activate the destination view before placing new group.
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
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
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) # 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 #Select the third edge (the one opposite the first) dimedge2 = edgeloops #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
edgeref2 variables both contain
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.
Alexander later added another, simpler solution to the conversation:
If you use Revit 2018 or later, you can prepare your family for easier dimensioning by adding specific reference planes in the family definition, e.g., xLeft, xRight, yTop, yBottom, and then access them on the family instance in the project environment like this:
def CreateDimension(instance, refNames, direction): references = ReferenceArray() for x in refNames: references.Append(instance.GetReferenceByName(x)) origin = instance.Location.Point transform = instance.GetTotalTransform() transform.Origin = XYZ.Zero dimensionDirection = transform.OfPoint(direction) dimensionLine = Line.CreateUnbound(origin, dimensionDirection) doc.Create.NewDimension(doc.ActiveView, dimensionLine, references) famInst = selection tx = Transaction(doc, "create dimensions") tx.Start() CreateDimension(famInst, ["xLeft", "xRight"], XYZ.BasisX) CreateDimension(famInst, ["yBottom", "yTop"], XYZ.BasisY) tx.Commit()
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
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.
normal.CrossProduct(direction) defines your second line direction, or maybe you need
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!