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 :class:`~routing.core.cadmap.MultiCadMap` as the routing container and pass it to :class:`~routing.core.route_planner.RoutePlanner` exactly like a regular ``CadMap``. .. contents:: On this page :local: :depth: 1 MultiCadMap ----------- :class:`~routing.core.cadmap.MultiCadMap` is a container for a list of :class:`~routing.core.cadmap.CadMap` objects. The router treats them as a unified space with implicit connections at shared boundaries. .. code-block:: python 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_scale`` — ``True`` if grids have different voxel sizes Creating a MultiCadMap ---------------------- Three factory methods are available: **1. From an existing CadMap** (most common): .. code-block:: python 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``: .. list-table:: :header-rows: 1 :widths: 25 15 60 * - Parameter - Default - Description * - ``cad_map`` - required - The existing :class:`~routing.core.cadmap.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): .. code-block:: python 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): .. code-block:: python 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 :class:`~routing.core.route_planner.RoutePlanner` exactly like a single ``CadMap``: .. code-block:: python 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: .. code-block:: python 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 ----------------------------- .. code-block:: python 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 -------------------------------------- .. list-table:: :header-rows: 1 :widths: 30 35 35 * - 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 ---------- - :doc:`../tutorials/basic_routing` — full routing tutorial with a single CadMap - :doc:`optimization` — tune the geometry optimizer - :doc:`pooling` — pipe bundling across multi-grid setups