Hello everyone,
I am trying to create a script that splits rectangular ducts when they exceed 1500 mm, but it isn’t working. I get the following error:
Warning: Could not access Id of selected element – 'Line' object has no attribute 'Id'.
-- coding: utf-8 --
“”"
Cuts selected duct elements in the active Revit model if their length exceeds 1500mm.
Handles both LocationCurve and direct Line geometries for duct paths.
Includes ultra-robust error handling for unexpected input types.
Requires:
- Revit 2023
- Dynamo 2.13 (or higher, compatible with Revit 2023’s Python 3)
Input:
- IN[0]: A list or single Revit element representing duct(s) to be checked and cut.
Output:
- A list of processed duct element IDs.
- A list of warning messages for ducts that couldn’t be processed or invalid selections.
Author: Your Name (or leave blank)
Date: 2025-04-29
Location: Mondercange, Esch-sur-Alzette, Luxembourg
import clr
# Import Revit API
clr.AddReference('RevitAPI')
from Autodesk.Revit.DB import Transaction, Line, XYZ, BuiltInCategory, ElementTransformUtils, CopyPasteOptions, TransactionStatus, LocationCurve
from Autodesk.Revit.DB.Mechanical import Duct
# Import Revit Services for Dynamo interaction
clr.AddReference('RevitServices')
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager
# Get the current Revit document
doc = DocumentManager.Instance.CurrentDBDocument
# Get the input element(s)
elements_to_process = UnwrapElement(IN[0]) if isinstance(IN[0], list) else [UnwrapElement(IN[0])] if IN[0] else []
# Define the threshold length in millimeters
max_length_mm = 1500.0
# Define potential parameter names for Length
length_param_names = ["Length", "Longueur"]
processed_duct_ids = []
warning_messages = []
# Start a new transaction
TransactionManager.Instance.EnsureInTransaction(doc)
transaction = None # Initialize transaction
try:
transaction = Transaction(doc, "Cut Long Ducts")
for item in elements_to_process:
if item is None:
warning_messages.append("Warning: Received a None object as input. Skipping.")
continue
if isinstance(item, Line):
warning_messages.append("Warning: Received a Line object as input (before Id check). Skipping.")
continue
try:
element_id = item.Id.IntegerValue
if isinstance(item, Duct):
duct = item
if duct and (duct.Category.BuiltInCategory == BuiltInCategory.OST_DuctCurves or duct.Category.BuiltInCategory == BuiltInCategory.OST_DuctFitting):
length_param = None
for name in length_param_names:
temp_param = duct.LookupParameter(name)
if temp_param:
length_param = temp_param
break # Found a valid length parameter
if length_param:
try:
length_value = length_param.AsDouble() # Length is stored in feet in Revit API
length_mm = length_value * 304.8 # Convert feet to millimeters
if length_mm > max_length_mm and duct.Category.BuiltInCategory == BuiltInCategory.OST_DuctCurves and hasattr(duct, 'MEPModel'):
location = duct.Location
curve = None
if isinstance(location, LocationCurve):
curve = location.Curve
elif isinstance(location, Line):
curve = location
if curve:
total_length = curve.Length if isinstance(curve, Line) else curve.Length
split_param = 0.5
split_point = curve.Evaluate(split_param, True) if isinstance(curve, LocationCurve) else XYZ(
(curve.GetEndPoint(0).X + curve.GetEndPoint(1).X) / 2.0,
(curve.GetEndPoint(0).Y + curve.GetEndPoint(1).Y) / 2.0,
(curve.GetEndPoint(0).Z + curve.GetEndPoint(1).Z) / 2.0
)
start_point = curve.GetEndPoint(0)
end_point = curve.GetEndPoint(1)
line1 = Line.CreateBound(start_point, split_point)
line2 = Line.CreateBound(split_point, end_point)
connectors = [c for c in duct.MEPModel.ConnectorManager.Connectors]
if len(connectors) == 2:
new_duct1_id = doc.Create.NewDuct(connectors[0], connectors[1])
new_duct1 = doc.GetElement(new_duct1_id)
new_duct1.Location.Curve = line1
new_duct2_id = doc.Create.NewDuct(connectors[0], connectors[1])
new_duct2 = doc.GetElement(new_duct2_id)
new_duct2.Location.Curve = line2
for param in duct.Parameters:
if not param.IsReadOnly:
try:
if new_duct1.LookupParameter(param.Definition.Name):
new_duct1.LookupParameter(param.Definition.Name).Set(param.AsValueString())
if new_duct2.LookupParameter(param.Definition.Name):
new_duct2.LookupParameter(param.Definition.Name).Set(param.AsValueString())
except Exception as e_param:
warning_messages.append(f"Warning: Could not copy parameter '{param.Definition.Name}' from duct ID {duct.Id} - {e_param}")
doc.Delete(duct.Id)
processed_duct_ids.append(new_duct1_id)
processed_duct_ids.append(new_duct2_id)
else:
warning_messages.append(f"Warning: Duct with ID {duct.Id} has {len(connectors)} connectors. Expected 2 for simple split.")
else:
warning_messages.append(f"Warning: Duct with ID {duct.Id} has an unsupported location type for cutting.")
elif length_mm > max_length_mm and duct.Category.BuiltInCategory == BuiltInCategory.OST_DuctFitting and hasattr(duct, 'LookupParameter'):
warning_messages.append(f"Warning: Duct fitting with ID {duct.Id} exceeds {max_length_mm}mm and cannot be directly cut.")
else:
processed_duct_ids.append(duct.Id) # Duct within limit or not a duct curve/fitting
except Exception as e:
warning_messages.append(f"Error processing duct with ID {element_id}: {e}")
else:
warning_messages.append(f"Warning: Selected element with ID {element_id} is not a valid duct or duct fitting.")
else:
warning_messages.append(f"Warning: Selected element with ID {element_id} is not a duct element and will be skipped.")
except Exception as main_loop_error:
if isinstance(item, Line):
# This should ideally prevent the 'Line' error, but we're still seeing it.
warning_messages.append(f"Warning: Could not access Id (again) - {main_loop_error}")
else:
warning_messages.append(f"Warning: Could not access Id of selected element - {main_loop_error}")
if transaction and transaction.GetStatus() == TransactionStatus.Started:
transaction.Commit()
except Exception as main_error:
if transaction and transaction.GetStatus() == TransactionStatus.Started:
transaction.RollBack()
warning_messages.append(f"An unexpected error occurred: {main_error}")
# Assign output
OUT = (processed_duct_ids, warning_messages)
