Python - Move elements to match wall location of the nearest wall

Hi, I’m using a python script to take elements (in this case furniture), and have them move to match the location of the nearest wall. The furniture only needs to move along the x axis since the walls are only going to be moving a few inches along the x axis. I have the two furniture elements as IN[0], the walls as IN[1], and a distance parameter as IN[2] so the elements are looking for walls within that distance. I copied the python script down below and i’m not sure where i’ve gone wrong but i keep getting the error “Warning: IronPythonEvaluator.EvaluateIronPythonScript operation failed.
invalid syntax”. Any help is greatly appreciated!!

import clr
clr.AddReference('RevitServices')
clr.AddReference('RevitAPI')
clr.AddReference('RevitNodes')

from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

# Inputs from Dynamo
elements = UnwrapElement(IN[0])  # First input - elements to move (e.g., furniture)
walls = UnwrapElement(IN[1])     # Second input - walls
distance_threshold = IN[2]        # Third input - distance threshold from a number slider
doc = DocumentManager.Instance.CurrentDBDocument

def get_element_location(element):
    """Get the location point of a given element (handles furniture elements)."""
    loc = element.Location
    if isinstance(loc, LocationPoint):
        return loc.Point
    elif isinstance(loc, LocationCurve):
        curve = loc.Curve
        return curve.Evaluate(0.5, True)  # Midpoint of the curve
    return None

def get_element_orientation(element):
    """Get the orientation of an element based on its facing direction."""
    facing_orientation = None
    if hasattr(element, 'FacingOrientation'):
        facing_orientation = element.FacingOrientation
    return facing_orientation

def get_wall_interior_face(wall):
    """Get the interior face of a wall."""
    options = Options()
    geometry_element = wall.get_Geometry(options)
    
    for geom_obj in geometry_element:
        solid = geom_obj if isinstance(geom_obj, Solid) else None
        if solid:
            for face in solid.Faces:
                # Assume the interior face is the one whose normal points inward
                normal = face.ComputeNormal(UV(0.5, 0.5))  # Get the normal at the midpoint of the face
                if normal.Z == 0:  # Avoid horizontal faces (top or bottom)
                    return face, normal
    return None, None

def distance_between_points(point1, point2):
    """Calculate the distance between two XYZ points."""
    return point1.DistanceTo(point2)

def move_element_to_wall(element, move_vector):
    """Move an element by a given vector."""
    TransactionManager.Instance.EnsureInTransaction(doc)
    element.Location.Move(move_vector)
    TransactionManager.Instance.TransactionTaskDone()

moved_elements = []

# Loop through each element
for element in elements:
    element_loc = get_element_location(element)
    element_orientation = get_element_orientation(element)

    if element_loc is None or element_orientation is None:
        print(f"Skipping element {element.Id}: No valid location or orientation")
        continue  # Skip if no valid location or orientation is found

    closest_wall = None
    closest_distance = float("inf")
    best_move_vector = None

    # Loop through each wall to find the closest wall with matching orientation
    for wall in walls:
        face, wall_normal = get_wall_interior_face(wall)
        if face is None or wall_normal is None:
            print(f"Skipping wall {wall.Id}: No valid face or normal")
            continue  # Skip if no valid wall face or normal is found

        # Check if the element's orientation is aligned with the wall's face normal
        if element_orientation.IsAlmostEqualTo(wall_normal, 0.1):  # Adjust tolerance as needed
            wall_face_center = face.Evaluate(UV(0.5, 0.5))  # Get the center of the wall face
            dist = distance_between_points(element_loc, wall_face_center)

            # Debugging outputs
            print(f"Element: {element.Id}, Wall: {wall.Id}, Distance: {dist}, Threshold: {distance_threshold}")

            # Check if the distance is within the specified threshold
            if dist < closest_distance and dist <= distance_threshold:
                closest_distance = dist
                # Only move along the X-axis to match the wall face
                best_move_vector = XYZ(wall_face_center.X - element_loc.X, 0, 0)  # Adjust Y and Z to 0
                closest_wall = wall

    # If we found a suitable wall, move the element
    if closest_wall and best_move_vector:
        move_element_to_wall(element, best_move_vector)
        moved_elements.append(element)
        print(f"Moved element {element.Id} to wall {closest_wall.Id}")

