Design Rules Reference#
This page is the complete parameter reference for every design rule class in
routing.core.design_rules.
Where to Attach Rules#
Design rules can be attached in two places:
Per-volume rules — attach to a
RuledVolumevia itsdesign_ruleslist. The rule applies relative to that CAD surface’s distance field.from routing.core.core import RuledVolume from routing.core.design_rules import WeightingRule volume = RuledVolume.from_volume_model( volume_model, design_rules=[WeightingRule(...)], name="v1" )
Global rules — pass to
CadMap.from_project_inputs(design_rules=[...]). These rules apply everywhere in the routing space.cadmap = CadMap.from_project_inputs( design_rules=[TurnPenalizer(cost=3.0), GravityRule(min_value=0.02)], ruled_volumes=[...], ... )
The table below shows where each rule can be used:
Rule |
Per-volume |
Global |
Notes |
|---|---|---|---|
|
✓ |
— |
Requires a distance field; attach to a volume |
|
— |
✓ |
Applies at every direction change |
|
✓ |
— |
Tags voxels; usually paired with |
|
— |
✓ |
Automatically instantiated if |
|
— |
✓ |
Automatically instantiated for a specific |
|
✓ |
— |
Requires |
|
— |
✓ |
Zone defined by an arbitrary 3D solid ; instantiated as a global_rule at |
|
— |
✓ |
Governs pipe bundling; see User Guide: Pipe Pooling (Bundling) |
WeightingRule#
Assigns a cost multiplier to voxels based on their distance from a CAD surface.
from routing.core.design_rules import WeightingRule
rule = WeightingRule(
function="linear",
min_length=0.0,
max_length=0.2,
min_value=1,
max_value=10,
)
Parameter |
Type |
Description |
|---|---|---|
|
|
Shape of the cost curve. One of: |
|
|
Distance (m) at which the rule starts applying. Typically |
|
|
Distance (m) at which the rule stops applying. Beyond this, the rule has no effect. |
|
|
Cost multiplier at |
|
|
Cost multiplier at |
Cost function shapes:
"linear": cost = min_value + (max_value - min_value) × (d / max_length)"sqrt": cost increases with the square root of distance (gentler than linear)"constant": cost = min_value everywhere in the range
Attach to: RuledVolume.design_rules
TurnPenalizer#
Adds a fixed cost every time the path changes direction.
from routing.core.design_rules import TurnPenalizer
rule = TurnPenalizer(cost=3.0)
Parameter |
Type |
Description |
|---|---|---|
|
|
Cost penalty per turn, in units of a single straight-voxel step. Default: |
The penalty is zero when the direction is unchanged and cost when the direction
changes (any angle). Increase this to produce longer but straighter paths.
Attach to: CadMap.from_project_inputs(design_rules=[...]).
CostRule (base class)#
CostRule is the base class for building custom
per-step cost rules (like TurnPenalizer). Subclass it and override:
compute_cost(self, node, neighbor, vector) → floatCalled for every candidate edge during pathfinding. Return the additional cost of moving from node to neighbor. Return
0.0for no penalty.node/neighbor:SmartGridNodevector: direction tuple(dx, dy, dz)of the candidate move
CostRule.compute_heuristic always returns 0.0 by design — cost rules add
traversal cost rather than heuristic penalties.
Example — reproducing TurnPenalizer’s logic:
from routing.core.design_rules import CostRule
class MyTurnPenalizer(CostRule):
def __init__(self, cost: float = 50.0):
super().__init__()
self.cost = cost
def compute_cost(self, node, neighbor, vector):
if node.direction == vector:
return 0.0 # no direction change, no penalty
return self.cost # any turn costs self.cost
Pass the rule as a global rule to CadMap.from_project_inputs(design_rules=[...]).
DistanceAttributeToggler#
Tags voxels within a distance band with a boolean attribute. Other constraint rules
(e.g. ClampingConstraint) read these attributes to decide where they apply.
from routing.core.design_rules import DistanceAttributeToggler
rule = DistanceAttributeToggler(
attribute="clampable",
min_distance=0.0,
max_distance=0.08,
)
Parameter |
Type |
Description |
|---|---|---|
|
|
Name of the boolean attribute to set. Standard names: |
|
|
Near edge of the distance band (m). |
|
|
Far edge of the distance band (m). Voxels beyond this are not tagged. |
Attach to: RuledVolume.design_rules (same volume as ClampingConstraint).
GravityRule#
Enforces a minimum drainage slope for gravity-drained pipes (drain lines, coolant returns). Paths that slope insufficiently receive a cost penalty.
from routing.core.design_rules import GravityRule
rule = GravityRule(
min_value=0.02,
max_value=0.5,
)
Parameter |
Type |
Description |
|---|---|---|
|
|
Minimum required slope in m/m. Example: |
|
|
Maximum acceptable slope in m/m. Set to |
|
|
Scales the penalty for slope violations. Default: |
Also set PipeType.min_slope to the same value so the router knows which pipe types
require slope enforcement.
Attach to: CadMap.from_project_inputs(design_rules=[...]) for a global requirement.
Only Specify in PipeType: min_slope and/or max_slope for a constraint specific to a PipeType.
StraightLengthRule#
Penalizes turns that occur before a minimum straight run. Use this when your pipe or fitting catalogue requires a certain run-in distance before each bend.
from routing.core.design_rules import StraightLengthRule
rule = StraightLengthRule(
min_value=0.030,
distance_multiplier=2.0,
)
Parameter |
Type |
Description |
|---|---|---|
|
|
Minimum straight segment (m) required before each turn. |
|
|
Scales the penalty cost for violations. Default: |
Also set PipeType.length_before_turn_min to the same value.
Attach to: CadMap.from_project_inputs(design_rules=[...]).
Only Specify in PipeType: length_before_turn_min != 0 for a constraint specific to a PipeType.
ClampingConstraint#
Specifies how pipe clamps are placed along the route. Works with a
DistanceAttributeToggler("clampable", ...) on the same RuledVolume.
from routing.core.design_rules import ClampingConstraint
rule = ClampingConstraint(
clamp_length=0.04,
clamp_step=0.25,
)
Parameter |
Type |
Description |
|---|---|---|
|
|
Distance from the surface (m) at which a clamp sits. Should match the physical clamp bracket depth. |
|
|
Minimum spacing between successive clamps along the pipe (m). |
|
|
Scales the clamping cost contribution. Default: |
Attach to: RuledVolume.design_rules, on the same volume as the
DistanceAttributeToggler("clampable", ...).
ShapedWeightingRule#
Applies weight modifications based on whether a voxel is inside an arbitrary 3D shape (box, cone, sphere, imported solid). Four zone types are available.
from routing.core.design_rules import ShapedWeightingRule
# Attractive
rule = ShapedWeightingRule.attractive_zone(shape=my_solid, strength=0.1)
# Repulsive
rule = ShapedWeightingRule.repulsive_zone(shape=my_solid, strength=10.0)
# Forbidden
rule = ShapedWeightingRule.forbidden_zone(shape=my_solid, cut_ports=False)
# Forced
rule = ShapedWeightingRule.forced_zone(shape=my_solid)
Constructor parameters (direct):
Parameter |
Type |
Description |
|---|---|---|
|
volmdlr |
The 3D shape defining the zone extent. Must support |
|
|
Weight multiplier inside the zone. Meaning depends on |
|
|
One of: |
|
|
For forbidden zones only: if |
|
|
Optional identifier. |
Zone behaviour summary:
|
|
Effect inside the shape |
|---|---|---|
|
0 < coeff < 1 (lower = stronger) |
Weight × coeff — path prefers to enter |
|
coeff > 1 (higher = stronger) |
Weight × coeff — path avoids if possible |
|
ignored |
Weight = 0 — voxels are non-traversable |
|
ignored |
Weight × 1e-16 inside; × 100 outside — path must enter |
Attach to: CadMap.from_project_inputs(design_rules=[...]).
See Tutorial: Shaped Routing Zones for a full tutorial.
PoolingRule#
Controls pipe bundling (grouping pipes of compatible types together). See the dedicated User Guide: Pipe Pooling (Bundling) guide for full details.
from routing.core.design_rules import (
PoolingMode, PoolingSpecification, PoolingRule
)
pooling_specs = [
PoolingSpecification(
types=["TypeA", "TypeB"],
pooling_mode=PoolingMode.strong_pooling(),
min_distance=0.0,
parallel=False,
)
]
rule = PoolingRule(
pooling_specs=pooling_specs,
min_distance=0.0,
distance_multiplier=500,
volume_indices=[0, 1],
)
Parameter |
Type |
Description |
|---|---|---|
|
list of |
Specifies which pipe type pairs are pooled or segregated, and how strongly. |
|
|
Default minimum separation (m) between pipes with no explicit spec. |
|
|
Scales the pooling cost contribution. Default: |
|
list of |
Indices of |
Attach to: CadMap.from_project_inputs(design_rules=[...]).
ConstraintRule (base class)#
ConstraintRule is the base class for building custom
constraint rules. Subclass it to encode any voxel-level constraint not provided by the
built-in rules.
Methods to override:
compute_heuristic(self, node, neighbor, vector)Called at every step during A* to steer the pathfinder in real time. Return
0.0if the move from node to neighbor is allowed; returnself.distance_multiplier(or another penalty) to discourage it.node/neighbor:SmartGridNodevector: direction tuple(dx, dy, dz)of the candidate move
Without this method, pathfinding ignores your constraint; it will find paths that violate it and only detect the violation after the fact.
fast_check(self, voxels, voxel_size, bool_matrix)Called on a complete candidate path during post-routing optimization.
voxelsis a list of(x, y, z)index tuples;bool_matrixis a numpy bool array marking where the rule’s attribute is active. Return0.0if satisfied, or a penalty cost otherwise.
A full custom constraint typically implements both.
from routing.core.design_rules import ConstraintRule
class UserDefinedConstraint(ConstraintRule):
"""Allow routing only through voxels tagged 'user_defined'."""
def __init__(self, name: str = ""):
super().__init__(attribute="user_defined", name=name)
def compute_heuristic(self, node, neighbor, vector):
"""Real-time check: penalize moves toward voxels not tagged 'user_defined'."""
if not neighbor.get_custom_attr(self.attribute):
return self.distance_multiplier
return 0.0
def fast_check(self, voxels, voxel_size, bool_matrix):
"""Post-routing check: penalize any path segment missing the attribute."""
if any(bool_matrix[v] for v in voxels):
return self.distance_multiplier
return 0.0
Note
ConstraintRule.__init__ requires attribute: str as a positional argument.
Every subclass must call super().__init__(attribute="your_attribute_name").
The attribute name must match the DistanceAttributeToggler used on the
same RuledVolume.
Use a DistanceAttributeToggler on the relevant RuledVolume to set the attribute
that your custom rule reads.
See Tutorial: Engineering Design Rules for a working example.