Tutorial: Routing in Large Spaces with Obstacles#
This tutorial demonstrates routing multiple pipes through a large room environment
(use_case_labyrinth.stp) that contains many fixed and mobile obstacles. For
assemblies of this scale, the CadMap is decomposed into a
coarse grid completed by a finer one where the coarse grid is blind: the search crosses
open space in coarse cells and still reaches the narrow passages.
What you will learn:
Splitting a CAD model into fixed and mobile parts
Choosing voxel sizes for large assemblies
Using
CadMap.decomposedwithmulti_scale=TrueRouting multiple pipes pooled as trays
Source script: scripts/simple_test_cases/large_room_with_obstacles.py
Assembly Structure#
The test assembly contains a large room with several obstacles identified by name. Some obstacles are considered “mobile” (can be repositioned) and some are “fixed”. We partition the CAD primitives into two groups to apply weighting rules to each independently:
from volmdlr.model import VolumeModel
from routing.core.core import RuledVolume
from routing.core.design_rules import WeightingRule
weighting_rule = WeightingRule("linear", 0.0, None, min_value=1, max_value=10)
# Names of mobile obstacles in this assembly
mobile_names = ["EnvSimpleDessia20", "EnvSimpleDessia21", ...]
mobile_primitives = [p for p in volume_model.primitives if p.name in mobile_names]
fixed_primitives = [p for p in volume_model.primitives if p.name not in mobile_names]
mobile_volume = RuledVolume.from_volume_model(
VolumeModel(mobile_primitives), [weighting_rule], name="mobile"
)
fixed_volume = RuledVolume.from_volume_model(
VolumeModel(fixed_primitives), [weighting_rule], name="fixed"
)
Choosing Voxel Size for Large Assemblies#
The assembly measures about 10.4 × 9.0 × 6.9 metres, and its five conduits are 100 mm in diameter. A coarse voxel size is appropriate:
voxel_size = 0.21 # 210 mm — about 2× the pipe diameter
For the optimizer, use a finer grid:
settings = Settings(voxel_size=0.07, ...) # 70 mm — 3× finer than routing grid
Building and Decomposing the CadMap#
The five conduits of the script are of pipe type "Type2"; ports maps each name to its
PortCouple, pipe_types to its PipeType.
A small volume around each port enters the bounding box, so that the routing space holds the
ports:
from routing.core.core import Specification
specifications = [
Specification(ports[name].start, ports[name].end, pipe_types[name], name) for name in ports
]
port_shapes = []
for couple in ports.values():
port_shapes += [couple.start.volmdlr_primitives(), couple.end.volmdlr_primitives()]
ports_volume = RuledVolume.from_volume_model(VolumeModel(port_shapes), [], name="ports")
Build a regular CadMap first, then decompose it into a coarse and a fine region:
from routing.core.cadmap import CadMap
from routing.core.design_rules import PoolingMode, PoolingRule, PoolingSpecification, TurnPenalizer
pooling_rule = PoolingRule(
pooling_specs=[
PoolingSpecification(
types=["Type2"],
pooling_mode=PoolingMode.strong_pooling(),
min_distance=0.0,
parallel=True,
)
],
min_distance=0.0,
cost=500,
)
cadmap = CadMap.from_project_inputs(
design_rules=[TurnPenalizer(cost=1), pooling_rule],
ruled_volumes=[fixed_volume, mobile_volume],
bounding_box=CadMap.build_bounding_volume(
[fixed_volume, mobile_volume, ports_volume], [], voxel_size
),
voxel_size=voxel_size,
exact_distances=True,
weights_operator="min_distance",
)
# Multi-scale: coarse grid (210 mm) + fine grid (70 mm)
decomposed_map = cadmap.decomposed(
multi_scale=True,
size_factor=3,
bounding_box=cadmap.bounding_box.bounding_box,
)
parallel=True pools the conduits as trays: side by side on the face they run along, in
layers — two at most by default, max_layers=2. A tray rests on the volumes flagged
RuledVolume(..., support=True); when none is flagged, as here, every volume is a support.
See Parallel Packs: Trays.
Routing and Optimization#
Pass the decomposed map to RoutePlanner exactly like a single-region CadMap:
from routing.core.finders import SmartFinder
from routing.core.optimizer import Costs, Settings
from routing.core.route_planner import RoutePlanner
finder = SmartFinder("AStar", "manhattan", "never")
route_planner = RoutePlanner(decomposed_map, finder)
result = route_planner.generate(
specifications=specifications,
n_iterations=1,
steady_mode=False,
)
# Visualize generated paths
packs = result.collect_packs(only_optimized=False)
result.specific_packs_display_3d(packs)
# Optimize
costs = Costs(
length=1000, sideways=100, short_line=5000,
gravity=5000, bend=250, constraints=5000, interferences=1e8,
)
settings = Settings(
voxel_size=0.07,
max_shift=0.5,
stabilization_threshold=1.0,
n_iterations=5,
max_iter=1000,
)
result = route_planner.optimize(
costs=costs, settings=settings, picked_path="best", max_refinements=0
)
packs = result.collect_packs(only_optimized=True)
result.specific_packs_display_3d(packs)
Memory and Performance Tips#
For assemblies larger than about 10 × 10 × 10 m with 200 mm voxels:
Check memory first:
print(cadmap.nbytes / 1e9, "GB")Use
weights_operator="min_distance"— more accurate than the default"min"Use multi-scale (
cadmap.decomposed(multi_scale=True)) to keep the routing grid coarse, a finer grid completing it where it is blindReduce
exact_distances=TruetoFalseif voxelization is too slow (slightly less accurate weighting, but much faster)Increase
voxel_sizeif memory is still an issue — halving the voxel count in each dimension reduces memory by 8×
See User Guide: Multi-Scale and Multi-Grid Routing for a complete guide to multi-scale routing.
Next Steps#
User Guide: Multi-Scale and Multi-Grid Routing — multi-scale and multi-grid routing guide
User Guide: Pipe Pooling (Bundling) — bundling pipes in large assemblies
User Guide: Path Geometry Optimization — optimizer settings for large-scale paths