routing.core.voxels#

Routing voxelizations: matrix-backed 3D grids with world/index conversions, morphology and components.

class routing.core.voxels.RoomVoxelization(primitives: list[Primitive3D], voxel_matrix: NDArray[bool], voxel_size: float, voxel_origin: tuple[float, float, float], name: str = '')[source]#

Bases: RoutingVoxelization

Deprecated: use RoutingVoxelization, which merges this class with VoxelVolume.

Construction shim only. This class keeps its historical argument order, which no longer matches the positional layout MatrixBasedVoxelization rebuilds instances with, so operations that reconstruct — extract_connected_matrix_based_voxelizations, crop_matrix, the boolean operators — must be called on a RoutingVoxelization instead.

class routing.core.voxels.RoutingVoxelization(matrix: ndarray, origin: ndarray | tuple[float, float, float], voxel_size: float, name: str = '', primitives: list[Primitive3D] | None = None)[source]#

Bases: MatrixBasedVoxelization

A class representing a 3D boolean grid of voxels, placed in space by an origin and a voxel size.

Carries the world/index conversions, the morphology operators and the connected components that every routing stage needs. True marks the voxels the grid owns: matter for a voxelized shape, free space for a carved room. Which of the two it means comes from the factory that built it, not from the type.

Parameters:
  • matrix – The 3D numpy boolean array where True marks an owned voxel.

  • origin – The centre of voxel (0, 0, 0) in global coordinates.

  • voxel_size – The edge length of a voxel, in the same unit as the primitives.

  • name – The name to identify this voxelization. Defaults to ‘’.

  • primitives – The CAD view; None defers it to first access, [] states there is none.

apply_closing(radius: int) → RoutingVoxelization[source]#

Dilation then erosion. Fills small holes and gaps.

apply_dilation(radius: int) → RoutingVoxelization[source]#

Expand all occupied regions outward by radius voxels.

apply_erosion(radius: int) → RoutingVoxelization[source]#

Shrink all occupied regions inward by radius voxels.

apply_opening(radius: int) → RoutingVoxelization[source]#

Erosion then dilation. Removes small protrusions and noise.

biggest_component() → RoutingVoxelization[source]#

Get the connected component holding the most voxels.

property border_centers: ndarray#

World centres of the border voxels, as an (N, 3) array.

border_contact_pairs(other: RoutingVoxelization) → ndarray[source]#

Pairs of border-voxel centres of the two grids lying within one voxel step of each other.

Returns:

Array of shape (N, 6) where each row is [x1, y1, z1, x2, y2, z2].

property border_indices: ndarray#

Indices of the border voxels — those not fully surrounded by matter, as an (N, 3) array.

cleaned(opening_radius: int = 0, closing_radius: int = 0, erosion_radius: int = 0) → RoutingVoxelization[source]#

Apply opening, then closing, then erosion; self unchanged when all radii are 0 (no allocation).

clip_coord(coordinates: ndarray) → ndarray[source]#

Clip a point onto the closest voxel centre inside the grid.

component_carrying(point: ndarray) → RoutingVoxelization[source]#

Get the connected component occupied at point exactly; the whole voxelization, logged, when none is.

The strict sibling of component_containing(): no quantization slack, and a point on no component falls back to the whole voxelization instead of raising.

component_containing(point: ndarray, tolerance_voxels: int = 2) → RoutingVoxelization[source]#

Return the connected component carrying the occupied voxel nearest to point.

Parameters:
  • point – The world position to seed the search from.

  • tolerance_voxels – How far point may penetrate the carved material (voxel-quantization slack); beyond that it is genuinely unroutable and this raises rather than selecting a wrong domain.

component_labels(components: list[RoutingVoxelization]) → ndarray[source]#

Label each voxel of this grid with the index of the component covering it, -1 elsewhere.

Parameters:

components – Voxelizations lying on this grid, typically its connected components.

property coords: ndarray#

world_coords() transposed to (3, n_voxels).

copy(deep: bool = True, memo: dict | None = None) → RoutingVoxelization[source]#

Deep copy of the voxel matrix; origin and voxel_size are shared (immutable).

Parameters:
  • deep – If False, perform a shallow copy. If True, perform a deep copy.

  • memo – A dict that keep track of references.

static covering_frame(bounds: ndarray, voxel_size: float, dilation_voxels: int, origin: ndarray | None = None) → VoxelFrame[source]#

Frame of the grid covering bounds, padded to survive a dilation_voxels dilation.

