I played around with this last night for a project I had all the room tags deleted and it would have been to much to do by hand.
it does like 90% ish of room tag recentering. I had some rooms that are skewed in the linked model that did not move, for example
Before Script
After Script
but it got most of the job done. If anyone decides to further refine this and gets it working better, please share.
for now, this is my result
# -*- coding: utf-8 -*-
# pyRevit | IronPython 2.7
# Recenter Room Tags in Active View
import clr, math
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
from pyrevit import script
uidoc = __revit__.ActiveUIDocument
doc = uidoc.Document
active_view = doc.ActiveView
output = script.get_output()
# ---------------- Helpers ----------------
def compute_2d_centroid(uv_points):
"""Shoelace formula centroid."""
n = len(uv_points)
if n < 3:
return None
area, cx, cy = 0.0, 0.0, 0.0
for i in range(n):
j = (i + 1) % n
xi, yi = uv_points[i].U, uv_points[i].V
xj, yj = uv_points[j].U, uv_points[j].V
cross = xi * yj - xj * yi
area += cross
cx += (xi + xj) * cross
cy += (yi + yj) * cross
area *= 0.5
if abs(area) < 1e-6:
return None
cx /= (6.0 * area)
cy /= (6.0 * area)
return UV(cx, cy)
def is_near_rectangle(uv_points, tol_ratio=0.95):
"""Check if polygon fills most of its bounding box (rectangle-ish)."""
xs, ys = [p.U for p in uv_points], [p.V for p in uv_points]
minx, maxx, miny, maxy = min(xs), max(xs), min(ys), max(ys)
bb_area = (maxx - minx) * (maxy - miny)
if bb_area < 1e-6:
return False
# Shoelace polygon area
poly_area = 0.0
for i in range(len(uv_points)):
j = (i+1) % len(uv_points)
poly_area += uv_points[i].U * uv_points[j].V - uv_points[j].U * uv_points[i].V
poly_area = abs(poly_area) / 2.0
return (poly_area / bb_area) >= tol_ratio
def get_room_center(room, transform):
"""Compute stable center point for tagging (ignores off-center refs)."""
try:
opts = SpatialElementBoundaryOptions()
boundaries = room.GetBoundarySegments(opts)
if not boundaries:
return None
uv_points, z_vals = [], []
for blist in boundaries:
for seg in blist:
c = seg.GetCurve()
p0, p1 = c.GetEndPoint(0), c.GetEndPoint(1)
uv_points.append(UV(p0.X, p0.Y))
z_vals.extend([p0.Z, p1.Z])
# Compute shoelace centroid
centroid_uv = compute_2d_centroid(uv_points)
if not centroid_uv:
return None
avg_z = sum(z_vals) / len(z_vals)
centroid = XYZ(centroid_uv.U, centroid_uv.V, avg_z)
centroid = transform.OfPoint(centroid)
# Compute bounding box center
bb = room.get_BoundingBox(None)
if bb:
bb_center = (bb.Min + bb.Max) / 2.0
bb_center = transform.OfPoint(bb_center)
# Rectangle-ish override
if is_near_rectangle(uv_points):
return bb_center
# Distance-based override
tol = max(0.05 * math.sqrt(room.Area), 0.5)
if centroid.DistanceTo(bb_center) > tol:
return bb_center
return centroid
except Exception as ex:
output.print_md("⚠️ Error computing center for Room {}: {}".format(room.Id, ex))
return None
def get_all_linked_room_centers():
results = []
for linkInst in FilteredElementCollector(doc).OfClass(RevitLinkInstance):
linkDoc = linkInst.GetLinkDocument()
if not linkDoc:
continue
transform = linkInst.GetTotalTransform()
rooms = FilteredElementCollector(linkDoc).OfCategory(
BuiltInCategory.OST_Rooms).WhereElementIsNotElementType()
for room in rooms:
if not isinstance(room, SpatialElement):
continue
center = get_room_center(room, transform)
if center:
results.append((room, center))
return results
def find_nearest_room_center(tag_pt, room_centers):
nearest = None
min_dist = 1e9
for (room, center) in room_centers:
d = tag_pt.DistanceTo(center)
if d < min_dist:
min_dist = d
nearest = center
return nearest, min_dist
# ---------------- Main ----------------
tags = FilteredElementCollector(doc, active_view.Id) \
.OfCategory(BuiltInCategory.OST_RoomTags) \
.WhereElementIsNotElementType()
room_centers = get_all_linked_room_centers()
output.print_md("### Found {} room tags".format(tags.GetElementCount()))
t = Transaction(doc, "Recenter Room Tags (Centroid + Rectangle Override)")
t.Start()
moved, skipped = 0, 0
for tag in tags:
try:
loc = tag.Location
if not isinstance(loc, LocationPoint):
continue
tag_pt = loc.Point
nearest, dist = find_nearest_room_center(tag_pt, room_centers)
if not nearest:
continue
delta = nearest - tag_pt
if dist > 0.001:
orig_pt = loc.Point
ElementTransformUtils.MoveElement(doc, tag.Id, delta)
if tag.HasLeader: # rollback if leader appears
back_delta = orig_pt - loc.Point
ElementTransformUtils.MoveElement(doc, tag.Id, back_delta)
tag.HasLeader = False
skipped += 1
else:
moved += 1
except Exception as ex:
output.print_md("⚠️ Error on tag {}: {}".format(tag.Id, ex))
continue
t.Commit()
output.print_md("### ✅ Room Tags Recentered: {}".format(moved))
output.print_md("### ❌ Room Tags Skipped: {}".format(skipped))
output.print_md("### ℹ️ Skipped tags were ones that would have landed outside their rooms and caused leaders")