__title__ = 'Distribute Pipes'
__author__ = 'Erik Frits'
import math
from pyrevit import forms
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI.Selection import ObjectType, ISelectionFilter
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument
selection = uidoc.Selection
from pyrevit import script
output = script.get_output()
class CustomFilter(ISelectionFilter):
def AllowElement(self, element):
return element.Category and element.Category.BuiltInCategory == BuiltInCategory.OST_PipeCurves
def get_pipe_insulation_and_diameter(pipe):
insulation_param = pipe.LookupParameter("Insulation Thickness")
insulation_thickness = insulation_param.AsDouble() if insulation_param else 0.0
diameter_param = pipe.LookupParameter("Outside Diameter")
diameter = diameter_param.AsDouble() if diameter_param else 0.0
return insulation_thickness, diameter
with forms.WarningBar(title='Select Pipes'):
custom_filter = CustomFilter()
pipe_refs = selection.PickObjects(ObjectType.Element, custom_filter, "Select pipes by dragging a box.")
pipes = [doc.GetElement(ref) for ref in pipe_refs]
if len(pipes) < 2:
forms.alert("Error: Select at least two pipes.", title="Selection Error", exitscript=True)
def get_pipe_direction(pipe):
"""Returns the normalized direction vector of a pipe."""
location_curve = pipe.Location
if isinstance(location_curve, LocationCurve):
curve = location_curve.Curve
direction = curve.GetEndPoint(1) - curve.GetEndPoint(0)
return direction.Normalize()
return None
def are_vectors_parallel(v1, v2, tolerance=1e-5):
"""Checks if two vectors are parallel within a given tolerance."""
cross_prod = v1.CrossProduct(v2).GetLength()
return cross_prod < tolerance
reference_direction = get_pipe_direction(pipes[0])
all_parallel = True
for pipe in pipes[1:]:
direction = get_pipe_direction(pipe)
if not are_vectors_parallel(reference_direction, direction):
all_parallel = False
break
if not all_parallel:
forms.alert('Selected pipes are not parallel.', exitscript=True)
def get_pipe_midpoint(pipe):
"""Returns the midpoint of a pipe's LocationCurve."""
location_curve = pipe.Location
if isinstance(location_curve, LocationCurve):
curve = location_curve.Curve
start_point = curve.GetEndPoint(0)
end_point = curve.GetEndPoint(1)
midpoint = (start_point + end_point) / 2
return midpoint
return None
def project_point_onto_line(point, line_point, line_direction):
"""Projects a point onto a line defined by line_point and line_direction."""
vector = point - line_point
projection_length = vector.DotProduct(line_direction)
projected_point = line_point + line_direction.Multiply(projection_length)
return projected_point
if abs(reference_direction.Z) < 1.0:
up_vector = XYZ.BasisZ
else:
up_vector = XYZ.BasisX
perpendicular_direction = reference_direction.CrossProduct(up_vector).Normalize()
pipe_positions = []
for pipe in pipes:
midpoint = get_pipe_midpoint(pipe)
projected_point = project_point_onto_line(midpoint, XYZ(0, 0, 0), perpendicular_direction)
distance_along_perpendicular = projected_point.DotProduct(perpendicular_direction)
pipe_positions.append((pipe, distance_along_perpendicular))
pipe_positions_sorted = sorted(pipe_positions, key=lambda x: x[1])
pipes_ordered = [item[0] for item in pipe_positions_sorted]
for idx, (pipe, position) in enumerate(pipe_positions_sorted):
print(output.linkify(pipe.Id))
positions = [pos for _, pos in pipe_positions_sorted]
first_position = positions[0]
last_position = positions[-1]
total_distance = last_position - first_position
number_of_intervals = len(pipes_ordered) - 1
if number_of_intervals == 0:
print("Only one pipe selected. No spacing needed.")
else:
equal_spacing = total_distance / number_of_intervals
t = Transaction(doc, "Equal Spacing of Pipes")
t.Start()
for idx, (pipe, _) in enumerate(pipe_positions_sorted):
target_position = first_position + equal_spacing * idx
displacement = target_position - positions[idx]
translation_vector = perpendicular_direction.Multiply(displacement)
ElementTransformUtils.MoveElement(doc, pipe.Id, translation_vector)
t.Commit()
print("Pipes have been spaced equally.")