Hi everyone,
I’m currently developing a custom pyRevit tool using Python to automate the placement of BOP (Bottom of Pipe) spot elevation tags in Revit plan views. However, I’m facing some roadblocks in achieving accurate, collision-free placement of these tags. I’ve tried multiple methods and optimization strategies but still fall short of 100% reliable results.
I’m using pyRevit as the platform and Python to write all logic, working directly with the Revit API.
My core requirement is:
- When I run the script in a plan view that contains pipes, pipe tags, text notes, dimensions, and possibly other spot elevation tags.
- The tool should automatically place BOP spot elevation tags on all pipes.
- No tag should overlap with any existing element (pipe tags, dimensions, text notes, other BOP tags, etc.).
- The tags should auto-align neatly, appearing clean and readable, ready for production without manual adjustments.
- The result must be 100% collision-free.
I’m fine with performance trade-offs as long as it avoids crashes and gives fully clean output. I’m open to any better logic or API combination to make this work.
I’ve implemented and tested multiple approaches, including:
Feedback Loop Collision Resolver
Runs a feedback loop that checks each tag against all elements and moves the tag iteratively until no collision is found.Tried different movement directions and offsets.
Problem: High resource usage, very slow, and eventually crashes on large views.
2D Spatial Indexing (Quadtree)
Built a custom quadtree using .NET collections to optimize 2D spatial checks.
Tested grid-based and spiral placement patterns for finding tag positions.
Problem: Better performance than loop method, but still unable to avoid 100% of overlaps in complex views.
Key APIs I Used
Movement APIs:
spot_elevation.LeaderEndPosition = new_leader_end
spot_elevation.LeaderShoulderPosition = new_bend
ElementTransformUtils.MoveElement(doc, spot_elevation.Id, translation)
spot_elevation.Location.Move(translation)
Element Collection APIs:
FilteredElementCollector(doc, doc.ActiveView.Id)
.OfCategory(BuiltInCategory.OST_SpotElevations)
.OfClass(SpotDimension)
.OfCategory(BuiltInCategory.OST_PipeCurves)
Geometry & Position APIs:
spot_elevation.get_BoundingBox(view)
pipe.get_BoundingBox(view)
spot_elevation.Origin, .LeaderEndPosition, .LeaderShoulderPosition
Others:
Transactions: doc.IsModifiable, Transaction.GetStatus()
Exceptions: InvalidOperationException, ArgumentException
Spatial filters (attempted): BoundingBoxIntersectsFilter, ElementIntersectsBoundingBoxFilter
Regeneration: doc.Regenerate()
Collision optimization: custom quadtree with spatial bounding boxes
Despite all these efforts, I’m still not able to achieve 100% collision-free BOP tag placement. I’m starting to explore external concepts like CGAL, force-based layouts, or even integrating D3fc-style grid solvers, but these require deeper research.Meanwhile, I’d really appreciate it if anyone could Suggest better placement logic.
Thank You
Thenu Areraa
