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.12.
All core dependencies (volmdlr, numpy, scipy, etc.) are installed automatically.
Verify the installation:
import routing
print(routing.__version__)
Your First Routing Problem#
This example routes a pipe through a window frame assembly — a 3D CAD model with two
volumes (a border frame and an outer shell). We define where the pipe starts and ends, what
type of pipe it is, and let routing find the path automatically.
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).
The window assembly has two volumes: the outer one (primitives[1], a box) and the border
(primitives[0], a frame with a window). Its STEP file is
data/step/Cas_test_windows_V5.step in the routing repository; it is not installed with the
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:
import routing.core.legacy_piping as 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 × pipe radius
length_before_turn_min=0.0, # no minimum straight segment required
color=(0, 100, 200), # RGB display colour, 0–255 or 0–1
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), # points along the pipe, into the room (−Y)
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. It is a
project decision with no universal formula: pick it from the narrowest passages your
pipes must thread, then refine on the routed result (the window example runs at 21 mm):
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, # paths searched per conduit: 0 or 1 = the best only; N = up to N
steady_mode=False, # False: an error met while routing a conduit is raised
)
routing_result is a RoutingResults object
containing the found paths. n_iterations given here becomes the planner’s
(RoutePlanner(cadmap, finder, n_iterations=...), 0 by default). A conduit no path joins
is kept as not routed, with a warning: check it before reading its geometry.
print(routing_result.summary()) # one line per conduit: routed, paths, cost
solution = routing_result.solution("PipeA_route")
if solution.is_routed:
print(solution.best_path.cost)
routed = routing_result.routed_solutions # the conduits with a geometry to read
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, then compare the paths on their indicators:
# 3D view: the conduits and the CAD
routing_result.display_3d()
# Parallel plots of the paths' indicators, generated then optimized
routing_result.plot_data_generated().plot()
routing_result.plot_data_optimized().plot()
Pipes gather in packs only under a PoolingRule: see User Guide: Pipe Pooling (Bundling).
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, ERROR, WARNING, 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:
Use Cases: Which Script to Start From — find the example script that does what you want to do
User Guide: Core Concepts — understand every class in the routing pipeline
Tutorial: Routing Pipes Through a CAD Assembly — full step-by-step tutorial with the window test case
Tutorial: Engineering Design Rules — control routing with engineering constraints
Tutorial: Routing Zones — define attractive, repulsive, forbidden, and forced zones
Tutorial: Tees — Tapping a Routed Conduit — tap a routed conduit with a tee
User Guide: Pipe Pooling (Bundling) — gather conduits in packs or trays, or keep them apart
User Guide: Accessing Routing Results — read the solutions, their alternatives and their geometry
Tutorial: 3D Pathfinding with routing.pathfinding — use the standalone 3D pathfinding module
User Guide: Path Geometry Optimization — tune the optimizer for your use case
User Guide: Harness Generation — bundle the routed cables into one optimized harness
Migrating to v3.0.0 — upgrade code written for v2.12
Example Scripts — browse all example scripts