Parameters:
  • bounds – World points the grid must contain, as any array of (x, y, z) rows.

  • voxel_size – The edge length of a voxel.

  • dilation_voxels – The dilation the padding must leave room for.

  • origin – Anchor the grid on an existing one; None snaps a fresh origin onto whole voxel steps, so repeated round-trips do not accumulate sub-voxel drift.

crossed_indices(start_position: ndarray, end_position: ndarray) → ndarray[source]#

Return the voxel indices crossed by the [start, end] segment (Bresenham), dropping out-of-grid ones.

display(title: str = '', overlay_prims: list[Primitive3D] | None = None, decimation: int = 0, color: tuple[float, float, float] = (0.5, 0.5, 0.5), alpha: float = 0.4) → None[source]#

Visualize this volume as small box primitives, with optional overlay.

classmethod enclosing(routing_shapes: list[RoutingShape], voxel_size: float, dilation_voxels: int) → RoutingVoxelization[source]#

Voxelize routing_shapes on a fresh grid auto-sized to enclose them.

filled_with(matrix: ndarray) → RoutingVoxelization[source]#

Get the same grid under the same name, carrying matrix as its matter.

property frame: VoxelFrame#

The chart this grid is sampled on.

classmethod from_routing_shapes(routing_shapes: list[RoutingShape], frame: VoxelFrame, dilation_voxels: int) → RoutingVoxelization[source]#

Let every shape write itself into one shared frame grid, each touching only the cells it occupies.

classmethod from_voxels(voxels: MatrixBasedVoxelization, build_cad: bool = False) → RoutingVoxelization[source]#

Adopt an existing voxelization, keeping its origin, its voxel size, its matter and its name.

Parameters:
  • voxels – The voxelization to adopt.

  • build_cad – Rebuild the CAD view now; otherwise the result states it has none, rather than deferring an expensive mesh build to the first attribute access.

property homogeneous_indices: ndarray#

indices with a row of ones appended, as a (4, n_voxels) array.

homogeneous_indices_in_box(lower: ndarray, upper: ndarray) → ndarray[source]#

homogeneous_indices restricted to the [lower, upper) index box, computed on that window only.

property indices: ndarray#

voxel_indices transposed to (3, n_voxels).

indices_are_active(indices: list[tuple[int, int, int]] | ndarray) → bool[source]#

Whether every index in indices is in-grid and occupied.

indices_to_world(indices: ndarray) → ndarray[source]#

Convert integer or fractional voxel indices, (3,) or (N, 3), to world coordinates.

is_adjacent_to(other: RoutingVoxelization) → bool[source]#

Whether the two voxelizations touch: some pair of border voxels lies within one voxel step.

labelled_components() → tuple[ndarray, int][source]#

Label each voxel with the index of its connected component, -1 off matter, and count the components.

The 6-connectivity and the numbering of extract_connected_matrix_based_voxelizations(), without materialising one voxelization per component.

maximal_boxes(min_fill: float = 1.0) → list[dict[str, tuple[int, int]]][source]#

Greedy cover of the occupied voxels by maximal index boxes, each box carved out as it is taken.

Every seed is grown along the six axis orders and the largest box wins, so a cluttered room yields fewer, fatter boxes — which is the point: a SmartGrid allocates its node container over the whole box, so a box that is all matter wastes nothing.

Parameters:

min_fill – Smallest share of free matter in every slab a box grows by: 1 keeps the boxes all matter; below, a box takes in some empty space and the room splits into fewer boxes. A box never takes a cell of another, empty or not, so the boxes never overlap.

nearest_active_center(point: ndarray, tolerance_voxels: int | None) → ndarray[source]#

Centre of the occupied voxel nearest to point.

Parameters:
  • point – The world position to search from.

  • tolerance_voxels – Search budget: the nearest centre within a ±tolerance_voxels window, raising beyond it. None removes the budget — the window grows until the nearest occupied voxel of the whole grid is found (raising only on an empty grid).

nesting_in(coarser: RoutingVoxelization) → tuple[int, ndarray][source]#

Get the two numbers mapping a coarser voxel onto the block of this grid’s voxels it covers.

Together they are the conversion: the block covering coarse voxel (i, j, k) starts at offset + (i, j, k) * ratio here and spans ratio voxels per axis. The multi-scale counterpart of offset_in(), which needs only the offset because both grids share a voxel size.

A coarser voxel must cover a whole block and sit centred on it, so ratio and offset both have to land on integers; anything else means the two grids were not built from one another and the caller is about to read the wrong voxels.

Parameters:

coarser – The grid this one nests into, of a larger voxel size.

Returns:

The size ratio, and the index here at which coarser’s first voxel starts.

offset_in(parent: RoutingVoxelization) → ndarray[source]#

