Profile-driven (thin-ribs: 76% of the improvement in the topological operations, most of it in face removal walking whole edge rings to find one neighbour; pinhole-block: 44% in the smoothing passes). Same results, less work: - face removal finds the sandwiched face across a ring edge through the tetrahedron it already holds (O(1) instead of an edge ring), keeps the candidate tetrahedra of ring edges between rounds and updates the removed worst incrementally; - Klincsek's table for edge removal starts at the quality to beat, so triangles that cannot help are never evaluated; - a change clock per vertex: a topological operation (per tetrahedron, per edge, per face and side) or a smoothing that failed is not tried again until a tetrahedron it reads changes (exact: the operations are functions of that region; checked by re-running them); - the quality of a live tetrahedron is cached per slot until one of its vertices is touched; - smoothing line searches stop at the first tetrahedron that does not beat the worst, and gradients are computed only near the worst; - the journal saves the free list's tail instead of the whole list. For the clock to be exact, the quality is the same bits for every listing of a tetrahedron with the same orientation (corners in a canonical even order), and a closed edge ring starts at its smallest vertex. Changed results: - smoothing uses the analytic gradient of the smallest dihedral sine (Klingner and Shewchuk 2007 §3.1; Freitag and Ollivier-Gooch 1997) instead of central differences (kept for flat or inverted tetrahedra); - the composite insertion runs its topological passes on the new vertex's tetrahedra below the threshold, and smooths the neighbours whose star has one, as the passes do. Tests: analytic gradient against central differences, quality the same for every even reordering, cached qualities equal fresh ones after improvement, rollbacks restore every slot; the washer fingerprint moves (the same on arm64 and x86-64). Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
17 KiB
The meshing skill on the corpus and the samples
Generated by cargo xtask skill-results; do not edit. Each part is meshed by following
the meshing skill with today's commands, headless, as
an agent over MCP would (crates/testkit/src/skill.rs): geometry.bodies, geometry.features,
mesh.plan and geometry.check at the plan's size, mesh.plan.apply (which meshes one cell of a
mirror-symmetric body and mirrors it), and where the plan fails the skill's fallbacks in order,
mesh.hex.generate, mesh.hex.project (sphere template) and then mesh.tet.generate (tet10);
the cells of a split part are joined with mesh.node.merge; then mesh.quality.mark at the
skill's limits (scaled Jacobian ≥ 0.3, edge ratio ≤ 10), mesh.node.relax round each marked
element when at most 20 are marked (more is a clustered failure: the blocks are wrong, and relaxing
is not the fix), mesh.quality.mark again, mesh.quality and mesh.check.duplicates. Sizes are
the plan's (1/25 of the body's diagonal, 2 layers through a shell).
Columns: method is what meshed the part (plan (project|cells|sweep) is the skill's target,
anything else a fallback); symmetry the mirror planes mesh.plan.apply used; volume error the
mesh's volume against the geometry's; untagged faces B-rep faces no element face is tagged
with (a set on them finds nothing); marked the elements failing the limits before and after
relaxing, and what relaxing changed; ¹ a tet mesh has no edge ratio: its largest aspect ratio
stands in; duplicates coincident nodes and cracks; geometry.check its findings at the size;
verdict pass when the minimum scaled Jacobian and the largest edge ratio meet the limits and
the volume is within 1 % (pass (fallback) when a fallback meshed it).
The section pictures cut each part through the middle of its longest side, marked elements red.
Times are wall clock on the machine that ran it.
Results
| part | method | symmetry | elements | min SJ | mean SJ | max edge ratio¹ | volume error | untagged faces | marked → after relax | duplicates | geometry.check | time s | verdict | section |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| bent-tube | plan (sweep) | – | 22272 (22272 hex8) | 0.998 | 0.998 | 1.4 | -2.8e-3 | 0 | 0 (not relaxed) | none | – | 3.7 | pass | y=37.500 |
| bolted-assembly | plan (sweep), mesh.hex.generate (auto) | – | 6272 (6272 hex8) | 0.583 | 0.939 | 3.6 | 2.0e-4 | 0 | 0 (not relaxed) | 8 coincident, 0 cracks | – | 3.7 | pass | x=50.000 |
| cantilever-i-beam | plan (sweep) | – | 61600 (61600 hex8) | 1.000 | 1.000 | 1.0 | 0.0e0 | 0 | 0 (not relaxed) | none | – | 8.5 | pass | x=100.000 |
| cross-cylinders | none | – | – | – | – | – | – | – | – | – | – | 27.2 | no mesh | – |
| filleted-bracket | tet10 | – | 20020 (20020 tet10) | 0.320 | 0.562 | 4.1 | -6.8e-6 | 0 | 0 (not relaxed) | none | – | 12.7 | pass (fallback) | x=30.000 |
| filleted-bracket-blocks | plan (sweep) | – | 75410 (75410 hex8) | 0.704 | 0.954 | 3.5 | 3.7e-5 | 0 | 0 (not relaxed) | none (after merging 2348 nodes) | – | 6.9 | pass | x=30.000 |
| i-beam | plan (sweep) | – | 61600 (61600 hex8) | 1.000 | 1.000 | 1.0 | 0.0e0 | 0 | 0 (not relaxed) | none | – | 4.5 | pass | z=100.000 |
| loft-transition | plan (sweep) | – | 2873 (2873 hex8) | 0.189 | 0.959 | 1.7 | -2.1e-3 | 0 | 4 → 4 (4 relaxed, 0 undone; min SJ 0.189 → 0.189, max ratio 1.7 → 1.7) | none | – | 0.9 | fail (SJ) | z=25.000 |
| perforated-plate | plan (project) | 2 plane(s) | 8576 (8576 hex8) | 0.707 | 0.888 | 3.0 | 7.7e-4 | 0 | 0 (not relaxed) | none | – | 9.5 | pass | x=60.000 |
| pinhole-block | plan (project) | 2 plane(s) | 40768 (40768 hex8) | 0.707 | 0.984 | 8.0 | 8.1e-5 | 0 | 0 (not relaxed) | none | – | 25.5 | pass | y=25.000 |
| rounded-box | plan (project) | 2 plane(s) | 11520 (11520 hex8) | 0.717 | 0.973 | 4.0 | -4.1e-3 | 0 | 0 (not relaxed) | none | – | 8.7 | pass | x=20.000 |
| shallow-groove | plan (sweep) | – | 1960 (1960 hex8) | 0.991 | 0.999 | 1.1 | 2.7e-4 | 0 | 0 (not relaxed) | none | – | 0.2 | pass | x=30.000 |
| sharp-wedge | plan (sweep) | – | 1674 (1674 hex8) | 0.014 | 0.301 | 149.6 | 2.7e-15 | 0 | 1524 (not relaxed) | none | – | 0.2 | fail (SJ, ratio) | x=40.000 |
| sphere-on-plate | tet10 | – | 24175 (24175 tet10) | 0.341 | 0.604 | 4.2 | -9.6e-5 | 0 | 0 (not relaxed) | none | – | 6.5 | pass (fallback) | y=0.000 |
| spline-vase | plan (sweep) | – | 4536 (4536 hex8) | 0.665 | 0.945 | 9.9 | -6.6e-3 | 0 | 0 (not relaxed) | none | – | 2.3 | pass | z=-35.000 |
| stepped-block | plan (cells) | – | 1416 (1416 hex8) | 1.000 | 1.000 | 1.3 | -4.8e-15 | 0 | 0 (not relaxed) | none | – | 1.6 | pass | x=30.000 |
| thin-ribs | plan (sweep) | – | 301308 (301308 hex8) | 0.999 | 1.000 | 1.3 | -3.4e-12 | 0 | 0 (not relaxed) | none | – | 34.7 | pass | x=50.000 |
| thin-wall-enclosure | plan (sweep) | – | 124512 (124512 hex8) | 1.000 | 1.000 | 1.0 | 0.0e0 | 0 | 0 (not relaxed) | none | – | 13.5 | pass | x=40.000 |
| analytic-sphere | mesh.hex.project (sphere) | – | 3400 (3400 hex8) | 0.728 | 0.968 | 3.3 | -1.1e-2 | 0 | 0 (not relaxed) | none | – | 8.8 | fail (volume) | z=0.000 |
| analytic-torus | plan (sweep) | – | 48360 (48360 hex8) | 0.837 | 0.986 | 2.9 | -4.2e-3 | 0 | 0 (not relaxed) | none | – | 9.8 | pass | x=0.000 |
| analytic-cylinder | plan (project) | – | 4347 (4347 hex8) | 0.859 | 0.985 | 2.4 | -4.9e-3 | 0 | 0 (not relaxed) | none | – | 7.3 | pass | z=20.000 |
| analytic-pipe | plan (project) | – | 2244 (2244 hex8) | 0.997 | 0.997 | 1.3 | -3.3e-3 | 0 | 0 (not relaxed) | none | – | 3.6 | pass | z=30.000 |
| analytic-plate-hole | plan (project) | – | 5856 (5856 hex8) | 0.937 | 0.996 | 3.0 | 4.3e-5 | 0 | 0 (not relaxed) | none | – | 4.7 | pass | x=100.000 |
| crank-arm | plan (project) | – | 4014 (4014 hex8) | 0.578 | 0.933 | 5.8 | -8.4e-4 | 0 | 0 (not relaxed) | none | 4 short_edge, 2 sliver_face | 21.7 | pass | x=81.000 |
| upright-bracket | plan (sweep) | – | 1364 (1364 hex8) | 0.707 | 0.968 | 3.1 | 2.1e-4 | 0 | 0 (not relaxed) | none | – | 0.1 | pass | x=30.000 |
| pi5-base | plan (project) | 1 plane(s) | 29852 (29852 hex8) | 0.683 | 0.969 | 4.9 | -3.0e-3 | 0 | 0 (not relaxed) | none | 8 short_edge, 4 sliver_face | 131.2 | pass | x=56.000 |
| pi5-lid | plan (project) | – | 5130 (5130 hex8) | 0.724 | 0.868 | 3.9 | -2.9e-4 | 0 | 0 (not relaxed) | none | – | 10.5 | pass | x=56.000 |
| cup | plan (project) | – | 1258 (1258 hex8) | 0.702 | 0.929 | 5.0 | -3.5e-3 | 0 | 0 (not relaxed) | none | – | 3.0 | pass | x=5.000 |
| plain-cup | mesh.hex.generate (auto) | – | 2000 (2000 hex8) | 0.820 | 0.995 | 4.7 | -2.3e-3 | 0 | 0 (not relaxed) | none | – | 1.3 | pass (fallback) | x=0.000 |
Why a part fails
Each earlier method's error, in the order the skill tries them, and for a part that fails the limits the cause read from its numbers and section picture.
bolted-assembly(corpus: A base plate with a clamp block component placed twice, once turned 90 degrees: bodies placed by component transforms.):- Clamp body: mesh.plan.apply: mesh plan
Plan-Clamp_body: the projected layout does not mesh: invalid input: node 3076 at [11.000000000000004, 10.0, 5.0] lies on the edge between nodes 2640 and 2715: new elements must meet the mesh face to face, with the same divisions… - Clamp body (Clamp:2): mesh.plan.apply: mesh plan
Plan-Clamp_body__Clamp_2_: the projected layout does not mesh: invalid input: node 5884 at [66.42857142857143, 5.000000000000001, 5.0] lies on the edge between nodes 984 and 1059: new elements must meet the mesh face to face, wi…
- Clamp body: mesh.plan.apply: mesh plan
cross-cylinders(corpus: Two orthogonal R10 cylinders joined: equal radii, so the intersection curves cross at two singular points.):- Cross: mesh.plan.apply: mesh plan
Plan-Cross: the cells ["Cross"] (cuts []) do not mesh all-hex: bodyCross: invalid boundary surface: 28 defects; triangles 142 and 254 are oriented inconsistently across edge 0–396 at (-15.488656021315903, 4.577998041166734, 7… - Cross: mesh.hex.generate: body
Cross: invalid boundary surface: 28 defects; triangles 146 and 258 are oriented inconsistently across edge 0–402 at (-15.488656021315903, 4.577998041166734, 7.853092194999726); triangles 255 and 258 are oriented inconsistently across… - Cross: mesh.hex.project (sphere): body
Cross: invalid boundary surface: 28 defects; triangles 146 and 258 are oriented inconsistently across edge 0–402 at (-15.488656021315903, 4.577998041166734, 7.853092194999726); triangles 255 and 258 are oriented inconsistently across… - Cross: mesh.tet.generate: invalid boundary surface: 28 defects; triangles 76 and 78 are oriented inconsistently across edge 9–230 at (-18.75000000000002, 5.634596901641276, 7.632255089815496); triangles 77 and 78 are oriented inconsistently across edge 9–232 at (-15…
- no mesh: the boundary tessellation of the joined cylinders is invalid (inconsistent orientation at the two singular points), so every mesher refuses the body.
- Cross: mesh.plan.apply: mesh plan
filleted-bracket(corpus: An 8 mm L-bracket with an R6 inside fillet and a hole through each leg: a blend beside holes.):- Bracket: mesh.plan.apply: mesh plan
Plan-Bracket: the cells ["Bracket"] (cuts []) do not mesh all-hex: bodyBracket: not supported yet: no structured hex method fits this body (extrude: cylindrical face 8 is not parallel to the direction; sweep: from face 1: lin… - Bracket: mesh.hex.generate: body
Bracket: not supported yet: no structured hex method fits this body (extrude: cylindrical face 8 is not parallel to the direction; sweep: from face 1: linking face 6 has 2 boundary loops (periodic or holed linking faces are not swept… - Bracket: mesh.hex.project (sphere): body
Bracket: meshing failed: 91 of 5104 elements are inverted, twisted or degenerate after projection (certified Jacobian; smallest corner scaled Jacobian -0.760): block 1 cell [3, 3, 0] near (12.519, 11.405, 1.604); block 1 cell [3, 4, …
- Bracket: mesh.plan.apply: mesh plan
loft-transition(corpus: A 40 mm square lofted to an R15 circle 50 mm above: a ruled free-form surface between unlike sections.):- fail (SJ): the lofted sides are swept one to one, and the elements where the section changes shape are skewed (min SJ 0.19). Relaxing the four marked elements did not raise it: the blocks are wrong, not the nodes (§6.3). A loft has no template yet (
unmapped).
- fail (SJ): the lofted sides are swept one to one, and the elements where the section changes shape are skewed (min SJ 0.19). Relaxing the four marked elements did not raise it: the blocks are wrong, not the nodes (§6.3). A loft has no template yet (
sharp-wedge(corpus: A wedge whose faces meet at under 3 degrees: a sharp edge with a needle-thin cross-section.):- fail (SJ, ratio): the wedge's sharp edge is swept as hex, so every element tapers to it (min SJ 0.01, edge ratio 150). A column of wedge6 at the edge (§4) raises the SJ but not the ratio: any element with a corner on a 2.9° edge has sides at least 1/tan 2.9° ≈ 20 times its width there, twice the limit. The edge has to be blunted (defeatured) or the limit waived along it.
sphere-on-plate(corpus: A sphere sunk 0.1 mm into a plate: the faces meet at under 6 degrees around a 2 mm circle.):- Plate: mesh.plan.apply: mesh plan
Plan-Plate: the cells ["Plate"] (cuts []) do not mesh all-hex: bodyPlate: not supported yet: no structured hex method fits this body (extrude: face 6 is curved across the direction; sweep: from face 0: 6 faces are not on the … - Plate: mesh.hex.generate: body
Plate: not supported yet: no structured hex method fits this body (extrude: face 6 is curved across the direction; sweep: from face 0: 6 faces are not on the sweep path; from face 5: linking face 11 has 2 boundary loops (periodic or … - Plate: mesh.hex.project (sphere): body
Plate: meshing failed: the mesh volume 32917.42909007507 is far from the body's 69353.30815403847: the blocks do not cover the part (every outer block face must stand for part of its surface)
- Plate: mesh.plan.apply: mesh plan
analytic-sphere(corpus: Sphere r 20: curvature everywhere, no edges.):- Sphere: mesh.plan.apply: mesh plan
Plan-Sphere: the cells ["Sphere"] (cuts []) do not mesh all-hex: bodySphere: not supported yet: no structured hex method fits this body (extrude: no direction to try; sweep: no planar face to sweep from; revolve: no axis to t… - Sphere: mesh.hex.generate: body
Sphere: not supported yet: no structured hex method fits this body (extrude: no direction to try; sweep: no planar face to sweep from; revolve: no axis to try; mapped: meshing failed: meshing failed: mesh validity gate: 8 inverted or… - fail (volume): no structured method takes a sphere (it has no face to sweep from or axis to revolve about), so the plan does not apply and the fallback's sphere template meshes it at the plan's size (2.45 mm, which keeps a sphere's chords to 0.75 % on paper), 1.1 % short of the volume: the template's elements at the cube's eight corners span more of the sphere than the size. A sphere template in the plan (§3) would size its caps; hex20 (
order: 2) closes it too.
- Sphere: mesh.plan.apply: mesh plan
plain-cup(the cup without its handle (#418)):- Cup: mesh.plan.apply: mesh plan
Plan-Cup: the projected layout does not mesh: bodyCup: meshing failed: the mesh volume 5012.205781249979 is far from the body's 16760.91944296662: the blocks do not cover the part (every outer block face must stand for part o…
- Cup: mesh.plan.apply: mesh plan
Parts
bent-tube: corpus: A 10 mm tube with a 1 mm wall, bent through R30: a thin-walled sweep along a line-arc-line path.bolted-assembly: corpus: A base plate with a clamp block component placed twice, once turned 90 degrees: bodies placed by component transforms.cantilever-i-beam: corpus: The 200 mm I-beam ofi-beamlying along X with its web upright (Z up): a sweep along a horizontal axis, and the cantilever of the README demo.cross-cylinders: corpus: Two orthogonal R10 cylinders joined: equal radii, so the intersection curves cross at two singular points.filleted-bracket: corpus: An 8 mm L-bracket with an R6 inside fillet and a hole through each leg: a blend beside holes.filleted-bracket-blocks: corpus: The filleted bracket split into 30 cells with SolidCraft's Split Body (three slabs across its width, the corner halved along its diagonal, an O-grid of four cells round each hole) so that every cell is a mapped block: conforming multi-block hex meshing.i-beam: corpus: A 200 mm I-beam with 3 mm flanges and a 2 mm web: a long sweepable prism with thin walls.loft-transition: corpus: A 40 mm square lofted to an R15 circle 50 mm above: a ruled free-form surface between unlike sections.perforated-plate: corpus: A 3 mm plate perforated by a 6 x 4 grid of 6 mm holes: genus 24 in a thin sheet.pinhole-block: corpus: A 50 mm block drilled through by sixteen 0.6 mm pinholes: features 80 times smaller than the part, genus 16.rounded-box: corpus: A 40 x 30 x 20 box with every edge rounded R3: cylinder and sphere blends meeting at the corners.shallow-groove: corpus: A block with a 0.5 mm deep groove cut by an R30 cylinder: groove walls 10 degrees off the top face.sharp-wedge: corpus: A wedge whose faces meet at under 3 degrees: a sharp edge with a needle-thin cross-section.sphere-on-plate: corpus: A sphere sunk 0.1 mm into a plate: the faces meet at under 6 degrees around a 2 mm circle.spline-vase: corpus: A vase revolved from a spline profile: doubly curved faces of varying curvature around a closed axis.stepped-block: corpus: Three stacked blocks with a slot cut through the base: axis-aligned steps for polycube and octree meshers.thin-ribs: corpus: A 4 mm plate carrying five 0.8 mm ribs 20 mm tall: thin walls standing on a thicker base.thin-wall-enclosure: corpus: An 80 x 60 x 40 box shelled to 1 mm walls, open on top: thin walls over a large span.analytic-sphere: corpus: Sphere r 20: curvature everywhere, no edges.analytic-torus: corpus: Torus 30/8: genus 1 with doubly curved faces and no edges.analytic-cylinder: corpus: Cylinder r 10, h 40: the round bar of beam and torsion checks.analytic-pipe: corpus: Thick-walled pipe 20/30, length 60: the Lamé cylinder.analytic-plate-hole: corpus: Plate 200 x 100 x 5 with a 20 mm hole: the Kirsch plate.crank-arm: samplecrank-arm: the 170 mm bicycle crank, mirror symmetric across y = 0upright-bracket: samplesdrop-test,flow-past: the upright L-bracketpi5-base: the Raspberry Pi 5 enclosure's base: a shelled box with fillets and standoffspi5-lid: the Raspberry Pi 5 enclosure's lidcup: the cup with a handle of #418plain-cup: the cup without its handle (#418)