User Guide: Multi-Scale and Multi-Grid Routing#

For large or complex assemblies you can split the routing space into multiple CadMap grids. This gives you two capabilities:

  • Multi-scale routing: one coarse grid for open space + one or more fine grids near critical areas. Faster voxelization while preserving accuracy where it matters.

  • Multi-grid routing (mono-scale): decompose the routing space into independent sub-regions and route pipes across them. Useful for very large assemblies that would exhaust memory as a single grid.

Both scenarios use MultiCadMap as the routing container and pass it to RoutePlanner exactly like a regular CadMap.

MultiCadMap#

MultiCadMap is a container for a list of CadMap objects. The router treats them as a unified space with implicit connections at shared boundaries.

from routing.core.cadmap import MultiCadMap

Key properties:

  • multi_cadmap.main_cadmap — the primary grid (cad_maps[0])

  • multi_cadmap.voxel_sizes — set of unique voxel sizes (length > 1 → multi-scale)

  • multi_cadmap.nbytes — total memory across all grids

  • multi_cadmap.is_multi_scaleTrue if grids have different voxel sizes

Creating a MultiCadMap#

Three factory methods are available:

1. From an existing CadMap (most common):

from routing.core.cadmap import CadMap, MultiCadMap

# Build a regular CadMap first
cadmap = CadMap.from_project_inputs(
    design_rules=design_rules,
    ruled_volumes=[outer_volume, border_volume],
    bounding_box=bounding_box,
    voxel_size=0.021,
    exact_distances=True,
)

# Wrap it into a MultiCadMap (mono-scale: auto-decompose into sub-regions)
multi_cadmap = MultiCadMap.from_cad_map(cadmap, multi_scale=False)

# Or: multi-scale (adds a finer grid at size_factor× resolution)
multi_cadmap = MultiCadMap.from_cad_map(
    cadmap,
    multi_scale=True,
    size_factor=3,    # fine grid = voxel_size / 3 ≈ 7 mm
    bounding_box=cadmap.bounding_box.bounding_box,
)

Parameters of from_cad_map:

Parameter

Default

Description

cad_map

required

The existing CadMap to build from.

multi_scale

False

If True, creates a hierarchical fine-grid overlay (multi-scale). If False, decomposes the coarse grid into sub-regions (mono-scale).

size_factor

3

Resolution multiplier for the fine grid (voxel_size / size_factor). Used only when multi_scale=True.

bounding_box

None

Optional bounding box to restrict fine-grid creation. Pass cadmap.bounding_box.bounding_box to cover the full space.

2. Directly from project inputs (combines CadMap.from_project_inputs and wrapping):

multi_cadmap = MultiCadMap.from_project_inputs(
    design_rules=design_rules,
    ruled_volumes=[outer_volume, border_volume],
    bounding_box=bounding_box,
    voxel_size=0.021,
    exact_distances=True,
    multi_scale=True,
    size_factor=3,
)

3. From a list of pre-built CadMaps (same voxel size required):

multi_cadmap = MultiCadMap.from_cad_maps([cadmap_region_A, cadmap_region_B])

All CadMaps in the list must share the same voxel_size.

Using MultiCadMap with RoutePlanner#

Pass a MultiCadMap to RoutePlanner exactly like a single CadMap:

from routing.core.finders import SmartFinder
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)

Visualizing the Multi-Grid Layout#

You can inspect the grid decomposition before routing:

from volmdlr.model import VolumeModel

# Show all sub-grid boundaries and their connections
VolumeModel(multi_cadmap.voxel_meshes(show_connections=True)).babylonjs()

Complete Multi-Scale Example#

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

from routing.core.cadmap import CadMap, MultiCadMap
from routing.core.core import Port, PipeType, RuledVolume, Specification
from routing.core.design_rules import TurnPenalizer, WeightingRule
from routing.core.finders import SmartFinder
from routing.core.optimizer import Costs, Settings
from routing.core.route_planner import RoutePlanner
from piping_3d import piping

# --- Load CAD ---
model = Step.from_file("data/step/Cas_test_windows_V5.step")
volume_model = model.to_volume_model()

weighting_rule = WeightingRule("linear", 0.0, 0.2, min_value=1, max_value=10)
outer_volume = RuledVolume.from_volume_model(
    VolumeModel([volume_model.primitives[1]]), [weighting_rule], name="outer"
)
border_volume = RuledVolume.from_volume_model(
    VolumeModel([volume_model.primitives[0]]), [weighting_rule], name="border"
)

# --- Build coarse CadMap ---
voxel_size = 0.021
bounding_box = CadMap.build_bounding_volume(
    [outer_volume, border_volume], [], voxel_size, voxel_margin=1
)
cadmap = CadMap.from_project_inputs(
    design_rules=[TurnPenalizer(cost=3.0)],
    ruled_volumes=[outer_volume, border_volume],
    bounding_box=bounding_box,
    voxel_size=voxel_size,
    exact_distances=True,
    weights_operator="min_distance",
)

# --- Wrap into multi-scale MultiCadMap ---
multi_cadmap = MultiCadMap.from_cad_map(
    cadmap,
    multi_scale=True,
    size_factor=3,       # adds a ~7 mm fine grid
    bounding_box=cadmap.bounding_box.bounding_box,
)

# --- Route ---
pipe_type = PipeType(
    section=piping.Section(radius_equivalent=0.015),
    name="PipeA",
    radius_of_curvature_ratio_min=1.5,
    color=(0, 150, 200),
    type_="TypeA",
)
spec = Specification(
    Port((0.05, -0.4, 0.1), (0, 1, 0), length=0.1),
    Port((0.05,  0.8, 0.1), (0, -1, 0), length=0.1),
    pipe_type, "route_A",
)

route_planner = RoutePlanner(multi_cadmap, SmartFinder("AStar", "manhattan", "never"))
result = route_planner.generate(specifications=[spec], n_iterations=1)

# --- Optimize ---
costs = Costs(length=1000, sideways=100, short_line=5000, gravity=5000,
              bend=250, constraints=5000, interferences=1e8)
settings = Settings(voxel_size=0.006, max_shift=0.1, stabilization_threshold=1.0,
                    n_iterations=3, max_iter=1000)
result = route_planner.optimize(costs=costs, settings=settings, picked_path="best")
result.specific_packs_cad_view(result.collect_packs(only_optimized=True))

When to Use Multi-Scale vs Multi-Grid#

Scenario

Recommended approach

Why

Large assembly, uniform complexity

from_cad_map(multi_scale=False)

Auto-decomposes into sub-regions; no memory explosion

Assembly with a few critical tight spots

from_cad_map(multi_scale=True, size_factor=3)

Coarse grid for open space, fine grid for tight areas

Assembly with distinct disconnected zones

from_cad_maps([zone_A, zone_B])

Build separate CadMaps per zone; combine manually

Single small assembly

Plain CadMap

Simpler; MultiCadMap adds overhead not needed here

Next Steps#