routing.core.route_planner#

Module to plan the routing problem and use its result.

class routing.core.route_planner.RoutePlanner(cadmap: CadMap, finder: SmartFinder, name: str = '', n_iterations: int = 0)[source]#

Bases: DessiaObject, Displayable3D

A class representing the main planner for routing problems.

Coordinates pathfinding and optimization for pipe routing. It combines a CadMap (spatial representation) with a SmartFinder (pathfinding algorithm) to generate and optimize routing solutions.

Parameters:
  • cadmap – The CadMap object containing the routing space, volumes, and design rules.

  • finder – The SmartFinder object implementing the pathfinding algorithm (e.g., AStarFinder, DijkstraFinder).

  • name – The name to identify this route planner. Defaults to ‘’.

  • n_iterations – The alternatives looked for per conduit, as k shortest paths: at generation, and when a tee is routed again at optimization. Defaults to 0, one route.

explore(specification: TeeSpecification, step: float) → Solution[source]#

Route the tee specification once per site along the conduit it taps, as the conduits stand, laying nothing.

The conduit tapped is one routed already (TeeSpecification.tapped); the sites are the declared ones, else one every step (TapSite.every()). The routes are the alternatives of the Solution returned, one per site reached, named after it: pick one (Solution.pick(), Solution.pick_nearest()), then optimize() with that Solution among solutions lays it at its site.

find_paths(grid: SmartGridInterface, solution: Solution, n_iterations: int, overweighted_voxels: ndarray = None, grid_forced_zones: tuple[GridForcedZone, ...] = ()) → list[Alternative][source]#

Find routing paths for a solution.

Updates the CadMap with pipe type information, recomposes weights, applies shaped weighting, then calls CadMap.update_grid() so the search grid matches the latest weights and toggled attributes. With the default update_rule=True, that path also runs CadMap.update_rules_with_solutions(), which rebuilds axis_voxels from packs and the bundle_graph from cadmap.solutions before design rules read the new context. Finally runs the finder to connect ports on the updated grid.

Parameters:
  • grid – SmartGridInterface representing the searchable grid.

  • solution – Solution defining the routing problem (ports, pipe type).

  • n_iterations – Number of pathfinding iterations (e.g. k-shortest paths).

  • overweighted_voxels – Optional (n, 3) voxel indices given extra weight penalties.

  • grid_forced_zones – The forced zones laid on the grids, passed through in the order the search finds.

Returns:

The alternatives the finder built, one per route, with the ports each joins.

generate(specifications: list[Specification | TeeSpecification] | RoutePlanning, n_iterations: int | None = None, steady_mode: bool = False) → RoutingResults[source]#

Generate routing paths for multiple specifications.

The specifications are planned first (RoutePlanning): each tee after the conduit it taps, the given order kept otherwise, one Solution per conduit. Then, for each planned Solution, in that order:

  1. the search grid is refreshed from the routing state: its pipe type, the packs laid so far;

  2. the finder searches its paths, through the forced zones of the CadMap (GridForcedZone) in the order the search reaches them, to its end port, or to the tap goals along the conduit a tee taps (one search per site for a tee exploring its sites);

  3. the paths become the Solution’s alternatives, and the CadMap packs take the Solution in.

A conduit no path joins is kept, not routed. An exception raised while routing a conduit is raised again, unless steady_mode is True: the other conduits are then routed all the same.

Parameters:
  • specifications – The conduits to route, port couples and tees, or their RoutePlanning; a list is planned here: a tee is routed after the conduit it taps, the order is kept otherwise.

  • n_iterations – Number of pathfinding iterations (k-shortest paths). Defaults to the planner’s; given, it becomes the planner’s, so the tees routed again at optimization take as many.

  • steady_mode – If True, continues processing even if a specification fails. If False, raises exceptions on failure. Defaults to False.

Returns:

RoutingResults object containing all generated solutions and the CadMap used for routing.

