Hello everyone, maybe someone can help. I am trying to write a script for laying electrical circuits on cable trays. But I can’t solve the problem with the diagonal of the chain segments, and for some reason, with the short lengths of the cable trays, it doesn’t build correctly.
When I try to script a circuit from SW1–>1, it builds like this
When I build a circuit from SW2–>4, it builds correctly.
But at the same time, if I add a small branch to the tray, I immediately get errors related to the diagonality of the chain segments. SW3–>5
Unfortunately, I can’t upload many screenshots, so I’m giving you a link to the file.
Revit 2022
Maybe someone has come across this and can help me, thank you.
# -*- coding: utf-8 -*-
from Autodesk.Revit.DB import (
FilteredElementCollector,
Transaction,
BuiltInCategory,
FamilyInstance,
LocationCurve,
XYZ,
SpotDimension,
Reference,
View,
Line,
)
from Autodesk.Revit.UI import TaskDialog
from pyrevit import revit, forms
import math
# Получение имени кабельной системы от пользователя через диалоговое окно
def get_tray_name_from_user():
tray_name = forms.ask_for_string(
title="Имя кабельной системы",
prompt="Введите имя кабельной системы:",
default=""
)
return tray_name
# Получение кабельных лотков по имени
def get_cable_trays_by_name(doc, tray_name):
trays = FilteredElementCollector(doc) \
.OfCategory(BuiltInCategory.OST_CableTray) \
.WhereElementIsNotElementType() \
.ToElements()
return [tray for tray in trays if
tray.LookupParameter("mS_Имя системы") and tray.LookupParameter("mS_Имя системы").AsString() == tray_name]
# Получение всех электрических цепей в проекте
def get_all_circuits(doc):
return FilteredElementCollector(doc) \
.OfCategory(BuiltInCategory.OST_ElectricalCircuit) \
.WhereElementIsNotElementType() \
.ToElements()
# Выбор электрических цепей через диспетчер инженерных систем
def select_circuits_from_list(doc):
all_circuits = get_all_circuits(doc)
circuit_names = [
"{0} (ID: {1})".format(circuit.Name, circuit.Id.IntegerValue)
for circuit in all_circuits
]
selected_names = forms.SelectFromList.show(
circuit_names,
title="Выберите электрические цепи",
multiselect=True
)
if not selected_names:
return []
# Найти соответствующие цепи по имени
selected_circuits = [
circuit for circuit in all_circuits
if "{0} (ID: {1})".format(circuit.Name, circuit.Id.IntegerValue) in selected_names
]
return selected_circuits
def interpolate_points(point1, point2):
intermediate1 = XYZ(point1.X, point2.Y, point1.Z)
intermediate2 = XYZ(point2.X, point2.Y, point1.Z)
return [intermediate1, intermediate2]
def find_nearest_end(tray_curve, equipment_point):
start = tray_curve.GetEndPoint(0)
end = tray_curve.GetEndPoint(1)
distance_to_start = equipment_point.DistanceTo(start)
distance_to_end = equipment_point.DistanceTo(end)
return start if distance_to_start < distance_to_end else end
# Получение оконечного оборудования
def get_load_equipment(circuit):
for elem in circuit.Elements:
if isinstance(elem, FamilyInstance):
return elem
return None
# Получение соединительных деталей кабельных лотков
def get_cable_tray_fittings(doc):
return FilteredElementCollector(doc) \
.OfCategory(BuiltInCategory.OST_CableTrayFitting) \
.WhereElementIsNotElementType() \
.ToElements()
# Прокладка цепи вдоль кабельного лотка с учетом соединительных деталей
def route_circuit_along_tray(circuit, tray, doc):
tray_location = tray.Location
if not isinstance(tray_location, LocationCurve):
forms.alert("Ошибка: Кабельный лоток не содержит геометрической кривой.")
return
tray_curve = tray_location.Curve
base_equipment = circuit.BaseEquipment
if not base_equipment:
forms.alert("Ошибка: Базовое оборудование цепи не найдено.")
return
# Используем местоположение оборудования напрямую (фиктивные соединители)
if hasattr(base_equipment, "Location") and base_equipment.Location is not None:
if hasattr(base_equipment.Location, "Point"):
start_point = base_equipment.Location.Point
else:
forms.alert("Ошибка: Location базового оборудования не содержит точки.")
return
else:
forms.alert("Ошибка: Не удалось получить Location оборудования.")
return
load_equipment = get_load_equipment(circuit)
if not load_equipment:
forms.alert("Ошибка: Оконечное оборудование цепи не найдено.")
return
# Используем местоположение оборудования напрямую (фиктивные соединители)
if hasattr(load_equipment, "Location") and load_equipment.Location is not None:
if hasattr(load_equipment.Location, "Point"):
end_point = load_equipment.Location.Point
else:
forms.alert("Ошибка: Location оконечного оборудования не содержит точки.")
return
else:
forms.alert("Ошибка: Не удалось получить Location оконечного оборудования.")
return
nearest_start = find_nearest_end(tray_curve, start_point)
nearest_end = find_nearest_end(tray_curve, end_point)
# Создаем список точек маршрута
path_points = [start_point]
# Добавляем промежуточные точки для корректного соединения
# Разбиваем маршрут на горизонтальные и вертикальные сегменты
intermediate_points = create_orthogonal_path(start_point, nearest_start)
path_points += intermediate_points
path_points.append(nearest_start)
# Добавляем точки соединительных деталей
fittings = get_cable_tray_fittings(doc)
for fitting in fittings:
if hasattr(fitting, "Location") and fitting.Location is not None:
if hasattr(fitting.Location, "Point"):
fitting_point = fitting.Location.Point
# Проверяем, что точка находится между началом и концом лотка
if (tray_curve.GetEndPoint(0).DistanceTo(fitting_point) < tray_curve.Length and
tray_curve.GetEndPoint(1).DistanceTo(fitting_point) < tray_curve.Length):
path_points.append(fitting_point)
# Добавляем конечные точки
path_points.append(nearest_end)
intermediate_points = create_orthogonal_path(nearest_end, end_point)
path_points += intermediate_points
path_points.append(end_point)
# Фильтруем точки, чтобы избежать дублирования
min_distance = 1e-3
filtered_points = [path_points[0]]
for point in path_points[1:]:
if point.DistanceTo(filtered_points[-1]) > min_distance:
filtered_points.append(point)
# Проверяем, что все сегменты строго горизонтальные или вертикальные
for i in range(1, len(filtered_points)):
prev_point = filtered_points[i - 1]
curr_point = filtered_points[i]
if not (math.isclose(prev_point.X, curr_point.X, abs_tol=1e-3) or
math.isclose(prev_point.Y, curr_point.Y, abs_tol=1e-3)):
# Выводим координаты точек, где обнаружен диагональный сегмент
print("Обнаружен диагональный сегмент между точками:")
print(f"Точка 1: X={prev_point.X}, Y={prev_point.Y}, Z={prev_point.Z}")
print(f"Точка 2: X={curr_point.X}, Y={curr_point.Y}, Z={curr_point.Z}")
forms.alert(f"Ошибка: Обнаружен диагональный сегмент между точками:\n"
f"Точка 1: X={prev_point.X}, Y={prev_point.Y}, Z={prev_point.Z}\n"
f"Точка 2: X={curr_point.X}, Y={curr_point.Y}, Z={curr_point.Z}")
return
try:
with Transaction(doc, "Маршрутизация цепи") as t:
t.Start()
circuit.SetCircuitPath(filtered_points)
t.Commit()
except Exception as ex:
forms.alert(f"Ошибка при установке пути цепи: {ex}")
def create_orthogonal_path(point1, point2):
"""
Создает промежуточные точки для маршрута, чтобы он состоял только из горизонтальных и вертикальных сегментов.
"""
intermediate_points = []
# Горизонтальный сегмент
if not math.isclose(point1.X, point2.X, abs_tol=1e-3):
intermediate1 = XYZ(point2.X, point1.Y, point1.Z)
intermediate_points.append(intermediate1)
# Вертикальный сегмент
if not math.isclose(point1.Y, point2.Y, abs_tol=1e-3):
intermediate2 = XYZ(point2.X, point2.Y, point1.Z)
intermediate_points.append(intermediate2)
return intermediate_points
# Запуск кода
if __name__ == "__main__":
tray_name = get_tray_name_from_user()
if tray_name:
doc = revit.doc
trays = get_cable_trays_by_name(doc, tray_name)
if not trays:
forms.alert(f"Кабельные лотки с именем '{tray_name}' не найдены.")
else:
selected_circuits = select_circuits_from_list(doc)
if not selected_circuits:
forms.alert("Не выбраны электрические цепи.")
else:
for circuit in selected_circuits:
for tray in trays:
route_circuit_along_tray(circuit, tray, doc)