Research: mesh probe run 2, which lowering gives shippable geometry
The compact Dexter Quiet description lowered three ways (OpenSCAD plus automatic cleanup, a bmesh script, Geometry Nodes) and judged against the operator's bar. Serves the brush-tree target revision. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
253f34df49
commit
91babd43cc
1 changed files with 226 additions and 0 deletions
226
docs/research/mesh-probe-run2-shippable/RESULT.md
Normal file
226
docs/research/mesh-probe-run2-shippable/RESULT.md
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
# Mesh probe run 2: is the lowered geometry shippable?
|
||||
|
||||
Agent `probe-shippable-lowering`, session `self-2026-10-10-ag2`, 2026-10-10. Disposable scratch; no repo edited.
|
||||
Root: this directory (`...\scratchpad\mesh-probe\run2-shippable\`).
|
||||
|
||||
Operator question: "Sure, it can fit geometry, especially with 'real OpenSCAD', but is it good geometry? Do we
|
||||
end up with something we'll want to ship, if we go this way? Because I'm not sure we wouldn't be better off
|
||||
building with, say, Blender's geonodes."
|
||||
|
||||
Her bar (brush-tree:ruling:shippable-part-bar): clean, unbroken topology flow; UVs that keep the parts of one
|
||||
object together, with minimal distortion; hard edges sharpened by a one-segment bevel shaded with face-weighted
|
||||
normals.
|
||||
|
||||
Input: `docs/research/mesh-probe-run1/dexter-quiet-compact.scad` from the brush-tree branch, copied here as
|
||||
`A.scad`. It is run 1's round 3: superellipse stations, n = 3. All three lowerings use its numbers:
|
||||
- planform, 17 points;
|
||||
- wing plane z = a + b|x| + c·y, with wing_t 0.045;
|
||||
- trailing scallops;
|
||||
- 12 tail and body stations;
|
||||
- pods as hulls of two spheres;
|
||||
- the chin fin.
|
||||
|
||||
Python copy of the numbers: `tools/desc.py`.
|
||||
|
||||
## Contact sheets (rows top to bottom: Tripo target, A, B, C; orange squares = row number)
|
||||
|
||||
- **`sheet-main.png`**. Columns: 3/4 front, side and 3/4 rear in metallic grey under studio light, then a
|
||||
wireframe overlay, then the UV layout. In the UV column, colour marks the island and red marks overlap. The
|
||||
target has no UVs, so its UV tile is black. This is the one to judge at a glance.
|
||||
- **`sheet-closeup.png`**. Columns: close-up of the wing edge and wing-body junction (shaded, then wireframe),
|
||||
and a top view with wireframe. The bevel highlights and the faceting are visible here.
|
||||
- **`sheet-edit.png`**. Rows: A, A_edit, B, B_edit, C, C_edit. The edit is nose_k 1.0 to 1.2 plus a new
|
||||
wing_sweep of 0.1.
|
||||
- `sheet-top.png`: top views, shaded and wireframe.
|
||||
- Per-lowering frames are in `out/<X>.v0-4.png`, `out/<X>.w0,w2,w4.png` and `out/<X>.uv.png`. Metrics are in
|
||||
`out/<X>.metrics.json`, scores in `out/<X>.score.txt`, and the .blend files in `out/`.
|
||||
|
||||
Renders are Eevee (Blender 5.2.2). The look:
|
||||
- Principled metallic 1.0, roughness 0.3, grey;
|
||||
- Blender's bundled `studio.exr` world plus one big soft area key;
|
||||
- orthographic camera, dark ground composited.
|
||||
|
||||
This approximates her test render; it is not her scene.
|
||||
|
||||
## The three lowerings
|
||||
|
||||
- **A, OpenSCAD lowering with the best automatic cleanup** (`tools/lower_a.py`):
|
||||
- OpenSCAD 2026.09.23 with Manifold renders the file (2 s, 2836 triangles, genus 0).
|
||||
- Blender then runs: merge by distance, tris-to-quads, limited dissolve at 2°, Bevel (1 segment, by angle
|
||||
30°, width 0.004), Smart UV Project (66°) and Weighted Normal (face area, keep sharp).
|
||||
- A2 also tried QuadriFlow, see "Could not test".
|
||||
- **B, Blender built by script** (`tools/lower_b.py`, 174 lines):
|
||||
- Body: tail and body stations as one quad loft, 36 verts per ring, with 2 Catmull-Rom rings inserted per
|
||||
span. The ends get Coons-patch quad caps.
|
||||
- Wing: one quad sheet across the whole span, with columns at the planform breakpoints and at the
|
||||
trailing-edge kink, 16 chord rows, the scallop as the trailing edge, and a closed side band.
|
||||
- Exact Boolean only for the wing into the body.
|
||||
- Pods are quad capsules and the chin fin is a prism. Both are left as intersecting shells, not
|
||||
booleaned.
|
||||
- Seams go on edges over 30°, the body side lines and the cap rings. Then unwrap (angle based), average the
|
||||
island scale, pack, Bevel (1 segment, 30°), Weighted Normal.
|
||||
- **C, Geometry Nodes generated by Python** (`tools/lower_c.py`, 172 lines). It is the same construction as
|
||||
one node group, `DexterQuiet`, with 199 nodes and 271 links. Its 16 parameters are modifier inputs:
|
||||
- nose_root, nose_k, wing_t, wing_a/b/c, wing_sweep, scallop_r/cx/cy, sect_n, ring_n, loft_res,
|
||||
wing_cuts, chord_rows, seam_angle.
|
||||
- Station and planform data live in Index Switch nodes.
|
||||
- Body: Points, then Points to Curves (Catmull-Rom), then Curve to Mesh with a superellipse profile, then
|
||||
Set Position from the hw/zc/hh attributes.
|
||||
- Wing: a Grid whose columns sample the leading-edge curve, then Extrude Mesh, joined with a flipped copy.
|
||||
- Mesh Boolean (Manifold solver) for the wing into the body.
|
||||
- Pods are UV spheres reshaped into capsules; the fin is a convex hull.
|
||||
- UVs come from the UV Unwrap node, with the seam field = edge angle > 30° or a `cut_e` edge flag, then
|
||||
Pack UV Islands.
|
||||
- Modifier stack: GN, then Bevel (1 segment, 30°), then Weighted Normal.
|
||||
|
||||
## Measurements
|
||||
|
||||
| | A (OpenSCAD + cleanup) | B (bmesh script) | C (geonodes) |
|
||||
|---|---|---|---|
|
||||
| Faces: quads / tris / ngons | 1146 / 125 / 168 | 3039 / 49 / 215 | 3979 / 127 / 229 |
|
||||
| Largest ngon | **59** (wing top/bottom dissolved to single faces) | 11 (boolean seam) | 36 (loft end caps; GN has no grid fill) |
|
||||
| Poles (valence ≠ 4) per 1000 verts | **411.7** | 100.1 | 98.0 |
|
||||
| Poles of valence 6 or more | 10 | 0 | 4 (UV-sphere pod tips) |
|
||||
| Longest edge loop (edges) | **8** | 86 | 35 |
|
||||
| Closed loops ≥ 20 edges | 0 | 41 | 57 |
|
||||
| Share of edges in loops ≥ 20 edges | **0.013** | 0.528 | 0.605 |
|
||||
| Non-manifold edges / shells | 0 / 1 | 0 / 4 (pods and fin intersect) | 0 / 4 |
|
||||
| Exported triangles | 3392 | 6914 | 9022 |
|
||||
| Sliver tris (< 10°) after export triangulation | 2082 (61%) | 2112 (31%) | 2537 (28%) |
|
||||
| …excluding faces of bevel width | 491 | 336 | 157 |
|
||||
| Dihedral > 60° / > 80° left (unbevelled hard edges) | 86 / 15 | 83 / 7 | 77 / 9 |
|
||||
| UV islands: total / ≥ 1% of area / < 4 faces | 51 / 13 / 15 | 23 / 11 / 2 | 21 / 11 / 2 |
|
||||
| UV area distortion, mean \|log2\|, area-weighted | 0.120 | 0.113 | 0.118 |
|
||||
| UV area outside 0.5-2x | 0.1% | 0.7% | 1.1% |
|
||||
| UV angle error, mean | 2.74° | **1.04°** | 1.14° |
|
||||
| UV space wasted | 54% | 47% | 61% |
|
||||
| Chamfer % diag / F@2% / mean sil-IoU (run-1 `mp score`) | 0.852 / 0.901 / 0.919 | 0.841 / 0.899 / 0.920 | 0.840 / 0.900 / 0.920 |
|
||||
|
||||
Raw OpenSCAD output scores 0.852 / 0.901 / 0.919. Cleanup lost no shape, and B and C lose none against A.
|
||||
|
||||
Reading the numbers:
|
||||
- **Fidelity is the description's, not the lowering's.** All three are within 0.012 chamfer. This is the
|
||||
same 0.852 that run 1's round 3 scored. No lowering makes the description closer to the target.
|
||||
- **A fails the topology bar, and cleanup cannot fix it.** Hull-of-disks lofting plus CSG emits long fan
|
||||
triangles. Join-triangles and limited dissolve turn them into a patchwork with no loops longer than 8
|
||||
edges. The wing top and bottom become single ngons of up to 59 sides. 61% of the exported triangles are
|
||||
slivers. On the close-up, the body hull's facets show through the weighted normals as flat shading
|
||||
patches.
|
||||
- **A's UVs look fine on paper** (area 0.12), because Smart UV cuts the surface into 51 islands, 15 of them
|
||||
crumbs under 4 faces. That breaks "parts of one object kept together". See its UV tile.
|
||||
- **B and C meet the topology-flow bar on the loft and the wing.** Body rings are closed 36-edge loops from
|
||||
stinger to beak, and the wing is a quad grid.
|
||||
- **Where B and C miss the topology bar:**
|
||||
- The wing-body boolean seam leaves 215-229 ngons and most of the poles. That is the one place a hand
|
||||
modeller would retopologise.
|
||||
- The pods intersect the body as separate shells instead of being merged.
|
||||
- C's loft caps are 36-gons and its pod tips are pole fans. Geometry Nodes has no grid-fill or Coons
|
||||
patch, so B's quad caps have no direct node equivalent.
|
||||
- **The remaining slivers in B and C are mostly the bevel strips themselves.** A 0.004 strip against
|
||||
0.025 edges is inherently thin when triangulated; her bevel style has the same property. Excluding faces
|
||||
of bevel width, C has 157 and B 336, from the thin rings near the stinger and beak tips.
|
||||
- **The bevel bar is met on the outline in all three:** one segment, a bright highlight on the wing edge
|
||||
band, weighted normals on.
|
||||
- **It is not met at the wing-body junction**, where the crease is under 30° over much of its length, so
|
||||
it gets no bevel. That is a hard intersection line, not a fillet. The edge counts above 60° (77-86) are
|
||||
similar in all three, so the leftover hard edges are in the description (fin, pod ends, crease), not in
|
||||
the lowering.
|
||||
- **The normal-deviation metric** (mean angle between custom and face normals, 3.6-4.4° in all three) is
|
||||
not diagnostic. The largest faces here sit on the curved body, which is legitimately curved. I have
|
||||
dropped it as evidence.
|
||||
- **The UV bar is met by B and C.** Each has about 11 islands of 1% of area or more:
|
||||
- body top, body bottom;
|
||||
- wing top and wing bottom per side;
|
||||
- the wing edge band;
|
||||
- pods, caps, fin.
|
||||
|
||||
Angle error is about 1° and area distortion about 0.11. Packing wastes 47-61%, which is plain
|
||||
pack_islands; no trim or texel-density decisions were made.
|
||||
|
||||
## Editability (nose_k 1.0 to 1.2, plus a wing_sweep 0.1 that the description lacked)
|
||||
|
||||
| | What the edit is | Rerun | Topology after the edit |
|
||||
|---|---|---|---|
|
||||
| A | 2 `.scad` lines (one value, plus a `multmatrix` shear on the planform inside `wing_side`) | OpenSCAD 2 s + cleanup script ~3 s | It changes. Faces 1439 to 1434, ngons 168 to 173, islands 51 to 48. Every edit re-rolls the patchwork, and any hand fix-up in Blender is lost. |
|
||||
| B | `DESC_OVERRIDES` or `desc.py` values; sweep is 1 line in the wing loop | Script ~3.6 s, produces a new .blend | Stable. Loops 86 to 86, islands 23 to 23, +4 faces at the boolean seam. Hand work is lost on rerun. |
|
||||
| C | Two modifier input values. Sweep was one exposed input plus 2 math nodes. | Live in the modifier panel. Script build ~3 s. | Stable. Loops 35 to 35, islands 21 to 21, +2 faces. Hand work can only be stacked as later modifiers; editing the result means applying the modifier. |
|
||||
|
||||
`sheet-edit.png` shows the edits. They are small on screen: the nose grows from 0.52 to 0.552 and the wing
|
||||
tips sweep back about 0.05.
|
||||
|
||||
## B and C converge
|
||||
|
||||
Same fidelity (0.841 / 0.840). Same UV quality (angle error 1.04° / 1.14°, area 0.113 / 0.118). Same island
|
||||
structure, and the same misses at the boolean seam. The differences are resolution choices and two Geometry
|
||||
Nodes gaps:
|
||||
- the caps are ngons, and the pods are UV spheres with pole fans;
|
||||
- there is no bevel node, so the bevel is a modifier after the tree, and the unwrap happens before the
|
||||
bevel. The bevel faces inherit UVs from the neighbouring face, and that measured fine.
|
||||
|
||||
**Geonodes is a practical lowering target the model can drive.** One generator script, about 170 lines,
|
||||
produced a working parametric tree. I hit four Blender 5.2 API traps before it ran:
|
||||
1. The Index Switch has a virtual extend socket.
|
||||
2. Several enum properties became menu sockets that take UI labels: `'Angle Based'`, not `'ANGLE_BASED'`.
|
||||
3. Modifier inputs are set through `mod.properties.inputs[id]["value"]`. `mod[id]` is gone.
|
||||
4. **The GN Mesh Boolean with the Exact solver corrupted named attributes.** Wing vertices came out with
|
||||
`cut` and `endcap` = 1, values the wing never had, even after storing explicit zeros. The Manifold
|
||||
solver propagates them correctly.
|
||||
|
||||
Point-attribute seams also broke where the boolean split an edge, which merged the body's top and bottom UV
|
||||
islands. The fix was carrying the seam as an edge attribute, `cut_e`. I checked it with
|
||||
`tools/c_opunwrap.py`: the same geometry unwrapped by the operator gave the identical bad result, so this
|
||||
was a seam-definition issue, not the UV node.
|
||||
|
||||
**Readability:** the tree's node positions are auto-placed with no frames, so as a canvas it is spaghetti.
|
||||
The readable artefact is the Python generator. A tree she would want to open needs frames per part
|
||||
(body / wing / pods / UV) and a layout pass, which I did not do.
|
||||
|
||||
## Conclusion on her question
|
||||
|
||||
**OpenSCAD as the lowering does not give geometry she would ship, and automatic cleanup cannot rescue
|
||||
it.** Its output has already lost the construction intent: which vertices are a station ring, which faces
|
||||
are the wing sheet. The topology numbers above are the evidence: 412 poles per 1000 vertices, a longest loop
|
||||
of 8 edges, 59-gons, 61% slivers, and 51 UV crumbs.
|
||||
|
||||
**Blender lowering (B or C) from the same description meets her bar where the description has clean
|
||||
construction:** the body loft and the wing sheet. Both misses come from the construction, not the lowering
|
||||
tool:
|
||||
- the boolean seam where the wing enters the body (ngons and poles);
|
||||
- intersecting pods and fin.
|
||||
|
||||
Neither B nor C is a finished part yet. The description itself is crude, so the part is too: a
|
||||
constant-thickness slab wing, no crease fillet, blob pods, 0.84% chamfer.
|
||||
|
||||
**Recommendation: the hybrid.** The model writes the description, in OpenSCAD or anything else that carries
|
||||
the parameters and the construction intent: stations, planform, plane, part list. Blender lowers it by
|
||||
construction, not by CSG output.
|
||||
- **Geonodes (C)** is the better target for her. It is equal in quality to B and live-editable from the
|
||||
modifier panel, which is her tool.
|
||||
- **B's bmesh script** is the fallback for constructions nodes cannot express, such as quad caps or
|
||||
grid fill.
|
||||
- **"Real OpenSCAD" can stay as the authoring and fitting surface** only if the lowering reads its
|
||||
parameters and not its mesh. Lowering the CSG tree itself reproduces A.
|
||||
|
||||
Confidence: moderate for "A cannot meet the bar". That follows from what the output loses, and the numbers
|
||||
are large. Low to moderate for "C is shippable with work":
|
||||
- one hull, one description;
|
||||
- construction and seam choices are mine;
|
||||
- no human retopologised the seam for comparison;
|
||||
- the eye on the renders was mine, not hers.
|
||||
|
||||
## Could not test / caveats
|
||||
|
||||
- **QuadriFlow, A2's best automatic retopology**, refused in background mode. It reported "needs to be
|
||||
manifold" even on bmesh-manifold input, and again after a voxel remesh. A voxel remesh alone (0.004)
|
||||
gives 114k faces and 380 poles per 1000 vertices, and destroys the hard edges that bevel-by-angle needs.
|
||||
I did not try QuadriFlow in the GUI. `tools/lower_a2.py` and `out/A2.*` hold the failed attempt.
|
||||
- Her studio scene, texture baking and decals, export into the game engine, and LOD were not tested.
|
||||
Packing efficiency was not tuned.
|
||||
- Interpenetrating pods were a choice. Booleaning them in would add more seam ngons of the kind the wing
|
||||
produced.
|
||||
- The A cleanup parameters (dissolve 2°, join 10°/40°, Smart UV 66°) were set once, not swept.
|
||||
- Tools: `tools/run.sh measure|render|target|score <name>`, `measure.py`, `render.py` and `sheet.py`. The
|
||||
scorer is run 1's `mp`, rebuilt into `tools/mp-target`; that 156 MB build directory has been deleted, so
|
||||
rebuild it before rescoring. `dbg*.py` are throwaway diagnostics.
|
||||
- No processes of mine are running. The operator's GUI Blender (PID 36324) was never touched. I waited out
|
||||
another agent's background Blender smoke and a cargo build before my first jobs.
|
||||
Loading…
Reference in a new issue