generate_harness(specifications: list[Specification], settings: HarnessSettings | None = None, initial_routing: RoutingResults | None = None, name: str = '') → tuple[Bundle, HarnessOptimizationHistory][source]#

Route, bundle and robustly optimize the harness: unlock cramped zones, consolidate, shape.

settings is the whole declaration surface (HarnessSettings, product defaults when omitted), completed against this cadmap before anything runs; the bundle carries the cadmap’s design rules plus settings.harness_rules. The unlock stage alternates feasibility passes with regeneration: after a violation plateau, every cramped free line (_cramped_free_lines()) becomes a repulsive lock on the cadmap — its cylinder dilated by unlocking.lock_dilation_voxels routing voxels, weights inside multiplied by unlocking.lock_strength — the solutions it carries are withdrawn (CadMap.remove_solutions()) and rerouted around the locks, at most unlocking.max_iterations rounds. Every (re)generation is snapshot into the returned history alongside the optimization passes, with its complete solution set.

initial_routing skips the initial generation and starts the loop from an existing routing — the replay lever on this mutable cadmap. Rerunning the unlock stage with other lock knobs, without rerouting from scratch:

seed = history.snapshots[0]                    # "generated 1": full solutions, no lock
cadmap.restore_solutions(seed.solutions)
cadmap.withdraw_zones([lock for snapshot in history.snapshots for lock in snapshot.locks])
route_planner.generate_harness(..., initial_routing=RoutingResults(cadmap.solutions, cadmap))

The cadmap must already carry initial_routing’s solutions and packs (restored as above).

optimize(costs: Costs, settings: Settings, picked_path: str = 'best', max_refinements: int = 5, solutions: list[Solution] | None = None) → RoutingResults[source]#

Optimize the geometry of the routed solutions, in planning order.

The solutions and the packs of the CadMap are rebuilt in planning order, from each finalized geometry. For each solution:

  1. a conduit built on others, a tee, is routed again on them as they stand, its alternatives taken afresh (the planner’s n_iterations); a picked tee is routed at the site it was picked at, else at the nearest site in reach, with a warning;

  2. the alternative picked_path names is optimized: its geometry is refined by the global 3D optimizer, minimizing length, bends and constraint violations under the design rules;

  3. if max_refinements is above 0 and the optimization leaves dead ends or violated constraints, the costs are adjusted and the path searched again around the dead ends, then optimized again;

  4. the CadMap packs take the solution in, from its best_pipe.

A conduit left out of solutions is kept as it stands, its packs laid again.

Parameters:
  • costs – Costs object with cost weights for optimization (length, bends, constraints, etc.).

  • settings – Settings object with optimization parameters (voxel_size, max_shift, max_iter, etc.).

  • picked_path – The alternative to optimize: “best”, the one the solution stands by (its pick, else its cheapest generated path), or “last”, the one appended last. Defaults to “best”.

  • max_refinements – Maximum number of refinement iterations when problems are detected. If 0, no refinements are attempted. Defaults to 5.

  • solutions – The conduits to optimize; the others are kept as they stand. Defaults to all of them. A Solution not laid yet, one explore() returned, joins the plan at its end.

Returns:

RoutingResults object containing all optimized solutions and the CadMap used for routing.

optimize_with_new_costs(failed_constraints: dict[str, int], solution: Solution, optimizer: Optimizer, costs: Costs, settings: Settings, init_max_shift: float, alternative_index: int | None) → tuple[list[ndarray], dict[str, int]][source]#

Optimize a solution with adjusted cost weights based on constraint violations.

When initial optimization fails to satisfy constraints, this method:

  1. Creates a copy of the cost object

  2. Adjusts cost weights based on failed constraints:

    • is_short: Multiplies short_line cost by (violations + 1)

    • gravity: Multiplies gravity cost by (violations + 1)

    • clampable: Multiplies constraint cost by 5

  3. Increases max_shift by 50% to allow larger movements

  4. Re-runs optimization with the adjusted costs