# Output the moved elements
OUT = moved_elements ```

The full error message tells you what the issue is: just some extra characters and a line break. Looks like that line was copied from the forum with the formatting by accident.

that was a typo i made when formatting the code my apologies! below is the correct code

import clr
clr.AddReference('RevitServices')
clr.AddReference('RevitAPI')
clr.AddReference('RevitNodes')

from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

# Inputs from Dynamo
elements = UnwrapElement(IN[0])  # First input - elements to move (e.g., furniture)
walls = UnwrapElement(IN[1])     # Second input - walls
distance_threshold = IN[2]        # Third input - distance threshold from a number slider
doc = DocumentManager.Instance.CurrentDBDocument

def get_element_location(element):
    """Get the location point of a given element (handles furniture elements)."""
    loc = element.Location
    if isinstance(loc, LocationPoint):
        return loc.Point
    elif isinstance(loc, LocationCurve):
        curve = loc.Curve
        return curve.Evaluate(0.5, True)  # Midpoint of the curve
    return None

def get_element_orientation(element):
    """Get the orientation of an element based on its facing direction."""
    facing_orientation = None
    if hasattr(element, 'FacingOrientation'):
        facing_orientation = element.FacingOrientation
    return facing_orientation

def get_wall_interior_face(wall):
    """Get the interior face of a wall."""
    options = Options()
    geometry_element = wall.get_Geometry(options)
    
    for geom_obj in geometry_element:
        solid = geom_obj if isinstance(geom_obj, Solid) else None
        if solid:
            for face in solid.Faces:
                # Assume the interior face is the one whose normal points inward
                normal = face.ComputeNormal(UV(0.5, 0.5))  # Get the normal at the midpoint of the face
                if normal.Z == 0:  # Avoid horizontal faces (top or bottom)
                    return face, normal
    return None, None

def distance_between_points(point1, point2):
    """Calculate the distance between two XYZ points."""
    return point1.DistanceTo(point2)

def move_element_to_wall(element, move_vector):
    """Move an element by a given vector."""
    TransactionManager.Instance.EnsureInTransaction(doc)
    element.Location.Move(move_vector)
    TransactionManager.Instance.TransactionTaskDone()

moved_elements = []

# Loop through each element
for element in elements:
    element_loc = get_element_location(element)
    element_orientation = get_element_orientation(element)

    if element_loc is None or element_orientation is None:
        print(f"Skipping element {element.Id}: No valid location or orientation")
        continue  # Skip if no valid location or orientation is found

    closest_wall = None
    closest_distance = float("inf")
    best_move_vector = None

    # Loop through each wall to find the closest wall with matching orientation
    for wall in walls:
        face, wall_normal = get_wall_interior_face(wall)
        if face is None or wall_normal is None:
            print(f"Skipping wall {wall.Id}: No valid face or normal")
            continue  # Skip if no valid wall face or normal is found

        # Check if the element's orientation is aligned with the wall's face normal
        if element_orientation.IsAlmostEqualTo(wall_normal, 0.1):  # Adjust tolerance as needed
            wall_face_center = face.Evaluate(UV(0.5, 0.5))  # Get the center of the wall face
            dist = distance_between_points(element_loc, wall_face_center)

            # Debugging outputs
            print(f"Element: {element.Id}, Wall: {wall.Id}, Distance: {dist}, Threshold: {distance_threshold}")

            # Check if the distance is within the specified threshold
            if dist < closest_distance and dist <= distance_threshold:
                closest_distance = dist
                # Only move along the X-axis to match the wall face
                best_move_vector = XYZ(wall_face_center.X - element_loc.X, 0, 0)  # Adjust Y and Z to 0
                closest_wall = wall

    # If we found a suitable wall, move the element
    if closest_wall and best_move_vector:
        move_element_to_wall(element, best_move_vector)
        moved_elements.append(element)
        print(f"Moved element {element.Id} to wall {closest_wall.Id}")

# Output the moved elements
OUT = moved_elements

Can you post the full error? It still seems like there’s some info missing. Does the message mention any particular line(s)?

so this is the error message i get " Warning: IronPythonEvaluator.EvaluateIronPythonScript operation failed. invalid syntax"
theres no information about which lines :confused:

Did you copy code from anywhere or write it all from scratch? It works fine for me in CPython3 but not IronPython2, so I’m guessing you have some unsupported syntax for IronPython.

i was using chat gpt to set it up and edit! i didnt think of changing the version but when i just made the switch and ran, it worked! thank you!!

FYI
You have f-strings in the code

print(f"Skipping element {element.Id}: No valid location or orientation")

These are only available in Python 3 and not IronPython 2

You could change

print(f"Element: {element.Id}, Wall: {wall.Id}, Distance: {dist}, Threshold: {distance_threshold}")

to

print("Element: {}, Wall: {}, Distance: {}, Threshold: {}".format(element.Id, wall.Id, dist, distance_threshold)

Also note that the print statement will write to the Dynamo console - I like to collect these strings in a list and output via the OUT variable

1 Like