Is 100% Collision-Free BOP Spot Elevation Tag Placement Possible in Revit? Looking for Solutions

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

I have a few thoughts from my experience. It isn’t clear whether you are doing it already, but finding the actual size of an annotation element can be tricky, because get_BoundingBox() will always include leaders, which you probably do not want. Use a temporary transaction to remove the leader, measure the bounding box, figure out what the offset is between when the leader is showing and when it is not, then roll the transaction back.

This also applies to figuring out what other annotation elements you might be hitting. You may be ahead to write a function that calculates the actual rectangle for each annotation element, and maybe integrate it with a general bounding box finder for all elements. Bounding boxes struggle with diagonal elements, so, for example, if you have a pipe on a 45, you would do better to get the centerline of the pipe, add its radius, and use that in your collision calculations.

This discussion might be useful if you do build a flattened list of bounding boxes: Polygons on 2D plane: Calculate the exact shift to resolve overlaps

i gave this a shot last year when i was making my own “Clash Detector for Tags” and it was a pain.

I am not sharing this to discourage but to share my struggles.

some of the issues i struggled with were

  • getting proper bounding boxes on tags
  • getting it to avoid/ignore the leader of tags
  • getting the proper solids of 3d elements then trying to flatten to use the plane parallel to the view to see if they intersected with the tag bounding boxes.

I put a couple weeks into this and gave up. Ideate is suppose to be coming out with a feature for this but TBD.

my 3 attempts failed

image

https://bimlogiq.com/product/smart-annotation

Do you mean something like this