Core Concepts#
This page explains the main objects you interact with in every routing workflow.
Understanding these classes and how they fit together is the foundation for using
routing effectively.
Overview#
Every routing workflow follows the same structure:
Define inputs:
Port,PipeType,SpecificationDefine the routing space:
RuledVolume,CadMapFind paths:
RoutePlannerRead results:
RoutingResults
Here is how they relate:
Port (start) ──┐
├──► Specification ──┐
Port (end) ──┘ │
PipeType ──────────────────────────►│
│
VolumeModel ──► RuledVolume ──────►CadMap ──► RoutePlanner.generate(specs) ──► RoutingResults
DesignRules ──►────────────────────►│
│
(optional) ──────── RoutePlanner.optimize() ──► refined RoutingResults
Port#
A Port is a fixed connection point in 3D space where a pipe
starts or ends. It has a position, an orientation, and an optional minimum straight length.
from routing.core.core import Port
port = Port(
coordinates=(0.05, -0.4, 0.1), # (x, y, z) in metres
direction=(0, 1, 0), # pipe exits in the +Y direction
length=0.1, # 100 mm straight before the first turn
name="inlet_port",
)
Parameters:
coordinates (tuple of 3 floats): The (x, y, z) position of the port in the global coordinate frame (metres).
direction (tuple of 3 floats): The direction the pipe must leave this port (for a start port) or arrive (for an end port). Does not need to be a unit vector — it is normalized automatically.
length (float, default 0.0): Minimum straight segment length at the port before the first direction change. Required for pipes with minimum bend radius constraints.
len_adaptable_with_environment (bool, default False): When
True, the router will automatically extend the port length if the immediate environment would cause collisions. This attribute is not used in generic mode, it is an attribute for custom algorithms.permitted_departure_angle (list of float, optional): If set, restricts the departure direction to a list of allowed angles (in radians). Leave as
Noneto permit all directions. This attribute is not used in generic mode, it is an attribute for custom algorithms.
Importing ports from Excel:
For large assemblies, define ports in an Excel spreadsheet and import them:
# Excel columns: name | coordinates_x | coordinates_y | coordinates_z
# | direction_x | direction_y | direction_z | length
with open("ports.xlsx", "rb") as f:
ports = Port.from_xlsx_stream(f)
PipeType#
A PipeType defines the physical properties of a class of
pipe. You reuse the same PipeType for all pipes of that specification.
from piping_3d import piping
from routing.core.core import PipeType
section = piping.Section(radius_equivalent=0.015) # 30 mm outer diameter
pipe_type = PipeType(
section=section,
name="HydraulicLine",
radius_of_curvature_ratio_min=1.5, # bend radius ≥ 1.5 × diameter
length_before_turn_min=0.030, # 30 mm straight before any turn
color=(0, 100, 200), # visualization color (RGB 0–255)
type_="Hydraulic", # group label for pooling
)
Key parameters:
section: A
piping.Sectionobject specifying the outer radius of the pipe (radius_equivalent).radius_of_curvature_ratio_min (float): Minimum bend radius as a multiple of the pipe diameter. Common values: 1.5 for flexible hoses, 3–5 for rigid pipes.
length_before_turn_min (float): Minimum straight length required before any direction change. Set to 0 for fully flexible tubing.
type_ (str): A group label. Pipes with the same
type_can be pooled together (see User Guide: Pipe Pooling (Bundling)).min_slope / max_slope (float, optional): For gravity-drained pipes, set
min_slopeto the minimum required slope (e.g.0.02= 2% grade). See Design Rules Reference forGravityRule.priority_index (int, optional): Routing priority. Lower index = higher priority (routed first when ordering matters). This attribute is not used in generic mode, it is an attribute for custom algorithms.
color: RGB tuple in range (0–255). Used by the visualization layer.
Compact creation with Specification.from_complete_definition:
If you only have a few pipes and don’t need to reuse PipeType objects, you can skip
creating them explicitly:
from routing.core.core import Specification
spec = Specification.from_complete_definition(
start=port_start,
end=port_end,
radius=0.015, # pipe outer radius
name="HydraulicLine_1",
radius_of_curvature_ratio=1.5,
min_straight_length=0.030,
pooling_type="Hydraulic",
color=(0, 100, 200),
)
Specification#
A Specification is a routing requirement: it pairs a start
port, end port, and pipe type into one object that RoutePlanner.generate() processes.
from routing.core.core import Specification
# Standard construction
spec = Specification(
start=port_start,
end=port_end,
pipe_type=pipe_type,
name="HydraulicLine_route_1",
)
# Shorthand: use pipe_type.name automatically
spec = Specification.from_ports(port_start, port_end, pipe_type)
Create one Specification per pipe to route. Pass a list of them to generate().
RuledVolume#
A RuledVolume wraps a 3D CAD volume (a VolumeModel from
volmdlr) and attaches a list of design rules to it. The design rules
control how the routing algorithm weights the space near that volume.
from routing.core.core import RuledVolume
from routing.core.design_rules import WeightingRule
from volmdlr.model import VolumeModel
# A weighting rule: routes prefer to stay close to this volume
weighting_rule = WeightingRule(
function="linear",
min_length=0.0, max_length=0.2,
min_value=1, max_value=10,
)
# Wrap the CAD volume
ruled_volume = RuledVolume.from_volume_model(
volume_model=VolumeModel([my_cad_primitive]),
design_rules=[weighting_rule],
name="main_structure",
)
Factory methods:
RuledVolume.from_volume_model(volume_model, design_rules, name)— the standard way to create aRuledVolumefrom a volmdlrVolumeModel.RuledVolume.from_ruled_volume(existing_rv, design_rules)— copy an existingRuledVolumeand override its global rules.
in_border parameter: Set in_border=True to use this volume for computing the
automatic bounding box of the routing space. In practice you need to set this
manually when using CadMap.build_bounding_volume() in order to use the specified volume’s bounding box into the
construction of the main bounding box.
See Design Rules Reference for all available design rule types.
CadMap#
CadMap is the central routing space object. It:
Takes all your
RuledVolumeobjects and voxelizes the free space between themComputes distance fields from the CAD surfaces to every voxel
Applies the design rules to compute a weight matrix
Stores the result as a compact grid ready for pathfinding
from routing.core.cadmap import CadMap
from routing.core.design_rules import TurnPenalizer
voxel_size = 0.021 # 21 mm — about 0.7× the largest pipe diameter
# Global rules applied to all pipes
global_rules = [TurnPenalizer(cost=3.0)]
# Auto-compute a bounding box from the volumes
bounding_box = CadMap.build_bounding_volume(
ruled_volumes=[ruled_vol_1, ruled_vol_2],
border_rules=[],
voxel_size=voxel_size,
voxel_margin=1,
)
# Build the CadMap (this does the voxelization + distance computation)
cadmap = CadMap.from_project_inputs(
design_rules=global_rules,
ruled_volumes=[ruled_vol_1, ruled_vol_2],
bounding_box=bounding_box,
voxel_size=voxel_size,
exact_distances=True,
)
Key parameters of from_project_inputs:
Parameter |
Default |
Description |
|---|---|---|
|
|
Global rules applied to every pipe type (e.g. |
|
required |
List of |
|
|
Bounding |
|
|
Voxel edge length in metres. Use 1.5–3× the largest pipe radius. |
|
|
Exact mesh distances (accurate, slower) vs voxel approximation (fast) |
|
|
How to combine weights from multiple volumes. |
|
|
Set |
|
|
Build visual box primitives per voxel (expensive; use only for debugging) |
Choosing voxel_size:
Start with
voxel_size = 1.5 × largest_pipe_radiusHalve it if paths look inaccurate or miss narrow passages
Double it if voxelization takes too long
Memory: cadmap.nbytes reports total memory usage. Large assemblies with fine
voxels can require several GB.
RoutePlanner#
RoutePlanner coordinates the pathfinding and
optimization. It takes a CadMap and a SmartFinder and exposes two main methods:
generate() and optimize().
from routing.core.finders import SmartFinder
from routing.core.route_planner import RoutePlanner
finder = SmartFinder(
algorithm="AStar", # "AStar", "ThetaStar", "Dijkstra"
heuristic="manhattan", # "manhattan", "euclidean", "octile"
diagonal="never", # "never", "always", "only_when_no_obstacle"
)
route_planner = RoutePlanner(cadmap, finder)
generate() — find routing paths:
routing_result = route_planner.generate(
specifications=[spec1, spec2, spec3],
n_iterations=1, # k-shortest paths; 1 = only the best path per pipe
steady_mode=False, # True = continue even if some pipes fail to route
)
Pipes are processed in the order they appear in specifications. Earlier pipes
influence the grid weights for later pipes (through the pooling and history mechanisms).
optimize() — refine path geometry:
from routing.core.optimizer import Costs, Settings
routing_result = route_planner.optimize(
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, n_iterations=3, max_iter=1000),
picked_path="best", # "best" (lowest cost path) or "last" (most recent)
max_refinements=0, # extra passes to fix constraint violations
)
See User Guide: Path Geometry Optimization for a full guide to the optimizer.
RoutingResults#
RoutingResults is the output of generate() and
optimize(). It contains all routing solutions and exposes visualization methods:
# Plot all generated paths (before optimization)
routing_result.plot_data_generated().plot()
# Plot all optimized paths
routing_result.plot_data_optimized().plot()
# Collect pipe bundles (groups of pipes routed together)
packs = routing_result.collect_packs(only_optimized=False)
routing_result.specific_packs_cad_view(packs)
# Get bifurcation points (where bundles split)
bifurcation_pts = routing_result.cadmap.get_bifurcation_points(packs, tolerance=0.04)
See User Guide: Accessing Routing Results for a complete guide to reading and exporting results.
Next Steps#
Tutorial: Routing Pipes Through a CAD Assembly — hands-on tutorial with the full routing workflow
Design Rules Reference — all design rule types explained
User Guide: Pipe Pooling (Bundling) — bundle pipes into organized packs
User Guide: Path Geometry Optimization — tune the geometry optimizer
User Guide: Multi-Scale and Multi-Grid Routing — route large assemblies with multiple voxel sizes