Integer index offset of this grid’s origin inside parent.

property origin: ndarray#

Grid origin as ndarray (alias for min_grid_center).

property primitives: list[Primitive3D]#

CAD primitives reconstructed from voxels, computed on first access.

refined_box(bounds: dict[str, tuple[int, int]], factor: int) → RoutingVoxelization[source]#

Build a fully-occupied voxelization, factor times finer, covering the inclusive index box bounds.

Parameters:
  • bounds – Per-axis inclusive (lowest, highest) indices on this grid.

  • factor – How many fine voxels a voxel of this grid spans per axis.

restrict_field(parent: RoutingVoxelization, field: NDArray[float64]) → NDArray[float64][source]#

Sample a field defined on parent’s grid over this sub-grid: parent values on occupied voxels, inf elsewhere.

self must lie on parent’s grid — the relation intersection() with parent produces (same voxel size, integer offset). Occupied voxels of self are then a subset of parent’s, so each maps to a finite field entry; the inf fill only reaches unoccupied voxels, which carry no value by convention.

sample_segment(start_position: ndarray, end_position: ndarray, samples_per_voxel: float = 2.0) → ndarray[source]#

Sample the [start, end] segment at samples_per_voxel density (>= 2 samples, endpoints included).

set_distances_direct(distances_array: ndarray) → ndarray[source]#

Scatter one distance per occupied voxel into a full grid, inf on the unoccupied ones.

property shape: tuple#

Voxel-grid dimensions (i, j, k).

sub_voxelization(bounds: dict[str, tuple[int, int]], copy: bool = True) → RoutingVoxelization[source]#

Extract the voxelization spanning the inclusive index box bounds, re-anchored on its own origin.

Parameters:
  • bounds – Per-axis inclusive (lowest, highest) indices.

  • copy – Copy the extracted block. False shares memory with this grid’s matrix — cheaper for a short-lived window, but it keeps the whole parent matrix alive.

subtract_coarser(coarser: RoutingVoxelization) → RoutingVoxelization[source]#

Clear every voxel covered by an occupied voxel of coarser — a multi-scale boolean difference.

Each occupied coarse voxel clears the whole block of this grid’s voxels it covers, following the block mapping of nesting_in().

to_graph() → Graph[source]#

26-connectivity graph over the occupied voxels.

to_matrix_based_voxelization() → MatrixBasedVoxelization[source]#

Downcast to the plain volmdlr voxelization, dropping every routing-side attribute.

touching_border_pairs(other: RoutingVoxelization) → ndarray[source]#

Pairs of border voxels of the two grids lying within one voxel step of each other.

The predicate of is_adjacent_to(), evaluated for every pair at once: grids whose bounding boxes are apart touch nowhere, and otherwise each side’s border is first reduced to the shell the other side’s border can reach (_border_facing()), so the distance search only ever sees the contact, whatever the size of the grids.

Returns:

Array of shape (K, 2): rows of self.border_indices and of other.border_indices.

voxel_boxes(centers: ndarray | None = None, scale: float = 1.0, color: tuple[float, float, float] = (0.5, 0.5, 0.5), alpha: float = 0.8, decimation: int = 1) → list[Solid][source]#

Render voxels as cube solids of side scale * voxel_size — the inspection view of the grid.

Distinct from the inherited volmdlr_primitives(), which returns one surface mesh for the whole volume: here each voxel stays a cube, so the discretization itself is visible.

Parameters:
  • centers – World centres to draw; None draws this volume’s own occupied voxels.

  • scale – Cube side as a multiple of the voxel size, to make markers stand out.

  • decimation – Keep one centre every decimation, to stay under a viewable count.

property voxel_count: int#

Number of occupied voxels.

property voxel_origin: tuple[float, float, float]#

Grid origin as tuple (alias for min_grid_center).

within_bounding_box(bounding_box: BoundingBox) → RoutingVoxelization | None[source]#

Keep the occupied voxels whose centres fall inside bounding_box, cropped to them; None when empty.

world_coords() → ndarray[source]#

World coordinates of the occupied voxel centres, as an (n_voxels, 3) array.

world_to_index(point: tuple[float, float, float] | ndarray) → tuple[int, int, int][source]#

Index of the single voxel containing point, as a plain int tuple.

world_to_indices(points: ndarray) → ndarray[source]#

Convert world coordinates, (3,) or (N, 3), to the indices of the voxels containing them.

class routing.core.voxels.VoxelVolume(*args, **kwargs)[source]#

Bases: RoutingVoxelization

Deprecated: use RoutingVoxelization, which merges this class with RoomVoxelization.