Hello i have a problem with my script it doesnt work… perfect.
Can anyone help me?
Below is a brief summary of what the script is designed to do:
- Purpose:
The script automates the dimensioning process in Revit 2025 (from a plan view) by associating system elements (pipes, ducts, and cable trays) with selected load-bearing walls. - How It Works:
For each system element:- It obtains the center point from the element’s location curve (the axis).
- It projects this point onto the nearest wall face to determine a reference point on the wall.
- For rectangular ducts and cable trays, it subtracts half of the element’s width from the axis—in the direction from the wall to the element—to compute the exact wall-side outer edge. (For round pipes, the axis is used directly.)
- It then creates a short detail line at the wall reference point and another at the computed outer edge.
- Finally, it groups these detail lines by wall and sets up dimensions (either as individual or chain dimensions) based on the detail lines’ midpoints.
- Goal:
The aim is to dimension the distance from the wall directly to the outer edge of the duct (or cable tray) without any extra offset—resulting in an accurate measurement that reflects the true external boundary of the channel.
This approach ensures that, in plan view, ducts and cable trays are dimensioned based on their actual wall-side edge (calculated as the axis minus half the width), while pipes are dimensioned using their axis.
and This is my script for Pyrevit:
# -*- coding: utf-8 -*-
import clr
import math
from collections import defaultdict
from Autodesk.Revit.DB import (
FilteredElementCollector, BuiltInCategory, Transaction, Line, DetailCurve,
Dimension, XYZ, HostObjectUtils, ShellLayerType, ReferenceArray,
OverrideGraphicSettings, Color, Options
)
from Autodesk.Revit.UI import TaskDialog
doc = __revit__.ActiveUIDocument.Document
uidoc = __revit__.ActiveUIDocument
# Umrechnungsfaktor: Revit speichert intern in ft (1 ft = 304.8 mm)
FT_TO_MM = 304.8
MM_TO_FT = 1.0 / 304.8
def is_parallel_xy(vec1, vec2, max_angle_deg=2.0):
"""Prüft, ob zwei Vektoren in der XY-Ebene einen Winkel < max_angle_deg haben."""
v1 = XYZ(vec1.X, vec1.Y, 0)
v2 = XYZ(vec2.X, vec2.Y, 0)
if v1.GetLength() < 1e-9 or v2.GetLength() < 1e-9:
return False
dot = v1.DotProduct(v2) / (v1.GetLength()*v2.GetLength())
dot = max(-1.0, min(1.0, dot))
angle_deg = math.degrees(math.acos(dot))
return (angle_deg <= max_angle_deg or abs(angle_deg-180) <= max_angle_deg)
def get_nearest_wall_face(wall, point):
"""Ermittelt die Wandfläche (Innen oder Außen), die dem Punkt am nächsten liegt."""
faces = []
try:
ext_refs = HostObjectUtils.GetSideFaces(wall, ShellLayerType.Exterior)
for r in ext_refs:
face_obj = doc.GetElement(r).GetGeometryObjectFromReference(r)
if face_obj:
faces.append(face_obj)
except:
pass
try:
int_refs = HostObjectUtils.GetSideFaces(wall, ShellLayerType.Interior)
for r in int_refs:
face_obj = doc.GetElement(r).GetGeometryObjectFromReference(r)
if face_obj:
faces.append(face_obj)
except:
pass
nearest_face = None
min_dist = float('inf')
for f in faces:
proj = f.Project(point)
if proj:
d = (proj.XYZPoint - point).GetLength()
if d < min_dist:
min_dist = d
nearest_face = f
return (nearest_face, min_dist) if nearest_face else (None, None)
def get_wall_direction(wall):
"""Liefert den normalisierten Richtungsvektor der Wand (aus der Location.Curve)."""
return wall.Location.Curve.Direction.Normalize()
def create_detail_line(view, start_pt, end_pt):
"""Erzeugt eine DetailLine zwischen start_pt und end_pt; setzt die Linienfarbe auf hellgrau."""
t = Transaction(doc, "Create Detail Line")
t.Start()
try:
curve = Line.CreateBound(start_pt, end_pt)
detail_line = doc.Create.NewDetailCurve(view, curve)
except Exception as e:
TaskDialog.Show("Fehler", "DetailLine-Erstellung fehlgeschlagen:\n{}".format(e))
t.RollBack()
return None
t.Commit()
t2 = Transaction(doc, "Set DetailLine Override")
t2.Start()
try:
ogs = OverrideGraphicSettings()
ogs.SetProjectionLineColor(Color(230,230,230))
view.SetElementOverrides(detail_line.Id, ogs)
except Exception as e:
TaskDialog.Show("Fehler", "SetElementOverrides fehlgeschlagen:\n{}".format(e))
t2.RollBack()
t2.Commit()
return detail_line
def create_2d_dimension(view, line1, line2, results):
"""Erstellt eine 2D-Bemaßung zwischen den Mittelpunkten zweier DetailLines."""
ref_array = ReferenceArray()
try:
ref_array.Append(line1.GeometryCurve.Reference)
ref_array.Append(line2.GeometryCurve.Reference)
except Exception as e:
results["errors"].append("Referenzen nicht ermittelbar: {}".format(e))
return
mid1 = line1.GeometryCurve.Evaluate(0.5, True)
mid2 = line2.GeometryCurve.Evaluate(0.5, True)
if (mid2 - mid1).GetLength() < 1e-3:
results["errors"].append("Einzelbemaßung abgebrochen: Distanz zu klein.")
return
dim_line = Line.CreateBound(mid1, mid2)
t = Transaction(doc, "Create 2D Dimension")
t.Start()
try:
doc.Create.NewDimension(view, dim_line, ref_array)
results["count"] += 1
except Exception as e:
results["errors"].append("Einzelbemaßung fehlgeschlagen: {}".format(e))
t.Commit()
def create_chain_dimension(view, detail_lines, results):
"""Erzeugt eine Kettenbemaßung (von der ersten zur letzten DetailLine)."""
if len(detail_lines) < 2:
return
ref_array = ReferenceArray()
for dl in detail_lines:
try:
ref_array.Append(dl.GeometryCurve.Reference)
except Exception as e:
results["errors"].append("Chain-Referenzproblem: {}".format(e))
first_mid = detail_lines[0].GeometryCurve.Evaluate(0.5, True)
last_mid = detail_lines[-1].GeometryCurve.Evaluate(0.5, True)
if (last_mid - first_mid).GetLength() < 1e-3:
results["errors"].append("Kettenbemaßung abgebrochen: Dimension-Linie zu kurz.")
return
dim_line = Line.CreateBound(first_mid, last_mid)
t = Transaction(doc, "Create Chain Dimension")
t.Start()
try:
doc.Create.NewDimension(view, dim_line, ref_array)
results["count"] += 1
except Exception as e:
results["errors"].append("Chain-Dimension fehlgeschlagen: {}".format(e))
t.Commit()
def create_detail_lines_for_wall_and_elem(view, wall, elem):
"""
Erzeugt für eine Wand und ein Systemelement (Pipe, Duct, Cable Tray) jeweils eine DetailLine.
- Für Pipes wird der exakte Achsenmittelpunkt verwendet.
- Für Ducts und Cable Trays wird der halbe Width-Wert (in ft) ermittelt und vom Achsenmittelpunkt in Richtung der Wand subtrahiert.
Rückgabe: (wall_dl, elem_dl)
"""
# Ermittele den Mittelpunkt der Elementachse (in 3D) und projiziere auf XY (View-Ebene)
loc_curve = elem.Location.Curve
if not loc_curve:
return None, None
p_mid_3d = loc_curve.Evaluate(0.5, True)
view_z = view.Origin.Z
axis_center = XYZ(p_mid_3d.X, p_mid_3d.Y, view_z)
# Bestimme den nächstgelegenen Wandpunkt anhand der Elementachse
face, _ = get_nearest_wall_face(wall, p_mid_3d)
if not face:
return None, None
proj = face.Project(p_mid_3d)
if not proj:
return None, None
wall_pt = XYZ(proj.XYZPoint.X, proj.XYZPoint.Y, view_z)
# Berechne den Vektor von der Wand zum Achsenmittelpunkt
vec = axis_center - wall_pt
if vec.GetLength() < 1e-9:
unit_vec = XYZ(0,0,0)
else:
unit_vec = vec.Normalize()
cat_name = elem.Category.Name.lower()
if "pipe" in cat_name:
# Für Pipes: Verwende den Achsenmittelpunkt
final_pt = axis_center
elif "duct" in cat_name or "kanal" in cat_name or "cable" in cat_name:
# Für Ducts/Kabeltrassen: Subtrahiere half width in Richtung Wand
width_param = elem.LookupParameter("Width")
if width_param and width_param.AsDouble():
half_width = width_param.AsDouble() / 2.0 # in ft (intern)
final_pt = axis_center - unit_vec.Multiply(half_width)
else:
final_pt = axis_center
else:
final_pt = axis_center
# Erzeuge Wandreferenz-DetailLine: an der projizierten Wand (wall_pt)
wall_dl = create_detail_line(view, wall_pt, wall_pt + get_wall_direction(wall).Multiply(0.1))
# Erzeuge Element-DetailLine: an final_pt (die errechnete Außenkante)
elem_dl = create_detail_line(view, final_pt, final_pt + get_wall_direction(wall).Multiply(0.1))
return wall_dl, elem_dl
def main():
TaskDialog.Show("Info", "Starte Skript: Automatische Bemaßung – Halbe Width von Ducts abziehen (Draufsicht)")
view = doc.ActiveView
view_z = view.Origin.Z
selected_ids = uidoc.Selection.GetElementIds()
if not selected_ids:
TaskDialog.Show("Hinweis", "Bitte tragende Wände auswählen.")
return
# Wandselektion: Nur tragende Wände
selected_walls = []
for el_id in selected_ids:
wall = doc.GetElement(el_id)
if wall and wall.Category:
if ("wände" in wall.Category.Name.lower() or "walls" in wall.Category.Name.lower()):
if str(wall.StructuralUsage).lower() == "bearing":
selected_walls.append(wall)
if not selected_walls:
TaskDialog.Show("Hinweis", "Keine tragende Wand gefunden.")
return
# Systemelemente: Pipes, Ducts und Cable Trays im aktiven View
pipes = list(FilteredElementCollector(doc, view.Id)
.OfCategory(BuiltInCategory.OST_PipeCurves)
.WhereElementIsNotElementType().ToElements())
ducts = list(FilteredElementCollector(doc, view.Id)
.OfCategory(BuiltInCategory.OST_DuctCurves)
.WhereElementIsNotElementType().ToElements())
cable_trays = list(FilteredElementCollector(doc, view.Id)
.OfCategory(BuiltInCategory.OST_CableTray)
.WhereElementIsNotElementType().ToElements())
system_elements = pipes + ducts + cable_trays
if not system_elements:
TaskDialog.Show("Hinweis", "Keine Rohre, Kanäle oder Kabeltrassen im View gefunden.")
return
results = {"count": 0, "errors": []}
chain_map = defaultdict(list)
for elem in system_elements:
if not hasattr(elem.Location, "Curve"):
continue
loc_curve = elem.Location.Curve
if not loc_curve:
continue
p_mid_3d = loc_curve.Evaluate(0.5, True)
cat_name = elem.Category.Name.lower()
tol = 2.0
if "duct" in cat_name or "kanal" in cat_name or "cable" in cat_name:
tol = 5.0
best_wall = None
min_dist = float('inf')
for wall in selected_walls:
if not is_parallel_xy(wall.Location.Curve.Direction, loc_curve.Direction, max_angle_deg=tol):
continue
face, dist = get_nearest_wall_face(wall, p_mid_3d)
if face and dist < min_dist:
min_dist = dist
best_wall = wall
if not best_wall:
continue
wall_dl, elem_dl = create_detail_lines_for_wall_and_elem(view, best_wall, elem)
if not wall_dl or not elem_dl:
continue
# Gruppiere nach Wand (verwende den projizierten Wandpunkt als Schlüssel)
face, _ = get_nearest_wall_face(best_wall, p_mid_3d)
proj = face.Project(p_mid_3d)
if not proj:
continue
wall_pt = XYZ(proj.XYZPoint.X, proj.XYZPoint.Y, view_z)
key = (best_wall.Id, round(wall_pt.X, 2), round(wall_pt.Y, 2))
chain_map[key].append((elem_dl, wall_dl))
for key, tup_list in chain_map.items():
if len(tup_list) < 1:
continue
if len(tup_list) == 1:
elem_dl, wall_dl = tup_list[0]
create_2d_dimension(view, elem_dl, wall_dl, results)
else:
wall_elem = doc.GetElement(key[0])
wall_dir = get_wall_direction(wall_elem)
base_pt = tup_list[0][0].GeometryCurve.Evaluate(0.5, True)
tup_list.sort(key=lambda tup: (tup[0].GeometryCurve.Evaluate(0.5, True) - base_pt).DotProduct(wall_dir))
elem_dl_list = [tup[0] for tup in tup_list]
create_chain_dimension(view, elem_dl_list, results)
msg = "Es wurden {} Dimensionen/Kettenbemaßungen erstellt.".format(results["count"])
if results["errors"]:
msg += "\n\nFehler:\n" + "\n".join(results["errors"])
TaskDialog.Show("Ergebnis", msg)
main()