This adaptive approach helps the optimizer find valid solutions when initial attempts fail by prioritizing constraint satisfaction.

Parameters:
  • failed_constraints – Dictionary mapping constraint names to violation counts (e.g., {“is_short”: 2, “gravity”: 1, “clampable”: 1}).

  • solution – Solution object to optimize.

  • optimizer – Optimizer instance with current settings.

  • costs – Original Costs object (will be copied and modified).

  • settings – Settings object with optimization parameters.

  • init_max_shift – Original max_shift value (will be increased by 50%).

  • alternative_index – The alternative to optimize, None for the one the solution stands by.

Returns:

Tuple of (overweighted_voxels, failed_constraints) where:

  • overweighted_voxels: List of arrays with problematic voxel indices

  • failed_constraints: Dictionary of remaining constraint violations

restart_avoiding_dead_ends(overweighted_voxels: list[ndarray], failed_constraints: dict[str, int], solution: Solution, alternative_index: int | None, grid: SmartGrid, optimizer: Optimizer, costs: Costs, settings: Settings, max_refinements: int = 5) → None[source]#

Restart pathfinding and optimization to avoid problematic regions.

When optimization fails to find valid solutions, this method attempts multiple refinement iterations:

  1. For each iteration (up to max_refinements): - Tries optimization with adjusted costs (via optimize_with_new_costs) - If still failing, generates a new path avoiding overweighted voxels - Updates solution with the new path - Re-optimizes with increased max_iter (3x original)

  2. If problems persist after all iterations: - Makes a final attempt with adjusted costs - Logs a warning if no valid solution is found

The overweighted voxels are accumulated across iterations to build a comprehensive map of problematic regions that should be avoided.

Parameters:
  • overweighted_voxels – List of arrays with problematic voxel indices from previous optimization attempts.

  • failed_constraints – Dictionary mapping constraint names to violation counts from previous optimization.

  • solution – Solution object to refine.

  • alternative_index – The alternative to optimize first, None for the one the solution stands by; every path generated here is appended and optimized as the last one.

  • grid – SmartGrid object for pathfinding.

  • optimizer – Optimizer instance with current settings.

  • costs – Costs object with cost weights.

  • settings – Settings object with optimization parameters.

  • max_refinements – Maximum number of refinement iterations to attempt. Defaults to 5.

Returns:

None (modifies solution in place).

volmdlr_primitives() → list[Primitive3D][source]#

Get volmdlr primitives for visualization.

Returns primitives from the CadMap for 3D visualization.

Returns:

List of Primitive3D objects from the CadMap

class routing.core.route_planner.RoutePlanning(solutions: list[Solution], name: str = '')[source]#

Bases: DessiaObject

The conduits to route, one Solution each, in the order they are routed in.

Built from specifications by from_specifications(): a conduit is routed after the conduits it depends on, a tee after the conduit it taps; the user’s order is kept otherwise: an entry whose dependencies are placed is added, an entry whose dependencies are not yet placed waits, and every addition releases the waiting entries in their order. A dependency absent from the specifications, or a cycle, is an error. A solution is named after its specification, else after its pipe type; a name several specifications share takes an index suffix, in planning order. A tee’s terminals hold the planned Solution of the conduit it taps: generate routes the solutions in order, so a tee finds its conduit routed, or not, when its turn comes.

dependents(solution: Solution) → list[Solution][source]#

Return the solutions built on solution, directly or through others, in planning order.

classmethod from_specifications(specifications: list[Specification | TeeSpecification], name: str = '') → RoutePlanning[source]#

Plan the routing of specifications: one Solution each, in the order given as far as the dependencies allow.

class routing.core.route_planner.RoutingResults(solutions: list[Solution], cadmap: CadMap | None = None, name: str = '')[source]#

