Hi All, I'm trying to do penetrations for piping, ductwork and cable trays

I got the piping to work in both Revit 2024 & 2026 but not for ductwork, Any suggestions please? This is the error I’m getting “AttributeError : module ‘clr’ has no attribute ‘Reference’ [’ File “”, line 65, in \n’]”

Hi, welcome to the forums.

Can you post the script that you’re currently working with? There’s a lot of guesswork that can get eliminated if we could simply just read it.

And add the fire rating into it, could be helpfull

Hi Robert, I can not upload any documents or scripts as I am a new member. Here is the Python script import clr

import clr
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *

doc = DocumentManager.Instance.CurrentDBDocument

# Inputs are lists of elements from the canvas nodes

ducts = UnwrapElement(IN[0]) if isinstance(IN[0], list) else [UnwrapElement(IN[0])]
walls = UnwrapElement(IN[1]) if isinstance(IN[1], list) else [UnwrapElement(IN[1])]
family_type = UnwrapElement(IN[2])

placed_elements = [ ]

TransactionManager.Instance.EnsureInTransaction(doc)

if not family_type.IsActive:
    family_type.Activate()
    doc.Regenerate()

def extract_solids(geom_element):
    solids = [ ]
    if geom_element is None:
        return solids
    for geom_obj in geom_element:
        if isinstance(geom_obj, Solid) and geom_obj.Volume > 0:
            solids.append(geom_obj)
        elif isinstance(geom_obj, GeometryInstance):
            solids.extend(extract_solids(geom_obj.GetInstanceGeometry()))
    return solids

for duct in ducts:
    if not duct or not hasattr(duct, 'Location') or not duct.Location:
        continue
    duct_line = duct.Location.Curve

    # Safely extract width and height from the MEPCurve class directly.
    try:
        duct_width = duct.Width
        duct_height = duct.Height
    except AttributeError:
        # Fallback if it is a round duct or lacks the standard properties
        try:
            duct_width = duct.Diameter
            duct_height = duct.Diameter
        except AttributeError:
            continue

    for wall in walls:
        if not wall:
            continue
        opt = Options()
        opt.ComputeReferences = True
        opt.DetailLevel = ViewDetailLevel.Fine
        
        wall_solids = extract_solids(wall.get_Geometry(opt))
        
        for wall_solid in wall_solids:
            results = clr.Reference[IntersectionResultArray]()
            intersect = wall_solid.IntersectWithCurve(duct_line, results)
            
            if intersect == SetComparisonResult.Overlap and results.Value.Count > 0:
                inter_point = results.Value[0].XYZPoint
                
                closest_face = None
                min_dist = float('inf')
                
                for face in wall_solid.Faces:
                    proj = face.Project(inter_point)
                    if proj:
                        dist = proj.Distance
                        if dist < min_dist:
                            min_dist = dist
                            closest_face = face
                
                if closest_face and closest_face.Reference:
                    ref = closest_face.Reference
                    proj_result = closest_face.Project(inter_point)
                    face_normal = closest_face.ComputeNormal(proj_result.UVPoint)
                    
                    duct_dir = duct_line.Direction
                    if abs(duct_dir.DotProduct(face_normal)) > 0.999:
                        if abs(face_normal.Z) < 0.999:
                            placement_dir = XYZ.BasisZ.CrossProduct(face_normal).Normalize()
                        else:
                            placement_dir = XYZ.BasisX.CrossProduct(face_normal).Normalize()
                    else:
                        placement_dir = duct_dir.Subtract(face_normal.Multiply(duct_dir.DotProduct(face_normal))).Normalize()
                    
                    # Place the face-based family instance
                    new_instance = doc.Create.NewFamilyInstance(ref, inter_point, placement_dir, family_type)
                    
                    # Extract wall thickness
                    wall_type = doc.GetElement(wall.GetTypeId())
                    wall_thickness = wall_type.Width
                    
                    # Set dimensions on the family
                    param_w = new_instance.LookupParameter("Void Width")
                    param_h = new_instance.LookupParameter("Void Height")
                    param_l = new_instance.LookupParameter("Sleeve Length")
                    
                    if param_w: param_w.Set(duct_width)
                    if param_h: param_h.Set(duct_height)
                    if param_l: param_l.Set(wall_thickness)
                    
                    placed_elements.append(new_instance)

TransactionManager.Instance.TransactionTaskDone()

OUT = placed_elements

I created a void form for the piping and a separate one for ducting. The piping one works 100%.

Manually made you a member, so you can post now. One of the fun aspects of my admin super powers!

Thank you so much. Will upload first thing in the morning.

Hi

Dynamo script anf family attached

(attachments)

Duct Penetrations.rfa (548 KB)
PR Eng Rectangular_Penetrations.dyn (14.3 KB)

You need to update the code for PythonNet3 (your current syntax is that of IronPython); the “out” and “ref” parameters appear as normal arguments in PythonNet.

read this article

Jacob Smallville :grinning_face_with_smiling_eyes: