Quickstart#

routing is a Python package for automatically routing pipes, tubes, and wires through 3D CAD environments. Given a 3D model of your assembly, port connection points, and a set of design rules, routing finds and optimizes feasible paths that respect your engineering constraints.

Installation#

Install with pip:

pip install routing

Requirements: Python ≥ 3.9.

All core dependencies (volmdlr, numpy, scipy, piping_3d, etc.) are installed automatically.

Verify the installation:

import routing
print(routing.__version__)

Your First Routing Problem#

This example routes multiple pipes through a window frame assembly — a 3D CAD model with two volumes (a border frame and an outer shell). We define where each pipe starts and ends, what type of pipe it is, and let routing find the paths automatically.

../_static/index-images/getting_started.svg

Step 1 — Load a CAD Model#

routing uses volmdlr to load STEP/STP files:

from volmdlr.step import Step
from volmdlr.model import VolumeModel

model = Step.from_file("path/to/your_assembly.step")
volume_model = model.to_volume_model()

Step 2 — Wrap Volumes with Routing Rules#

A RuledVolume wraps a CAD volume and attaches design rules to it — for example, a WeightingRule that tells the router to prefer paths near the surface (lower cost = preferred path). In this example, it is constituted with 2 parts: a outer volume (a box) and a inner volume (a box with window). The step file can be found in data/ in the root directory of the routing package.

from routing.core.core import RuledVolume
from routing.core.design_rules import WeightingRule

# Penalize routing far from the surface (paths stay closer to the assembly)
weighting_rule = WeightingRule(
    function="linear",
    min_length=0.0,    # start applying at distance 0
    max_length=0.2,    # stop applying at distance 0.2 m
    min_value=1,       # cost multiplier at min_length
    max_value=10,      # cost multiplier at max_length
)

# Wrap each CAD volume
outer_volume = RuledVolume.from_volume_model(
    VolumeModel([volume_model.primitives[1]]),
    design_rules=[weighting_rule],
    name="outer",
)
border_volume = RuledVolume.from_volume_model(
    VolumeModel([volume_model.primitives[0]]),
    design_rules=[weighting_rule],
    name="border",
)

Step 3 — Define Pipe Types#

A PipeType describes the physical properties of a pipe: its outer radius and minimum bend radius:

from piping_3d import piping
from routing.core.core import PipeType

# A 30 mm diameter pipe (radius = 0.015 m)
section = piping.Section(radius_equivalent=0.015)

pipe_type_A = PipeType(
    section=section,
    name="PipeA",
    radius_of_curvature_ratio_min=1.5,   # min bend radius = 1.5 × diameter
    length_before_turn_min=0.0,           # no minimum straight segment required
    color=(0, 100, 200),                  # RGB display color
    type_="TypeA",                        # group label for pooling
)

Step 4 — Define Ports#

A Port is a connection point — a position in 3D space with an orientation (the direction the pipe must leave/enter the port) and an optional minimum straight length before the first turn:

from routing.core.core import Port

port_start = Port(
    coordinates=(0.05, -0.4, 0.1),   # (x, y, z) in metres
    direction=(0, 1, 0),              # pipe exits in +Y direction
    length=0.1,                       # 100 mm straight before first turn
    name="start_port",
)

port_end = Port(
    coordinates=(0.05, 0.8, 0.1),
    direction=(0, -1, 0),             # pipe enters from +Y direction
    length=0.1,
    name="end_port",
)

Step 5 — Create Specifications#

A Specification pairs a start port, end port, and pipe type into a routing requirement. Create one per pipe to route:

from routing.core.core import Specification

spec_A = Specification(
    start=port_start,
    end=port_end,
    pipe_type=pipe_type_A,
    name="PipeA_route",
)

Step 6 — Build the CadMap#

The CadMap voxelizes the routing space. The voxel_size parameter controls resolution — smaller values are more accurate but slower. A good starting point is 2–3× the largest pipe diameter:

from routing.core.cadmap import CadMap
from routing.core.design_rules import TurnPenalizer

voxel_size = 0.021  # 21 mm voxels

# Global design rules (apply to all pipe types)
design_rules = [TurnPenalizer(cost=3.0)]

# Build a bounding box around the routing space
bounding_box = CadMap.build_bounding_volume(
    [outer_volume, border_volume],
    border_rules=[],
    voxel_size=voxel_size,
    voxel_margin=1,
)

cadmap = CadMap.from_project_inputs(
    design_rules=design_rules,
    ruled_volumes=[outer_volume, border_volume],
    bounding_box=bounding_box,
    voxel_size=voxel_size,
    exact_distances=True,
)

Step 7 — Generate Routing Paths#

Create a RoutePlanner and call generate() with your list of specifications:

from routing.core.finders import SmartFinder
from routing.core.route_planner import RoutePlanner

# A* pathfinding with Manhattan heuristic, no diagonal movement
finder = SmartFinder("AStar", "manhattan", "never")

route_planner = RoutePlanner(cadmap, finder)

routing_result = route_planner.generate(
    specifications=[spec_A],
    n_iterations=1,     # 0 = single best path (default); N = best + N alternatives
    steady_mode=False,  # stop on first failure
)

routing_result is a RoutingResults object containing the found paths.

Step 8 — Optimize the Paths (optional)#

The paths from generate() follow the voxel grid and may look “staircase-like”. The optimize() step refines them into smooth geometry respecting port tangency:

from routing.core.optimizer import Costs, Settings

costs = Costs(
    length=1000,
    sideways=100,
    short_line=5000,
    gravity=5000,
    bend=250,
    constraints=5000,
    interferences=1e8,
)
settings = Settings(
    voxel_size=0.01,
    max_shift=0.1,
    stabilization_threshold=5.0,
    n_iterations=3,
    max_iter=1000,
)

routing_result = route_planner.optimize(
    costs=costs,
    settings=settings,
    picked_path="best",
)

Step 9 — Visualize Results#

Display the result in a 3D viewer:

# Show generated paths
routing_result.plot_data_generated().plot()

# Collect and display pipe packs (bundled pipes)
packs = routing_result.collect_packs(only_optimized=False)
routing_result.specific_packs_cad_view(packs)

# Show optimized paths
routing_result.plot_data_optimized().plot()

Controlling Verbosity#

Use routing.core.verbose to control the amount of output:

from routing.core.verbose import VerboseLevel, verbose

verbose.set_level(VerboseLevel.INFO)   # options: SILENT, WARNING, ERROR, INFO, DEBUG, TRACE

Set VerboseLevel.TRACE to see detailed pathfinding progress, or VerboseLevel.SILENT to suppress all output.

Next Steps#

Now that you have the basics working, explore more features: