Tutorial: Routing Inside a Closed Volume#

By default a RuledVolume voxelizes only the shell of a solid: the volume acts as an obstacle and pipes are routed around it. Sometimes you want the opposite — to route a pipe through the interior of a closed body, such as a chamber, a duct or an enclosure. Setting voxel_inside=True fills the enclosed voxels so the solver can route inside the solid.

This tutorial contrasts the two modes on a single cube: one route outside it, one route through it. The runnable version is scripts/simple_test_cases/cube_case.py.

The voxel_inside Flag#

from_volume_model() accepts a voxel_inside flag:

voxel_inside

Behavior

False (default)

Only the solid’s surface is voxelized. The body is an obstacle; pipes route around it.

True

Enclosed voxels are filled. The interior becomes routable space; pipes can pass through.

Shared Setup#

Both runs use the same volume model, weighting rule and design rules:

import os
from pathlib import Path

from volmdlr.model import VolumeModel

from routing.core.cadmap import CadMap
from routing.core.core import PipeType, Port, RuledVolume, Specification
from routing.core.design_rules import PoolingRule, TurnPenalizer, WeightingRule
from routing.core.finders import SmartFinder
from routing.core.legacy_piping import Section
from routing.core.route_planner import RoutePlanner

current_folder = Path(__file__).parent
voxel_size = 0.005
volume_model = VolumeModel.from_json(
    os.path.join(current_folder, "../../data/json/cubic_volume_model.json")
)

weighting_rule = WeightingRule(
    function="linear", min_length=0.0, max_length=1.0, min_value=1, max_value=10
)
pooling_rule = PoolingRule(
    pooling_specs=[], min_distance=0.0, distance_multiplier=500,
    volume_indices=[], name="pooling_rule",
)
design_rules = [TurnPenalizer(cost=3.0), pooling_rule]

pipe_type = PipeType(section=Section(0.005, name="section"), name="test_pipe",
                     color=(0, 0.8, 0.8))

Routing Outside the Cube (default)#

With ports placed just outside the cube and the volume left as an obstacle (voxel_inside defaults to False), the pipe routes around the body:

ruled_volume = RuledVolume.from_volume_model(volume_model, [weighting_rule],
                                             name="cubic_volume")

input_port = Port(coordinates=(0.11, 0, 0), direction=(1, 0, 0), length=0.02)
output_port = Port(coordinates=(-0.11, 0, 0), direction=(-1, 0, 0), length=0.02)
specifications = [Specification(input_port, output_port, pipe_type, "test")]

ports_prims = [input_port.volmdlr_primitives(radius=0.01),
               output_port.volmdlr_primitives(radius=0.01)]
ports_model = RuledVolume.from_volume_model(VolumeModel(ports_prims), [], name="ports")
bounding_box = CadMap.build_bounding_volume(
    [ruled_volume, ports_model], [], voxel_size, voxel_margin=30
)

cadmap = CadMap.from_project_inputs(
    design_rules=design_rules, ruled_volumes=[ruled_volume],
    bounding_box=bounding_box, voxel_size=voxel_size, exact_distances=True,
    weights_operator="min_distance", build_cad=False,
)

finder = SmartFinder("AStar", "manhattan", "never")
routing_result = RoutePlanner(cadmap, finder).generate(specifications, 0, False)
routing_result.display_3d()

Routing Through the Cube (voxel_inside=True)#

Now place the ports inside the cube and rebuild the RuledVolume with voxel_inside=True so the interior is routable. Note the voxel_margin=0 — the route stays within the body:

input_port = Port(coordinates=(0.1, 0, 0), direction=(-1, 0, 0), length=0.005)
output_port = Port(coordinates=(-0.1, 0, 0), direction=(1, 0, 0), length=0.005)
specifications = [Specification(input_port, output_port, pipe_type, "test")]

ports_prims = [input_port.volmdlr_primitives(radius=0.01),
               output_port.volmdlr_primitives(radius=0.01)]
ports_model = RuledVolume.from_volume_model(VolumeModel(ports_prims), [], name="ports")
bounding_box = CadMap.build_bounding_volume(
    [ruled_volume, ports_model], [], voxel_size, voxel_margin=0
)

ruled_volume = RuledVolume.from_volume_model(
    volume_model, [weighting_rule], voxel_inside=True, name=""
)

cadmap = CadMap.from_project_inputs(
    design_rules=design_rules, ruled_volumes=[ruled_volume],
    bounding_box=bounding_box, voxel_size=voxel_size, exact_distances=True,
    weights_operator="min_distance", build_cad=True,
)

finder = SmartFinder("AStar", "manhattan", "never")
routing_result = RoutePlanner(cadmap, finder).generate(specifications, 0, False)
routing_result.display_3d()

The only change that switches outside vs inside routing is the voxel_inside flag on from_volume_model (together with placing the ports inside the body and using a zero voxel margin).

Next Steps#