Recenter Space Reference Lines

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()

Yep. Harder than it first appears…
This may help.
The Center

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())