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, using a MultiCadMap with multi-scale grids is essential to keep memory and computation times manageable.

What you will learn:

  • Splitting a CAD model into fixed and mobile parts

  • Choosing voxel sizes for large assemblies

  • Using MultiCadMap.from_cad_map with multi_scale=True

  • Routing multiple pipes with pooling

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 dimensions are on the order of 15 × 10 × 10 metres with pipe radii of 50 mm. 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

Rule of thumb for large assemblies:

  • Routing grid: voxel_size = 1-2 * max_pipe_radius

  • Optimization grid: voxel_size_opt = voxel_size / 3

Building the CadMap and MultiCadMap#

Build a regular CadMap first, then wrap it in a MultiCadMap with multi-scale decomposition:

from routing.core.cadmap import CadMap, MultiCadMap
from routing.core.design_rules import PoolingMode, PoolingRule, PoolingSpecification, TurnPenalizer

pooling_rule = PoolingRule(
    pooling_specs=[
        PoolingSpecification(
            types=["TypeA"],
            pooling_mode=PoolingMode.strong_pooling(),
            min_distance=0.0,
            parallel=True,
        )
    ],
    min_distance=0.0,
    distance_multiplier=500,
    volume_indices=[0, 1],
)

cadmap = CadMap.from_project_inputs(
    design_rules=[TurnPenalizer(cost=10.0), pooling_rule],
    ruled_volumes=[fixed_volume, mobile_volume],
    bounding_box=CadMap.build_bounding_volume(
        [fixed_volume, mobile_volume], [], voxel_size
    ),
    voxel_size=voxel_size,
    exact_distances=True,
    weights_operator="min_distance",
)

# Multi-scale: coarse grid (210 mm) + fine grid (70 mm)
multi_cadmap = MultiCadMap.from_cad_map(
    cadmap,
    multi_scale=True,
    size_factor=3,
    bounding_box=cadmap.bounding_box.bounding_box,
)

Routing and Optimization#

Pass the MultiCadMap to RoutePlanner exactly like a single 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(multi_cadmap, 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_cad_view(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_cad_view(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 (MultiCadMap.from_cad_map(multi_scale=True)) to keep the routing grid coarse while preserving accuracy near the pipes

  • Reduce exact_distances=True to False if voxelization is too slow (slightly less accurate weighting, but much faster)

  • Increase voxel_size if 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#