Volume Decomposition#

A part usually reads as material in more than one way. A stepped block is a big block with a corner taken off, and it is equally a small block sitting on a large one. Decomposition returns every such reading rather than choosing between them, which is what makes it useful upstream of feature recognition: the readings are the candidate features.

It runs in two stages. The first cuts the solid into cells; the second grows maximal volumes over them.

This page is the vocabulary and the caveats, for someone working on the package. For the method in pictures and a walkthrough of reading a result, see Decomposition.

Vocabulary#

Extension face (or sheet)

A face’s surface, re-trimmed far beyond the part. It removes nothing — it only partitions. Only faces carrying a concave edge are extended, since a convex edge divides no material.

Cell

A region of the solid left by cutting it with every extension face. Cells tile the solid and need not be convex.

Nucleus

A cell with no face left to grow across. Such a cell can only lie inside a maximal volume, and every maximal volume holds one — which is what bounds how many volumes a part can yield.

Collection

The cells gathered around one nucleus by the growth rule.

Volume

A collection assembled back into one solid.

It is maximal when it meets four conditions, and all four matter:

  1. it lies inside the solid — which holds by construction here, a volume being a union of cells of that solid;

  2. it has no concave edge;

  3. every face of it lies on the same surface as some face of the solid, meeting or overlapping that face rather than merely sharing its surface somewhere else;

  4. no other volume meeting the first three contains it.

The fourth is what makes close_maximality worth having: two collections can each meet the first three while their union does too, and then only the union is maximal.

Localisation

What bounds an extension face. "material" bounds it by the sheets of the neighbours its face meets, which is the published construction and the default. "none" bounds it by nothing but the part’s own extent, and exists to measure the first against.

Concavity

Which reentrant junctions are read as concave, and so ask for a cut. "all" takes every one the adjacency graph reports; "sharp" takes only those where the two faces meet at an angle. Tangent junctions are what a removed blend leaves behind, and cutting there divides material the part does not divide, so "sharp" is the default.

Verdict

What a grown volume still is: how many concave edges it has, how many of those growth could never remove, and which surfaces the removable ones lie on.

Stuck nucleus

A nucleus that reached no returned volume — its cells would not assemble, or its volume was discarded as held inside another. Reported rather than dropped.

Using it#

from volmdlr_tools.decomposition import Decomposer

part = Decomposer.from_step("part.step")
print(part.summary())

for volume, cells in zip(part.volumes, part.decomposition.volume_cells):
    print(volume.volume(), "from cells", sorted(cells))

print(part.faces_of_volume(0))     # where volume 0 touches the part
print(part.volumes_of_face(12))    # which volumes reach that face

The decomposition is made on the first question and kept, so a part is never decomposed twice by accident. decompose() is the same run as one value for a caller with nothing to ask, and decompose_into_cells() is the cutting stage on its own.

Reading the result#

Both stages report what they could not do rather than failing quietly, and the fields that say so are worth checking before trusting a count.

CellDecomposition

dropped_face_ids names faces whose extension is not in the cut. uncut_face_ids names extensions that were imprinted but kept no piece, so the cut their concave edges asked for is missing. unbounded_face_ids names extensions selected without all of their bounds, which cut more than was asked. unpaired_interior_faces above zero means the partition has a gap: a cut interior to the solid has material on both sides, so a single cell beside one is a hole.

Decomposition

covered_fraction and uncovered_cells say how much of the part the volumes explain, and answer a different question from stuck_nuclei: a region holding no nucleus lies in no volume however plainly it is one, and the stuck count stays at zero while it does. concave_volumes names volumes returned with a concave edge still on them — the method could not separate those, and they are reported rather than dropped so their material stays in the reading.

Overlap is by design. The volumes are readings, not a partition, and their volumes sum to more than the part.

Two knobs worth knowing#

localisation="none" gives the unlocalised reading — every extension bounded only by the part. It produces far more cells and exists so the default can be measured against something; it is not a way anyone should decompose.

close_maximality=True merges two returned volumes wherever their union still has no concave edge. The definition maximality serves says a volume contained in another valid volume is not maximal, and two nuclei can each reach a valid collection whose union is valid too — so only the union is maximal, and nothing else would find it. It is off by default because the pass is quadratic in the volumes and every pair costs an assembly.

Running the tests#

pytest tests/decomposition

The suite includes a corpus of benchmark solids. Cell and volume counts there are recorded as ceilings rather than pinned: they move whenever the extensions are bounded differently, which is a design change and not a regression. What is asserted outright is what granularity cannot move — the cells add back up to the solid, no interior face is left with one cell beside it, and the caller’s own solid is never written to.