Bases: DessiaObject, Displayable3D

A class representing routing results containing multiple solutions and the CadMap.

Contains all routing solutions generated by RoutePlanner along with the CadMap used for routing. It provides methods for visualization and analysis.

Parameters:
  • solutions – The list of Solution objects from routing.

  • cadmap – The optional CadMap object used for routing. Defaults to None.

  • name – The name to identify this routing results. Defaults to ‘’.

collect_packs(only_optimized: bool = True) → list[Pack][source]#

Collect all pipeline packs from optimized solutions.

Gathers Pack objects that group aligned PipeLine segments from multiple solutions. Packs are created when pipes share similar paths and can be routed together for efficiency. The method:

  1. Iterates through all packs in the CadMap

  2. For each pack, collects pipe lines from solutions that: - Are well optimized (if only_optimized=True) or all solutions (if False) - Have pipe lines that belong to the pack

  3. Creates new Pack objects containing only the best pipe lines from each solution that match the pack’s alignment and proximity criteria

Packs are used for: - Collision detection between aligned pipes - Pooling optimization (routing pipes together) - Managing pipe clusters in voxelized space

Parameters:

only_optimized – If True, only collects packs from solutions that are well optimized (is_well_optimized=True). If False, includes packs from all solutions regardless of optimization status. Defaults to True.

Returns:

List of Pack objects, each containing aligned pipe lines from multiple solutions that can be routed together. Each pack includes the best pipe lines from matching solutions and maintains the pack’s alignment vector and parallel status.

get_solutions_bounds(offset: float = 0.0) → ndarray[source]#

Get the bounds of all solutions with an offset.

plot_data_generated(reference_path: str = '') → MultiplePlots#

Specific display tag for PlotData. Add a picture argument.

plot_data_optimized(reference_path: str = '') → MultiplePlots#

Specific display tag for PlotData. Add a picture argument.

property routed_solutions: list[Solution]#

The solutions a route joins: the only ones with a geometry to read.

solution(name: str) → Solution[source]#

Return the solution named name.

specific_packs_display_3d(packs: list[Pack]) → None[source]#

Display 3D CAD view of solutions with specific packs highlighted.

Creates a combined visualization showing: - All solutions from the CadMap - The specified packs (highlighted) - The CadMap voxelization (if available)

Opens an interactive 3D viewer (BabylonJS) for inspection.

Parameters:

packs – List of Pack objects to highlight in the visualization.

Returns:

None (opens interactive 3D viewer).

summary() → str[source]#

One line per conduit: what it taps, routed, alternatives, generated cost, optimized cost, well optimized.

to_bundle(rules: list[DesignRule] | None = None, settings: SkeletonSettings | None = None, name: str = '') → Bundle[source]#

Skeletonize the routed solutions and wire them into a Bundle.

settings is the skeletonization recipe the bundle then carries (Bundle.settings), so rebuilding a sibling bundle from a new routing result reuses it. When rules is None, the design rules that governed this routing (cadmap.design_rules) are carried over — same source as RoutePlanner.find_paths().

to_skeleton(settings: SkeletonSettings | None = None, display_operations: bool = False) → Skeleton[source]#

Skeletonize the solutions’ voxelized volume under settings, then fit the skeleton to the solutions.

settings.voxel_size left None voxelizes at the cadmap voxel size (voxelize_solutions()).

volmdlr_primitives() → list[Primitive3D][source]#

Get volmdlr primitives for 3D visualization.

Collects primitives from all solutions and optionally from the CadMap to create a complete 3D visualization of the routing results.

Returns:

List of Primitive3D objects combining: - All primitives from all solutions in the results - Primitives from the CadMap (if available)

voxelize_solutions(voxel_size: float = 0, dilation_voxels: int = 0) → RoutingVoxelization[source]#

Voxelize all solutions onto a shared grid; voxel_size=0 defaults to the cadmap voxel size.