Tag piles as per coordinate X and Y value so it take X value in ascending order till Y is constant and move to new row (for a new higher value of Y) Y value and tag piles again for X(lower to high value) till the new constant Y value
import clr
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import *
clr.AddReference('RevitAPIUI')
from Autodesk.Revit.UI import *
clr.AddReference('System')
from System.Collections.Generic import List
clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)
clr.ImportExtensions(Revit.Elements)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
doc = DocumentManager.Instance.CurrentDBDocument
uidoc = DocumentManager.Instance.CurrentUIApplication.ActiveUIDocument
# Function to get the X, Y coordinates using direct access to Location property with unit conversion
def get_location_coordinates(element):
location = UnwrapElement(element).Location
if isinstance(location, LocationPoint):
point = location.Point
# Convert from feet to millimeters
x_mm = point.X * 304.8 # 1 foot = 304.8 millimeters
y_mm = point.Y * 304.8
return x_mm, y_mm
# Retrieve selected pile elements from the model
selected_elements = UnwrapElement(IN[0])
if not isinstance(selected_elements, list):
selected_elements = [selected_elements]
# Retrieve coordinates using direct access to Location property with unit conversion
coordinates = [get_location_coordinates(element) for element in selected_elements]
# Dictionary to store tag values based on columns
tag_values_by_column = {}
# Assign pile tags and update the "Mark" parameter
TransactionManager.Instance.EnsureInTransaction(doc)
# Counter for assigning tag values
tag_counter = 1
for x, y in coordinates:
# Check if the X coordinate is already in the dictionary
if x not in tag_values_by_column:
tag_values_by_column[x] = {}
# Check if the Y coordinate is already assigned a tag in the column
if y not in tag_values_by_column[x]:
tag_value = f'CP{tag_counter:03d}'
tag_values_by_column[x][y] = tag_value
tag_counter += 1
# Find the corresponding pile based on coordinates
pile = next(pile for pile, coord in zip(selected_elements, coordinates) if coord == (x, y))
# Assign tag value to the pile
pile.get_Parameter(BuiltInParameter.ALL_MODEL_MARK).Set(tag_values_by_column[x][y])
TransactionManager.Instance.TransactionTaskDone()
# Refresh the view to see the changes
uidoc.RefreshActiveView()
# Output the sorted piles and their coordinates
OUT = [(x, y) for pile, (x, y) in zip(selected_elements, coordinates)]
I’m finding your code somewhat convoluted, as I understand what you are wanting to do is
Select piles in the current document
Get the pile location x & y coordinates in mm (project units)
Sort the piles based on x, then y
Tag the piles in order with CP000, CP001,...
Some comments on your code
Remove code that is not required - there are a lot of imports that can be removed like 'System' and 'RevitNodes'
The get_location_coordinates function uses hard coded conversion - use the API UnitUtils to do this dynamically (see my code below)
Iterating through the coordinates and building a dictionary, then checking every pile in the zip function is using a lot of cycles as we are doing it every time we test a coordinate - these can be separate operations
Your OUT variable does not need the list comprehension as it is only selecting coordinates - just use the zip
Here’s another way to do this operation
import sys
import clr
clr.AddReference("RevitServices")
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
clr.AddReference("RevitAPI")
from Autodesk.Revit.DB import BuiltInParameter, SpecTypeId, UnitUtils
def to_proj_units(val):
units = doc.GetUnits().GetFormatOptions(SpecTypeId.Length).GetUnitTypeId()
return UnitUtils.ConvertFromInternalUnits(val, units)
def location(obj, tol=0):
loc = obj.Location.Point
return round(to_proj_units(loc.X), tol), round(to_proj_units(loc.Y), tol)
def sort(obj, tol=-3):
# Use rounding tolerance to deal with minor variances (-3 = meter)
# -Y, X sorting Top left -> Bottom Right
x, y = location(obj, tol)
return -y, x
doc = DocumentManager.Instance.CurrentDBDocument
piles = UnwrapElement(IN[0]) if isinstance(IN[0], list) else [UnwrapElement(IN[0])]
sorted_piles = sorted(piles, key=sort) # Sort list of piles based on location function
TransactionManager.Instance.EnsureInTransaction(doc)
for cp, pile in enumerate(sorted_piles, 1): # Use enumerate to count items
mark = pile.get_Parameter(BuiltInParameter.ALL_MODEL_MARK)
if not mark.IsReadOnly:
mark.Set(f"CP{cp:03d}")
TransactionManager.Instance.TransactionTaskDone()
OUT = [(p, location(p)) for p in sorted_piles]
thanks Mike, it has changes coordinate values to sort the piles
this had happened to me as well when i am trying to sort pile as per coordinates this keep changing values and assigning coordinates values to diff piles
def sort(obj, tol=-3):
# Use rounding tolerance to deal with minor variances (-3 = meter)
# Y, X sorting From bottom left to top right
x, y = location(obj, tol)
return y, x