Automatically Fill Cable Tray with Cables

Hi everyone,

I have a Dynamo script that uses AutoCAD lines to create a cable tray path and currently places one cable path inside the tray.

Output of Current Script below: (Yes, the bend is a little messed up still trying to figure out how I’d fix this, probably a bigger elbow)

I would like to expand it so that the tray can be automatically filled with multiple cables without overlaps. Currently I’m using the offset command in Revit to duplicate the cable paths multiple times and filling the cable tray.

The intended logic is:

Tray Inside Width + Tray Inside Height

↓

Cable Diameter

↓

Calculate Horizontal Positions

↓

Fill First Row of Tray

↓

Calculate Vertical Positions

↓

Add Additional Rows

↓

Create Cable Paths

Ideally, the inside width/height and cable diameter would be retrieved from Revit, but manual inputs would also be fine.

Is this practical to do in Dynamo? Also, would you integrate this into the existing cable tray script or handle the cable-filling calculation as a separate script?

Any help with this would be greatly appreciated.

Thank You!

Hi @alyaan_h both should be possible…here is a fast example for conduits…maybee

Revit_xAFu0haX5W

Hi @sovitek,

Thanks for your response!

I also wanted to share what I’ve come up with so far. I haven’t been able to fully solve the bend issue because, in real life, the conduits/cables can be bent quite tightly within the cable tray. However, the elbow fittings available in Revit introduce a larger bend radius than what would actually occur, which makes it difficult to accurately represent the routing.

One thing I really like about your script is the ability to select the cable tray directly. My main goal with my script was to tightly pack cables within a tray so I could determine the maximum number that can fit while avoiding overlaps.

At the moment, I’m a little stuck what is want is for the user to be able to simply select a conduit type, and the script will automatically retrieve the conduit’s diameter. That diameter would then be used both for cable placement and for calculating the offset distance between adjacent conduits and rows. I haven’t been able to figure out the best way to implement that yet, so any help would be greatly appreciated.

Here is my script and how it works so far:

Fill Cable Tray with Cables Script.dyn (119.5 KB)

Hi @alyaan_h looks great so far, cheers…yay bend its about how fitting family is build…try share a small rvt with that, and can probably help…but no garanti there …haha

and PS if you mean cabletray fitting bend it is controlled by the cabletray itself in bend radius type parameter

Path Testing.dwg

@sovitek it wasn’t letting me share the Revit file as it was too big, but this is the AutoCAD path I’m using if u link this AutoCAD file to any Revit project it should work with the script. Don’t mind the vertical path, I’m biting off more than I can chew :laughing:

just share a small rvt with cabletray, cabletry fitting and conduit, conduit fitting families you use..

Keys to a successful process:

  • use cable tray curves (rather than the DWG geometry)
  • use conduits type without fittings
  • once the elbows are created, adjust their radii

Here is an example with a layer on the cable tray

Note : code with only 1 layer and the vertical offset has not been fixed here; the conduits are centered on the cable trays

PythonNet3 code
import clr
import sys
import System
from System.Collections.Generic import List

clr.AddReference("System.Numerics")
#
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS

#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB
#import specify namespace
from Autodesk.Revit.DB.Electrical import *

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)

#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

doc = DocumentManager.Instance.CurrentDBDocument


clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

import math
import numpy as np

def get_conduit_size(type_conduit_name, size_project):
    global unit_L
    input_size = UnitUtils.ConvertToInternalUnits(float(size_project), UnitTypeId.Millimeters)
    css = ConduitSizeSettings.GetConduitSizeSettings(doc)
    enumeratorA = css.GetEnumerator()
    while enumeratorA.MoveNext(): 
        type_conduit, allSize = enumeratorA.Current.Key, enumeratorA.Current.Value
        #print(type_conduit, allSize)
        enumeratorB = allSize.GetEnumerator()
        while enumeratorB.MoveNext():
            size_c = enumeratorB.Current
            #print(type_conduit, size_c)
            if abs(size_c.NominalDiameter - input_size ) < 0.01:
                return type_conduit, size_c.NominalDiameter
    return None, None
        
def create_elbows(lstconduit):
    global unit_L
    max_distance_cons = 4 # in feet
    all_connectors = [c for cond in lstconduit for c in cond.ConnectorManager.Connectors]
    out = []
    i = 0
    while all_connectors and i < 300: # avoid inifinite loop
        i += 1
        conA = all_connectors.pop(0)
        if len(all_connectors) > 0:
            all_connectors.sort(key = lambda c : c.Origin.DistanceTo(conA.Origin))
            conCandidate = all_connectors[0]
            if conCandidate.Origin.DistanceTo(conA.Origin) < max_distance_cons :
                conB = all_connectors.pop(0)
                # first compute the radius
                pta = conA.Origin.ToPoint()
                ptb = conB.Origin.ToPoint()
                vector = conA.CoordinateSystem.BasisZ.ToVector()
                arc_ds = DS.Arc.ByStartPointEndPointStartTangent(pta, ptb, vector)
                radius = arc_ds.Radius
                radius = UnitUtils.ConvertToInternalUnits(radius, unit_L)
                #
                is_horizontal = math.isclose(pta.Z, ptb.Z)
                # then create elbow fitting
                newfitting = doc.Create.NewElbowFitting(conA, conB)

                out.append([newfitting, radius, arc_ds, is_horizontal])
    return out

toList = lambda x : x if hasattr(x, "__iter__") and not isinstance(x, (str, System.String)) else [x]

