pyRevit

Code
Library
Distribute Pipes Equally

This is a draft script to Equally Space Selected Pipes (if parallel).
I was asked by of of the students on how to fix this.
The script will
- Prompt User to Select Pipes
- Check if they are Parallel
- Sort Them based on their location
- Distribute pipes equally between First and Last one based on CenterLine distance.
Here is the Draft Tool in Action:
Here is the code:
# -*- coding: utf-8 -*-
__title__ = 'Distribute Pipes'
__author__ = 'Erik Frits'
#⬇️IMPORTS
# ==================================================
import math
from pyrevit import forms
from Autodesk.Revit.DB import *
from Autodesk.Revit.UI.Selection import ObjectType, ISelectionFilter
#📦VARIABLES
# ==================================================
doc = __revit__.ActiveUIDocument.Document # type: Document
uidoc = __revit__.ActiveUIDocument # type: UIDocument
selection = uidoc.Selection # type: Selection
from pyrevit import script
output = script.get_output()
#🥚 CLASSES
# ==================================================
# Class
class CustomFilter(ISelectionFilter):
def AllowElement(self, element):
return element.Category and element.Category.BuiltInCategory == BuiltInCategory.OST_PipeCurves
#🧬 FUNCTIONS
# Function to retrieve insulation and diameter of a pipe
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
#╔╦╗╔═╗╦╔╗╔
#║║║╠═╣║║║║
#╩ ╩╩ ╩╩╝╚╝
# ==================================================
#1️⃣ Get Pipes
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]
# Ensure Pipes
if len(pipes) < 2:
forms.alert("Error: Select at least two pipes.", title="Selection Error", exitscript=True)
#2️⃣ Ensure Pipes Are Parallel
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
# Get the direction of the first pipe as a reference
reference_direction = get_pipe_direction(pipes[0])
# Check if all pipes are parallel to the reference direction
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
# Check Parallels
if not all_parallel:
forms.alert('Selected pipes are not parallel.', exitscript=True)
#3️⃣ Order Pipes
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
# Use the direction vector perpendicular to the pipes for sorting
# For simplicity, find a vector perpendicular to the pipe direction
if abs(reference_direction.Z) < 1.0:
# Pipes are not vertical; use Z-axis as up vector
up_vector = XYZ.BasisZ
else:
# Pipes are vertical; use X-axis
up_vector = XYZ.BasisX
perpendicular_direction = reference_direction.CrossProduct(up_vector).Normalize()
# Get the projected positions of the pipes onto the perpendicular direction
pipe_positions = []
for pipe in pipes:
midpoint = get_pipe_midpoint(pipe)
# Project the midpoint onto a line along the perpendicular direction
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))
# Sort the pipes based on their position along the perpendicular direction
pipe_positions_sorted = sorted(pipe_positions, key=lambda x: x[1])
# Extract the sorted pipes
pipes_ordered = [item[0] for item in pipe_positions_sorted]
# Verify the order Manually
for idx, (pipe, position) in enumerate(pipe_positions_sorted):
# print("Pipe {}: Position along perpendicular = {}".format(idx, position))
print(output.linkify(pipe.Id))
#4️⃣ Space Pipes (Center Line)
# Get the positions along the perpendicular direction
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):
# Calculate the target position along the perpendicular direction
target_position = first_position + equal_spacing * idx
# Calculate the displacement along the perpendicular direction
displacement = target_position - positions[idx]
# Compute the translation vector
translation_vector = perpendicular_direction.Multiply(displacement)
# Move the pipe
ElementTransformUtils.MoveElement(doc, pipe.Id, translation_vector)
t.Commit()
print("Pipes have been spaced equally.")

⌨️ Happy Coding!
Erik Frits