Dynamo Python — Distribute Lines Along Curve — Digitteck
Dynamo Python — Distribute Lines Along Curve
dotnet·6 December 2018·3 min read

Dynamo Python — Distribute Lines Along Curve

Distributing lines along a polycurve is useful in road design — for example, to visualise curvature variations by drawing perpendicular lines of proportional length at regular intervals. The script accepts a curve (or list of curves), a list of parameter values (0–1), and one or more target lengths. The lengths are interpolated across the parameter range so each line's length grows or shrinks smoothly from start to end.

Utility Class

A small helper to detect whether a value is iterable (including .NET System.Array objects from Dynamo) and to flatten nested lists:

python
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import re

# Utility class — checks if a unit is iterable and flattens nested lists
class utils:
    ul = []
    def __init__(self):
        self.ul = []

    def isIterable(self, unit):
        t1 = t2 = False
        if isinstance(unit, (tuple, set, list, dict)):
            t1 = True
        if hasattr(unit, "__iter__"):
            iterstr = str(unit.__iter__())
            if re.search("System.Array", iterstr):
                t2 = True
        return t1 or t2

    def UnwrapList(self, lst):
        self.ul = []
        if self.isIterable(lst):
            self.UnwrapL(lst)
            return self.ul
        else:
            return lst

    def UnwrapL(self, unit):
        if self.isIterable(unit):
            for item in unit:
                self.UnwrapL(item)
        else:
            self.ul.append(unit)

ut = utils()

Core Functions

getPerpLine builds each perpendicular line using the curve tangent. composeLNF distributes lengths — given e.g. 3 target lengths and 100 parameters, it interpolates a smooth length value for every parameter:

python
# Returns a vector perpendicular to the curve tangent at the given parameter
def getPerpVector(curve, parameter):
    TP = curve.TangentAtParameter(parameter)
    VT = Vector.Rotate(TP, Vector.ZAxis(), 90)
    return VT

# Creates a perpendicular line at a point on the curve with the given length
def getPerpLine(curve, parameter, length):
    perpVector = getPerpVector(curve, parameter)
    pointAtParam = curve.PointAtParameter(parameter)
    L = Line.ByStartPointDirectionLength(pointAtParam, perpVector, length)
    return L

# Interpolates line lengths across the parameter range.
# Arc_Length: list of target lengths; Arc_Parameters: list of t values (0..1).
# Returns one length per parameter, distributed proportionally.
def composeLNF(Arc_Length, Arc_Parameters):
    ALU = Arc_Length
    APU = Arc_Parameters
    N = []
    for index1 in range(len(ALU) - 1):
        N.append([])
        LLeft  = ALU[index1]
        LRight = ALU[index1 + 1]
        for index2 in range(len(APU) - 1):
            steper = float(index2) / (len(APU) - 1)
            Dist   = float(LLeft) + (float(LRight) - LLeft) * steper
            N[index1].append(Dist)
    N.append(ALU[len(ALU) - 1])

    P   = ut.UnwrapList(N)
    P_L = len(P)
    segment = float(P_L) / len(APU)
    LE = []
    for index3 in range(len(APU)):
        newIndex = int(float(P_L - 1) / (len(APU) - 1) * index3)
        LE.append(P[newIndex])
    return LE

Recursive Iteration

The input can be jagged (a list of curves, each with a different parameter set). The recursive function handles variable nesting depth:

python
# Handles jagged (variable-depth) inputs recursively.
# Arc_Curves can be a single curve or a nested list of curves.
def returnElements(Arc_Curves, Arc_Parameters, Lengths):
    if isinstance(Arc_Curves, (list, tuple)):
        retList = []
        for Arc_Curve in Arc_Curves:
            retList.append(returnElements(Arc_Curve, Arc_Parameters, Lengths))
        return retList
    elif isinstance(Arc_Parameters, (list, tuple)):
        retList = []
        for index, Arc_Parameter in enumerate(Arc_Parameters):
            try:
                retList.append(getPerpLine(Arc_Curves, Arc_Parameter, Lengths[index]))
            except:
                retList.append(False)
        return retList
    else:
        return getPerpLine(Arc_Curves, Arc_Parameters, Lengths)

Input and Normalisation

python
# Read Dynamo inputs
Arc_Curves     = IN[0]   # curve or list of curves
Arc_Parameters = IN[1]   # parameter values (0..1) at which to create lines
Arc_Length     = IN[2]   # one or more target lengths

# Normalise inputs
if ut.isIterable(Arc_Parameters):
    Arc_Parameters = ut.UnwrapList(Arc_Parameters)
    if ut.isIterable(Arc_Length):
        Arc_Length = ut.UnwrapList(Arc_Length)
        Lengths = composeLNF(Arc_Length, Arc_Parameters)
    else:
        Arc_Length = [Arc_Length]
        Lengths = composeLNF(Arc_Length, Arc_Parameters)
else:
    if ut.isIterable(Arc_Length):
        Lengths = False
    else:
        Lengths = Arc_Length

Output

python
# Execute and expose both the lines and the computed lengths
Result = returnElements(Arc_Curves, Arc_Parameters, Lengths)

OUT = Result, Lengths

Tags

DynamoPythonRevit
digitteck

© 2026 Digitteck