I made a script that re-centers MEP spaces reference lines so it “tries” to be in the middle of the space. I ran into a roadblock when the spaces are not perfect rectangular/square spaces.
In the screenshot, after i run the script, it does center but you can see how if the “corridor” portion of the space where longer, the space reference would get placed outside the space region.
Here is the script. If anyone has any suggestion or improvements on this, please let me know.
# pyRevit metadata
__title__ = 'Re-center\nReference'
__author__ = 'Your Name'
__doc__ = 'This script aligns MEP space reference lines and tags with the geometric center of the spaces.'
# pyRevit script for Revit 2023
# Python 2.7
from Autodesk.Revit.DB import FilteredElementCollector, Transaction, XYZ, SpatialElement
from Autodesk.Revit.DB.Mechanical import Space
# Get the current document
doc = __revit__.ActiveUIDocument.Document
def get_space_center(space):
"""
Calculate the center of a space using its bounding box.
"""
bbox = space.get_BoundingBox(None)
if bbox:
center = (bbox.Min + bbox.Max) / 2
return center
return None
def recenter_space_reference_lines():
"""
Re-center reference lines for all spaces in the active view.
"""
# Collect all SpatialElement instances in the active view
spatial_elements = FilteredElementCollector(doc, doc.ActiveView.Id).OfClass(SpatialElement)
# Filter out only Space elements
spaces = [se for se in spatial_elements if isinstance(se, Space)]
if not spaces:
print("No spaces found in the active view.")
return
# Begin a transaction to modify the document
t = Transaction(doc, "Recenter MEP Space Reference Lines")
t.Start()
for space in spaces:
# Get the center of the space
center = get_space_center(space)
if not center:
print("Unable to determine center for space: {}".format(space.Id))
continue
# Check the reference line's current position
ref_point = space.Location.Point
if ref_point != center:
# Update the reference line's position
space.Location.Point = center
#print("Recentered space ID {}.".format(space.Id))
# Commit the transaction
t.Commit()
# Run the script
recenter_space_reference_lines()
I had a similar issue trying to centre my room tags few months back.
My solution was basically to use the room edges to identify the largest “sub-shape” and centre the tag to its midpoint.
I knew that all the files I wanted my script to work on were either square or L-shaped, so I only developed the script for those two scenarios, but you could imagine adjusting this logic to work with more complex shapes, too (up to a limit, I admit).
I wrote this when I was stilll very much a Python beginner, so it’s not the cleanest code in the world, but here’s what I ended up with:
# ----------------------------------------------------------------
# SET GLOBAL VARIABLES
# ----------------------------------------------------------------
doc = DocumentManager.Instance.CurrentDBDocument
filename = doc.Title
rooms = Fec(doc).OfCategory(Bic.OST_Rooms).WhereElementIsNotElementType().ToElements()
roomtags = Fec(doc).OfCategory(Bic.OST_RoomTags).WhereElementIsNotElementType().ToElements()
#Initialise Geometry Calculator
calculator = SpatialElementGeometryCalculator(doc)
# ----------------------------------------------------------------
# METHODS
# ----------------------------------------------------------------
def r2(num):
return round(num, 2)
# ----------------------------------------------------------------
# EXECUTE
# ----------------------------------------------------------------
bland_roomtags = [] #Rooms with four edges that can be easily processed via bounding box
complex_roomtags = [] #Rooms with more than six edges that need manual processing #ForTheFuture
#Process six-edge rooms
for roomtag in roomtags:
roomtag_id = roomtag.Id
bounding = roomtag.get_BoundingBox(doc.ActiveView)
if bounding:
size_x = bounding.Max.X-bounding.Min.X
current_x = r2(roomtag.Location.Point.X)
current_y = r2(roomtag.Location.Point.Y)
room = roomtag.Room
########## GET ROOM EDGES ##########
calc_geometry = calculator.CalculateSpatialElementGeometry(room)
get_solid = calc_geometry.GetGeometry()
for face in get_solid.Faces:
origin_z = face.Origin.Z
if r2(origin_z) == 0: #At this point, we have the faces of the bottom faces of each room
edge_array_array = face.EdgeLoops
edges = []
for edge_array in edge_array_array: #Unpack the .EdgeLoops return into a single, iterable list.
for edge in edge_array:
edges.append(edge)
########## COMBINE LITTLE LINES ##########
line_dictionary = {} #Frustratingly, elements like doors split the edges of faces into multiple lines. Here, we want to combine these little lines into one, long line, to simplify our shape.
for edge in edges:
tesselation = edge.Tessellate()
direction = edge.AsCurve().Direction.X
if r2(abs(direction)) == 1: #Horizontal
for point in tesselation:
x_point = r2(point.X)
y_point = r2(point.Y)
if y_point not in line_dictionary:
line_dictionary[y_point] = [x_point]
else:
line_dictionary[y_point].append(x_point)
if len(line_dictionary) == 2:
bland_roomtags.append(roomtag)
elif len(line_dictionary) != 3:
complex_roomtags.append(roomtag)
else:
########## GET POINTS OF L SHAPE ##########
x_points = []
y_points = []
actual_points = []
for y, x in line_dictionary.items():
y_points.append(y)
x_points.append(max(x))
x_points.append(min(x))
actual_points.append(XYZ(max(x), y, 0))
actual_points.append(XYZ(min(x), y, 0))
max_x = max(x_points)
min_x = min(x_points)
max_y = max(y_points)
min_y = min(y_points)
########## GET THE TWO POINTS ON ONE BOUNDARY BUT NOT THE OTHER ##########
horizontal_point = None
vertical_point = None
for point in actual_points:
x_point = r2(point.X)
y_point = r2(point.Y)
if (x_point == max_x or x_point == min_x) and (y_point != max_y and y_point != min_y):
vertical_point = point
if (y_point == max_y or y_point == min_y) and (x_point != max_x and x_point != min_x):
horizontal_point = point
#Compare which point is further from the mother:
if r2(vertical_point.X) == max_x:
x_distance = horizontal_point.X-min_x
x_mother = "RIGHT"
else:
x_distance = max_x-horizontal_point.X
x_mother = "LEFT"
if r2(horizontal_point.Y) == max_y:
y_distance = vertical_point.Y-min_y
y_mother = "UP"
else:
y_distance = max_y-vertical_point.Y
y_mother = "DOWN"
#Acquire target orientation and establish its bounds
if y_distance > x_distance: #TARGET: HORIZONTAL
if y_mother == "UP":
max_y = r2(vertical_point.Y)
elif y_mother == "DOWN":
min_y = r2(vertical_point.Y)
else: #TARGET: VERTICAL
if x_mother == "RIGHT":
max_x = r2(horizontal_point.X)
elif x_mother == "LEFT":
min_x = r2(horizontal_point.X)
#Establish move
target_x = min_x+(max_x-min_x)/2
target_y = min_y+(max_y-min_y)/2
move_x = target_x-current_x
move_y = target_y-current_y
move = XYZ(move_x, move_y, 0)
TransactionManager.Instance.EnsureInTransaction(doc)
ElementTransformUtils.MoveElement(doc, roomtag_id, move)
TransactionManager.Instance.TransactionTaskDone()
for roomtag in bland_roomtags:
#Process four-sided rooms as normal (via get_Bounding())