unit_L = doc.GetUnits().GetFormatOptions(SpecTypeId.Length).GetUnitTypeId()
#Preparing input from dynamo to revit
lstcableTray  = toList(UnwrapElement(IN[0]))
conduit_type_name = IN[1]
conduit_size_input_mm = IN[2]
offset_arc_CT_fitting = 0.2

unit_L = doc.GetUnits().GetFormatOptions(SpecTypeId.Length).GetUnitTypeId()
lst_new_conduit = []

conduit_type = FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_Conduit).WhereElementIsElementType()\
            .FirstOrDefault(System.Func[System.Object, System.Boolean](lambda x: x.Name == conduit_type_name))

if conduit_type and lstcableTray:
    
    conduit_std_type_name  = conduit_type.get_Parameter(BuiltInParameter.CONDUIT_STANDARD_TYPE_PARAM).AsValueString()
    conduit_std_type, size_conduit = get_conduit_size(conduit_std_type_name, conduit_size_input_mm)

    if size_conduit:
        cable_diameter_mm = conduit_size_input_mm + 5 # in millimiter (5 : margin)
        width_ct = lstcableTray[0].Width # in feet
        width_ct_mm = width_ct * 304.8
        n_cables = int(width_ct_mm // cable_diameter_mm)
    
        # compute all offsets to fill cable tray (one layer)
        offsets_centered = np.linspace( -1 * (n_cables - 1) * cable_diameter_mm / 2,
                                        (n_cables - 1) * cable_diameter_mm / 2,
                                        n_cables  )   
        #
        ds_curves = [c.Location.Curve.ToProtoType() for c in lstcableTray]
        # create one polycurve to ensure same direction
        poly_curve = DS.PolyCurve.ByJoinedCurves(ds_curves, 1.0, True, 1.0) # value in meter, so project unit in meter
        ds_curves_fixed = poly_curve.Curves()
    
        TransactionManager.Instance.EnsureInTransaction(doc)
        for offset in offsets_centered:
            temp = []
            offset_unit_projet = UnitUtils.Convert(offset, UnitTypeId.Millimeters, unit_L)

            for ds_curve, cable_tray in zip(ds_curves_fixed, lstcableTray):
                ds_curve : DS.Curve
                cable_tray : CableTray
                offset_ds_curve = ds_curve.OffsetMany(offset_unit_projet, None).First()
                #
                line = offset_ds_curve.ToRevitType()
                newconduit = Conduit.Create( doc, conduit_type.Id, line.GetEndPoint(0), line.GetEndPoint(1), cable_tray.LevelId)
                newconduit.get_Parameter(BuiltInParameter.RBS_CONDUIT_DIAMETER_PARAM).Set(size_conduit)
                temp.append(newconduit)
            #
            doc.Regenerate()
            #
            lst_elbows = create_elbows(temp)
            # set the radius of elbow conduits
            for elbow, radius_to_set, arc_ds, is_horizontal in lst_elbows:
                if is_horizontal:
                    try:
                        elbow.get_Parameter(BuiltInParameter.RBS_CONDUIT_BENDRADIUS).Set(radius_to_set - offset_arc_CT_fitting )  
                    except Exception as ex:
                        print(ex)
                
            #
            lst_new_conduit.append(temp)

        TransactionManager.Instance.TransactionTaskDone()

OUT = lst_new_conduit

Hey @sovitek ,

I’m not sure why I’m unable to share the Revit file here. The file is less than 10MB but the site is not letting me upload. But for the families I’m using all Revit default stuff.

Hi @c.poupin ,

For some reason this code isn’t working for me. I’m not sure what I’m doing wrong but it’s not placing anything.

@alyaan_h share your rvt file (an sample)

@c.poupin @sovitek

Path Test 2.0.rvt (484 KB)

I was finally able to upload a Revit file here, and this is a brand-new document.

My goal is to use the AutoCAD path as a reference to automatically place cable trays in Revit, and then fill those cable trays with cables/conduits across both the width and height of the tray.

For the vertical rows, I’m planning to keep the script relatively simple. Instead of having one complex script automatically calculate and generate every row, I would like to have an offset input for each row. I can then duplicate the script for however many rows I need.

This approach will also allow me to independently change the conduit/cable type, size, spacing, and offset for each row, while keeping the overall Dynamo script much easier to manage and modify.

Your RVT file does not contain any cable trays. Start by completing your script to generate the cable trays, then use the cable trays to create the conduit runs.

I’ve already added the cable trays using my script. If you meant that the cable trays needed to be created in the model before running the conduit generation script, I had already done that as well. However, the script still does not work and produces the same issue.

Path Test 2.0.rvt (500 KB)
This file should have cable trays placed along a path. No fittings are loaded because if I do I’ll be unable to upload here

That’s the first step; you now need to generate the cable tray fitting (using doc.Create.NewElbowFitting(conA, conB)). Be careful—it seems there are some inaccuracies due to your DWG file.

Next, there will be an issue to resolve regarding the offsets on the vertical lines.

Hey @c.poupin ,

This is me running the script with the fittings. Still not sure what to do here. Really want to solve this, I know I’m close even with my script just having a hard time adding these final touches.

P.S this was done by manually placing the cable trays, so the CAD drawing shouldn’t be an issue

@alyaan_h

Try setting your project units to metres, or modify these numeric values on this line.

poly_curve = DS.PolyCurve.ByJoinedCurves(ds_curves, 1.0, True, 1.0)