diff --git a/docs/research/spike-boolean-weld.md b/docs/research/spike-boolean-weld.md index 0fd6612..3f444e2 100644 --- a/docs/research/spike-boolean-weld.md +++ b/docs/research/spike-boolean-weld.md @@ -7,6 +7,123 @@ Agent `spike-bt-boolean-weld`, 2026-10-12. A spike, not a cut: the code under `s Her words, the spec of the rule: "the surface with the tighter curve defines the silhouette of the cut, and the spare vertices that come from the flatter surface all get merged away." +## Round 3: Self's calls and ruling weld-owner-local-corners-kept + +Inputs to this round: + +- Self's calls: a flat split keeps its seam exactly on the cutter's plane; hull vertices survive; hull-side slivers + along a flat seam are repaired by edge flips; the cap faces a cut leaves are ours and get a constrained Delaunay + (CDT); worst-angle guard 5 deg. +- Ruling `weld-owner-local-corners-kept` (operator): ownership is local along the loop, and a hard-edged cutter's + corners always survive. + +How it was run: + +- **Code:** `py/round3.py` for the flat seams; `py/owner_weld.py` with `NOSNAP_FLAT=1 MIN_ANGLE=5` for the curved + cutters. +- **Ownership:** per loop vertex, by curvature along the loop. Nothing in the code takes a loop-wide vote. +- **Protected corners:** loop vertices on a sharp cutter edge (dihedral > 30 deg) or on a hull crease edge. They are + never a merge source and never moved. +- **Edge flips:** greedy. A flip must raise the pair's worst angle and keep both normals within 20 deg. Seam and + crease edges are constrained. +- **CDT:** Shewchuk's Triangle through the `triangle` package (Python wrapper LGPL-3.0; the Triangle core's licence + bars commercial use, so it is fine for this spike and not shippable). Variants: + - `cdt`: plain CDT, loop as the boundary. + - `cdt-q`: CDT with a 20 deg quality bound, interior Steiner points only (`pq20Y`), so the seam is never split. +- **Short seam merge (`short`):** consecutive seam vertices closer than the 0.001 bevel width merge, jointly on both + pieces. Both endpoints lie on the plane, so the seam stays on it. Only boolean-inserted vertices are merged away. +- **New probes:** a box cut into the curved flank (`probe_box_flank`, half-width 0.02) and a box across the dorsal + ridge (`probe_box_ridge`, half-width 0.06). Each is rotated off the tessellation, with its sharp vertical edges on + the loop. + +Tables: `spikes/boolean-weld/results/round3/`. + +### Flat seams (seam on the plane; hull side and cap reported separately) + +| case | variant | hull slivers | hull worst deg | hull p5 deg | cap slivers | cap worst deg | flips | seam off plane | seam crack | seam-adjacent edges < bevel width | +|---|---|---|---|---|---|---|---|---|---|---| +| panel split, subtract piece | naive | 37 | 0.676 | 3.86 | 85 / 122 | 0.058 | 0 | 3e-16 | 0 | 15 | +| | flip + cdt | 28 | 0.676 | 4.69 | 80 / 122 | 0.122 | 0 | 3e-16 | 0 | 15 | +| | flip + cdt-q | 28 | 0.676 | 4.69 | 0 / 332 | 6.76 | 0 | 3e-16 | 0 | 23 | +| | short + flip + cdt-q | 26 | 0.676 | 5.30 | 1 / 313 | 5.50 | 0 | 3e-16 | 0 | 16 | +| panel split, intersect piece | naive | 32 | 0.539 | 4.36 | 85 / 122 | 0.056 | 0 | 3e-16 | 0 | 14 | +| | short + flip + cdt-q | 17 | 0.539 | 6.69 | 1 / 313 | 5.50 | 0 | 3e-16 | 0 | 16 | +| box across the crease, subtract piece | naive | 22 | 0.377 | 3.73 | 22 / 82 | 0.033 | 0 | 7e-16 | 0 | 12 | +| | short + flip + cdt-q | 12 | 2.41 | 6.64 | 3 / 147 | 0.714 | 0 | 7e-16 | 0 | 6 | +| box across the crease, intersect piece | naive | 25 | 0.0405 | 5.61 | 22 / 82 | 0.033 | 0 | 7e-16 | 0 | 11 | +| | short + flip + cdt-q | 10 | 0.0405 | 7.14 | 3 / 147 | 0.714 | 0 | 7e-16 | 0 | 5 | + +- **Flips applied:** 138 and 99 on the split; 65 and 79 on the crease box. +- **Short seam merges:** 3 on the split, 4 refused by the 5 deg guard; 5 on the crease box, 1 refused. They move the + seam polyline by at most 0.0069 % and 0.0040 % of the diagonal, within the plane. +- **The seam stays on the plane to float** (3e-16), the two pieces' seams coincide (crack 0), and nothing flips. +- **The cap CDT with interior refinement removes the cap's slivers.** Cap worst angle 0.058 to 5.5 deg (6.8 deg + without the short merge); the crease box's cap reaches only 0.71 deg. Plain CDT cannot: the cap is a long thin + polygon, and with no interior points its worst angle only reaches 0.12 deg. +- **The hull side's worst angle does not move** (0.676, 0.539 and 0.0405 deg). It is a triangle whose two vertices + are a boolean-inserted seam vertex and a hull vertex almost on top of it. Flips cannot remove the edge between + them: two nearly coincident points stay connected in any Delaunay-like triangulation. With both points pinned (the + seam on the plane, hull vertices surviving), that angle stays. The seam-adjacent edges shorter than the bevel width + remain for the same reason (15 to 16). +- **Render** (`r3_longinus_vs_split.jpg`, `r3_r3split.jpg`): the round-3 seam is straight, crisp and continuous for + most of its length. That is better than round 2's kinks, and its seam no longer shows round 1's sliver highlight. + Where the short seam-adjacent edges sit, the bevel narrows and a light wedge appears: Bevel's overlap clamp (on in + the cage's own modifier) caps the width at the shortest adjacent edge. Turning the clamp off (`r3_r3clamp0.jpg`) + gives a constant-width groove but overlapping bevel geometry: a spike on the round-3 seam, sawtooth on the naive + one. So her uniform Longinus groove is not reached yet. Reaching it needs those near-coincident seam/hull vertex + pairs resolved. Either the hull vertex moves onto the seam within the hull's own surface (it survives, displaced + by less than the bevel width), or Self's no-move call is relaxed for vertices within the bevel width. + +### Curved and hard-edged cutters (owner rule, local ownership, corners kept, 5 deg) + +| case | variant | slivers | worst deg | p5 deg | flips | silhouette dev max | corners kept / total | +|---|---|---|---|---|---|---|---| +| gun port 16 (regression) | naive | 32 | 0.007 | 1.14 | 0 | 0.0198 % | 0 / 0 | +| | owner r3 | 5 | 5.09 | 6.68 | 0 | 0.0198 % | 0 / 0 | +| ownership switch r 0.08 | naive | 112 | 1.2e-6 | 0.62 | 0 | 0.0128 % | | +| | owner r3 | 53 | 1.2e-6 | 2.65 | 0 | 0.323 % | 4 / 4 | +| crease notch 16 | owner r3 | 10 | 4.31 | 6.83 | 0 | 0.061 % | 2 / 2 | +| box in flank (corners on loop) | naive | 3 | 0.285 | 6.62 | 0 | 0 | | +| | owner r3 | 2 | 0.285 | 9.61 | 0 | 0.0108 % | 4 / 4 | +| | hull-side flips (24) | hull 0 (was 2) | hull 6.0 (was 0.95) | hull 12.1 | 0 | 0 | 4 / 4 | +| box across ridge | owner r3 | 66 | 0.070 | 1.41 | 0 | 0.0014 % | 6 / 6 | +| | hull-side flips (118) | hull 7 (was 19) | hull 1.75 (was 0.72) | hull 7.24 | 0 | 0 | 6 / 6 | + +- **Corners:** no corner was moved or lost in any case. `corners_kept_in_place` equals `corners` everywhere, and the + guard turned away 4 merges on the flank box and 6 on the ridge box (`protected_corner`). + - On the ownership-switch probe, the 4 corners are where the r 0.08 cylinder's floor rim breaks through the ridge. + - On the crease notch they are the 2 rim-crease crossings; on the flat splits, the 4 crease crossings. +- **Ownership on the box cut-ins:** the hull owns every stretch (box faces have zero curvature along the loop), no + vertex snaps (flat), and the box contributes its corners. That matches her "keep the first's corners". +- **The ridge box's pocket walls keep 48 slivers.** They are the cutter's own faces, trimmed by the hull. They are + not a planar cap, so this round did not re-triangulate them. +- **The port regression is unchanged** from round 2: slivers 5, worst 5.09 deg, silhouette unchanged. +- **The ownership switch** keeps its owner-side 1.2e-6 deg needle and round 2's 0.32 % loop movement on the + hull-owned stretches, where curved-cutter snapping is still on. + +### Renders, round 3 + +![Longinus vs split, round 3](spike-boolean-weld/r3_longinus_vs_split.jpg) +![split seam](spike-boolean-weld/r3_r3split.jpg) +![split wide](spike-boolean-weld/r3_r3splitwide.jpg) +![clamp off](spike-boolean-weld/r3_r3clamp0.jpg) +![crease box](spike-boolean-weld/r3_r3crease.jpg) +![box in flank](spike-boolean-weld/r3_r3boxflank.jpg) +![box across ridge](spike-boolean-weld/r3_r3boxridge.jpg) +![port regression](spike-boolean-weld/r3_r3port16.jpg) +![switch](spike-boolean-weld/r3_r3switch.jpg) + +### Questions for Self (round 3, text only) + +1. A flat seam's residual worst angle and short bevel-clamped edges come from a boolean-inserted seam vertex lying + almost on a hull vertex. Under "seam on the plane, hull vertices survive" both are pinned. Should a hull vertex + within the bevel width of the seam be allowed to slide onto the seam inside the hull's surface? It still survives, + moved by less than 0.001. +2. Curved-cutter snapping on hull-owned stretches still moves the loop (switch probe 0.32 %). Should the round-3 + no-snap apply to every hull-owned stretch, curved cutter or not? +3. The CDT here uses Triangle, whose licence bars commercial use. A shippable cap CDT needs an owned implementation + or a permissive crate (for example `spade`, a Rust CDT; not evaluated here). + ## Round 2: ruling weld-owner-defines-silhouette The operator answered round 1's question (ruling `weld-owner-defines-silhouette`, superseding diff --git a/docs/research/spike-boolean-weld/r3_longinus_vs_split.jpg b/docs/research/spike-boolean-weld/r3_longinus_vs_split.jpg new file mode 100644 index 0000000..9c0e5b6 Binary files /dev/null and b/docs/research/spike-boolean-weld/r3_longinus_vs_split.jpg differ diff --git a/docs/research/spike-boolean-weld/r3_r3boxflank.jpg b/docs/research/spike-boolean-weld/r3_r3boxflank.jpg new file mode 100644 index 0000000..aeb8f36 Binary files /dev/null and b/docs/research/spike-boolean-weld/r3_r3boxflank.jpg differ diff --git a/docs/research/spike-boolean-weld/r3_r3boxridge.jpg b/docs/research/spike-boolean-weld/r3_r3boxridge.jpg new file mode 100644 index 0000000..b3bf654 Binary files /dev/null and b/docs/research/spike-boolean-weld/r3_r3boxridge.jpg differ diff --git a/docs/research/spike-boolean-weld/r3_r3clamp0.jpg b/docs/research/spike-boolean-weld/r3_r3clamp0.jpg new file mode 100644 index 0000000..c5667a1 Binary files /dev/null and b/docs/research/spike-boolean-weld/r3_r3clamp0.jpg differ diff --git a/docs/research/spike-boolean-weld/r3_r3crease.jpg b/docs/research/spike-boolean-weld/r3_r3crease.jpg new file mode 100644 index 0000000..481bebf Binary files /dev/null and b/docs/research/spike-boolean-weld/r3_r3crease.jpg differ diff --git a/docs/research/spike-boolean-weld/r3_r3port16.jpg b/docs/research/spike-boolean-weld/r3_r3port16.jpg new file mode 100644 index 0000000..7a9a0ce Binary files /dev/null and b/docs/research/spike-boolean-weld/r3_r3port16.jpg differ diff --git a/docs/research/spike-boolean-weld/r3_r3split.jpg b/docs/research/spike-boolean-weld/r3_r3split.jpg new file mode 100644 index 0000000..9a2bb9b Binary files /dev/null and b/docs/research/spike-boolean-weld/r3_r3split.jpg differ diff --git a/docs/research/spike-boolean-weld/r3_r3splitwide.jpg b/docs/research/spike-boolean-weld/r3_r3splitwide.jpg new file mode 100644 index 0000000..1666148 Binary files /dev/null and b/docs/research/spike-boolean-weld/r3_r3splitwide.jpg differ diff --git a/docs/research/spike-boolean-weld/r3_r3switch.jpg b/docs/research/spike-boolean-weld/r3_r3switch.jpg new file mode 100644 index 0000000..4339970 Binary files /dev/null and b/docs/research/spike-boolean-weld/r3_r3switch.jpg differ diff --git a/spikes/boolean-weld/cutters/cases.json b/spikes/boolean-weld/cutters/cases.json index b5c84cc..649e9d5 100644 --- a/spikes/boolean-weld/cutters/cases.json +++ b/spikes/boolean-weld/cutters/cases.json @@ -253,5 +253,93 @@ "seg": 48, "depth": 0.15, "note": "vertical cylinder r 0.03 through rim crease vertex 35" + }, + { + "name": "probe_box_flank", + "kind": "obox", + "op": "sub", + "cutter": "cutters/probe_box_flank.obj", + "p": [ + 0.17, + 0.0, + 0.10491874328928341 + ], + "n": [ + 0.16691628816232545, + 0.2153707407240696, + 0.9621613153656081 + ], + "R": [ + [ + -0.7999919715756821, + -0.5763261213588748, + 0.16691628816232545 + ], + [ + 0.5999939786817615, + -0.770469123057891, + 0.2153707407240696 + ], + [ + 0.004480062508814215, + 0.27244363133286853, + 0.9621613153656081 + ] + ], + "lo": [ + -0.02, + -0.02, + -0.02 + ], + "hi": [ + 0.02, + 0.02, + 0.03 + ], + "note": "oriented box half-width 0.02" + }, + { + "name": "probe_box_ridge", + "kind": "obox", + "op": "sub", + "cutter": "cutters/probe_box_ridge.obj", + "p": [ + 0.0, + -0.05, + 0.1680719189037999 + ], + "n": [ + -0.09574350308538447, + 0.1106194787646127, + 0.9892403714642789 + ], + "R": [ + [ + -0.7917740521194535, + -0.6032636504939445, + -0.09574350308538447 + ], + [ + 0.5938305390895902, + -0.7969495729106153, + 0.1106194787646127 + ], + [ + -0.14303545446814095, + 0.03073021689327954, + 0.9892403714642789 + ] + ], + "lo": [ + -0.06, + -0.06, + -0.02 + ], + "hi": [ + 0.06, + 0.06, + 0.03 + ], + "note": "oriented box half-width 0.06" } ] \ No newline at end of file diff --git a/spikes/boolean-weld/cutters/probe_box_flank.obj b/spikes/boolean-weld/cutters/probe_box_flank.obj new file mode 100644 index 0000000..c8b9120 --- /dev/null +++ b/spikes/boolean-weld/cutters/probe_box_flank.obj @@ -0,0 +1,20 @@ +v 0.194188036 -0.000897912 0.080137043 +v 0.202533851 0.009870625 0.128245109 +v 0.171134991 -0.031716677 0.091034788 +v 0.179480806 -0.020948140 0.139142854 +v 0.162188357 0.023101847 0.080316246 +v 0.170534172 0.033870384 0.128424311 +v 0.139135312 -0.007716918 0.091213991 +v 0.147481127 0.003051619 0.139322057 +f 1 2 4 +f 5 7 8 +f 1 5 6 +f 3 4 8 +f 1 3 7 +f 2 6 8 +f 1 4 3 +f 5 8 6 +f 1 6 2 +f 3 8 7 +f 1 7 5 +f 2 8 4 diff --git a/spikes/boolean-weld/cutters/probe_box_ridge.obj b/spikes/boolean-weld/cutters/probe_box_ridge.obj new file mode 100644 index 0000000..4c6610a --- /dev/null +++ b/spikes/boolean-weld/cutters/probe_box_ridge.obj @@ -0,0 +1,20 @@ +v 0.085617132 -0.040025248 0.155025426 +v 0.080829957 -0.034494274 0.204487444 +v 0.013225494 -0.135659196 0.158713052 +v 0.008438319 -0.130128222 0.208175070 +v -0.009395754 0.031234417 0.137861171 +v -0.014182929 0.036765391 0.187323190 +v -0.081787392 -0.064399532 0.141548797 +v -0.086574567 -0.058868558 0.191010816 +f 1 2 4 +f 5 7 8 +f 1 5 6 +f 3 4 8 +f 1 3 7 +f 2 6 8 +f 1 4 3 +f 5 8 6 +f 1 6 2 +f 3 8 7 +f 1 7 5 +f 2 8 4 diff --git a/spikes/boolean-weld/py/case_obox.py b/spikes/boolean-weld/py/case_obox.py new file mode 100644 index 0000000..d3e32d3 --- /dev/null +++ b/spikes/boolean-weld/py/case_obox.py @@ -0,0 +1,20 @@ +"""Round 3 case: an oriented box cut into the curved flank (a square port; its 4 vertical edges cross the hull, +putting 4 cutter corners on the loop). Appends to cases.json. Usage: case_obox.py """ +import sys, os, json, numpy as np +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from geo import save_obj +cf, outd = sys.argv[1:3] +cases = [c for c in json.load(open(cf)) if c['kind'] != 'obox'] +base = next(c for c in cases if c['name'] == 'port_flank_s16') +for name, src, half in (('probe_box_flank', base, 0.02), ('probe_box_ridge', next(c for c in cases if c['name'] == 'probe_switch_s48'), 0.06)): + p, n = np.array(src['p']), np.array(src['n']); n /= np.linalg.norm(n) + a = np.cross(n, [0.6, 0.8, 0]); a /= np.linalg.norm(a); b = np.cross(n, a) # rotated off the tessellation + R = np.stack([a, b, n], 1); lo, hi = np.array([-half, -half, -0.02]), np.array([half, half, 0.03]) + Vl = np.array([[x, y, z] for x in (lo[0], hi[0]) for y in (lo[1], hi[1]) for z in (lo[2], hi[2])]) + Vb = p + Vl @ R.T + Q = [[0, 1, 3, 2], [4, 6, 7, 5], [0, 4, 5, 1], [2, 3, 7, 6], [0, 2, 6, 4], [1, 5, 7, 3]] + Tb = np.array([[q[0], q[1], q[2]] for q in Q] + [[q[0], q[2], q[3]] for q in Q]) + if np.einsum('ij,ij->i', Vb[Tb[:, 0]], np.cross(Vb[Tb[:, 1]], Vb[Tb[:, 2]])).sum() < 0: Tb = Tb[:, ::-1] + fn = f'{outd}/{name}.obj'; save_obj(fn, Vb, Tb) + cases.append(dict(name=name, kind='obox', op='sub', cutter=fn, p=p.tolist(), n=n.tolist(), R=R.tolist(), lo=lo.tolist(), hi=hi.tolist(), note=f'oriented box half-width {half}')) +json.dump(cases, open(cf, 'w'), indent=1); print('ok') diff --git a/spikes/boolean-weld/py/owner_weld.py b/spikes/boolean-weld/py/owner_weld.py index 7e177cf..b263de3 100644 --- a/spikes/boolean-weld/py/owner_weld.py +++ b/spikes/boolean-weld/py/owner_weld.py @@ -17,6 +17,29 @@ MIN_ANGLE = float(os.environ.get('MIN_ANGLE', '10')) SNAP = float(os.environ.get('SNAP', '0.25')) # SNAPDIR=reverse: the hull vertex slides onto the loop vertex instead (the loop stays on the cutter; spike variant) SNAPDIR = os.environ.get('SNAPDIR', 'loop-to-hull') +# Round 3 (ruling weld-owner-local-corners-kept + Self's calls): no snapping where the cutter is planar (the seam stays on +# the cutter's plane), and feature corners on the loop always survive: loop vertices on a sharp cutter edge (dihedral +# > CORNER_DEG) or on a hull crease edge are never a merge source and never moved. +NOSNAP_FLAT = os.environ.get('NOSNAP_FLAT', '0') == '1' +CORNER_DEG = 30.0 + + +def sharp_cutter_edges(cut): + from geo import edge_faces, face_normals + FN, _ = face_normals(cut.V, cut.T); out = set() + for e, fs in edge_faces(cut.T).items(): + if len(fs) == 2 and FN[fs[0]] @ FN[fs[1]] < np.cos(np.radians(CORNER_DEG)): + out.add(e) + return out + + +def corners_of(P, info, hull, cut, tol): + sharp = sharp_cutter_edges(cut); out = [] + for v, inf in info.items(): + ce = cut.edge_of(P.V[v], tol) + if (ce is not None and (min(ce), max(ce)) in sharp) or inf['crease']: + out.append(P.V[v].copy()) + return out class Piece: @@ -91,7 +114,7 @@ class Piece: return s.V, np.array([s.faces[i] for i in keep]), np.array([s.prov[i] for i in keep]) -def candidates(P, info, loops, hull, tol, scope_ring=True): +def candidates(P, info, loops, hull, tol, scope_ring=True, cut_is_flat=False): """Operations as (dist, v_pos, w_pos, kind): v merges into w.""" cand = []; loopset = set(info) for L in loops: @@ -116,7 +139,7 @@ def candidates(P, info, loops, hull, tol, scope_ring=True): d = np.linalg.norm(P.V[x] - P.V[v]) if own == 1 and scope_ring: cand.append((d, P.V[x].copy(), P.V[v].copy(), 'ring')) - elif own == 0 and inf['origin'] == 'H' and hull.kd.query(P.V[v])[0] > tol and hull.kd.query(P.V[x])[0] < tol: + elif own == 0 and not (NOSNAP_FLAT and cut_is_flat) and inf['origin'] == 'H' and hull.kd.query(P.V[v])[0] > tol and hull.kd.query(P.V[x])[0] < tol: if d < SNAP * hull.vlen[hull.kd.query(P.V[x])[1]]: if SNAPDIR == 'reverse': cand.append((d, P.V[x].copy(), P.V[v].copy(), 'snap')) @@ -129,10 +152,11 @@ def owner_weld(pieces, hull, cut, tol): """pieces: list of (V, T, prov); one for a cut-in, two for a split. Returns welded pieces, stats, done ops, info.""" Ps = [Piece(*p) for p in pieces] infos = [] - cand = [] + cand = []; corners = [] for P in Ps: loops, le, info = analyse_loops(P.V, np.array([f for f in P.faces]), np.array(P.prov), hull, cut, 'along', tol) - infos.append((loops, info)); cand += candidates(P, info, loops, hull, tol) + infos.append((loops, info)); cand += candidates(P, info, loops, hull, tol, cut_is_flat=cut.kind in ('box', 'obox')) + corners.extend(corners_of(P, info, hull, cut, tol)) cand.sort(key=lambda z: z[0]) stats = defaultdict(int); done = []; seen = set() for d, pv, pw, kind in cand: @@ -140,6 +164,8 @@ def owner_weld(pieces, hull, cut, tol): if key in seen: continue seen.add(key); stats['cand_' + kind] += 1 + if any(np.linalg.norm(pv - q) < tol for q in corners): + stats['protected_corner'] += 1; continue plan, reason = [], None for P in Ps: v = P.at(pv, tol); w = P.at(pw, tol) @@ -165,4 +191,9 @@ def owner_weld(pieces, hull, cut, tol): else: P.apply_move(v, w) stats['applied_' + kind] += 1; done.append((pv, pw)) - return [P.result() for P in Ps], dict(stats), done, infos + res = [P.result() for P in Ps] + kept = 0 + for q in corners: + kept += int(any(np.linalg.norm(Vr[np.unique(Tr)] - q, axis=1).min() < tol for Vr, Tr, _ in res)) + stats['corners'] = len(corners); stats['corners_kept_in_place'] = kept + return res, dict(stats), done, infos diff --git a/spikes/boolean-weld/py/render_blender.py b/spikes/boolean-weld/py/render_blender.py index ee46fe6..0a9eba4 100644 --- a/spikes/boolean-weld/py/render_blender.py +++ b/spikes/boolean-weld/py/render_blender.py @@ -22,7 +22,7 @@ for e in me.edges: w = att.data[e.index].value if att else 0.0 if w > 0: segs.append((cage.matrix_world @ me.vertices[e.vertices[0]].co, cage.matrix_world @ me.vertices[e.vertices[1]].co, w)) -print('CAGE weighted edges', len(segs), 'smooth', [p.use_smooth for p in me.polygons][:1]) +print('CAGE clamp_overlap', bev_src.use_clamp_overlap, 'harden', bev_src.harden_normals, 'WN', wn_src.mode, wn_src.keep_sharp, 'weighted edges', len(segs), 'smooth', [p.use_smooth for p in me.polygons][:1]) # Diagnostic: compare the cage's bevel-weight edges with the crease edges exported with the hull fixture. fx = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'fixtures') HV = [Vector(tuple(map(float, l.split()[1:4]))) for l in open(fx + '/hull_nobevel.obj') if l.startswith('v ')] @@ -102,6 +102,7 @@ def build(stem, tubes): for k in ('width', 'segments', 'limit_method', 'offset_type', 'profile', 'harden_normals', 'miter_outer', 'miter_inner', 'clamp_overlap', 'loop_slide'): if hasattr(bev_src, k): setattr(b, k, getattr(bev_src, k)) + b.use_clamp_overlap = bev_src.use_clamp_overlap if os.environ.get('CLAMP0') != '1' else False w = ob.modifiers.new('WeightedNormal', 'WEIGHTED_NORMAL') for k in ('mode', 'weight', 'thresh', 'keep_sharp', 'use_face_influence'): if hasattr(wn_src, k): diff --git a/spikes/boolean-weld/py/round3.py b/spikes/boolean-weld/py/round3.py new file mode 100644 index 0000000..2e14938 --- /dev/null +++ b/spikes/boolean-weld/py/round3.py @@ -0,0 +1,280 @@ +"""Round 3 (Self's calls): a flat split keeps its seam exactly on the cutter plane. Hull vertices survive; hull-side +slivers along the seam get edge flips only (loop and crease edges constrained); each cap polygon is re-triangulated +with a constrained Delaunay (loop as boundary), plain ('p') or with interior-only Steiner refinement ('pqY'). +Usage: round3.py . Scratch. Uses the `triangle` package (Shewchuk's Triangle).""" +import sys, os, json, numpy as np +from collections import defaultdict +from scipy.spatial import cKDTree +import triangle +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from geo import * +from weld import Hull, Cutter, loops_of, densify, angles + +fx, casesf, runs, outd = sys.argv[1:5] +os.makedirs(outd, exist_ok=True) +hull = Hull(fx); TOL = 1e-6 * hull.diag +cases = {c['name']: c for c in json.load(open(casesf))} +FLIP_DIHEDRAL_COS = np.cos(np.radians(20)) + + +def load_run(name): + V, F = load_obj(f'{runs}/boolmesh_{name}.obj'); T = triangulate(F) + return V, T, np.array([int(x) for x in open(f'{runs}/boolmesh_{name}.prov').read().split()]) + + +def tri_min_angle(V, t): + return min(angles(V[list(t)])) + + +def flip_hull(V, T, prov, loopedges, band_faces): + """Greedy Delaunay-style flips on hull-provenance faces near the loop. Constrained: loop edges, hull crease edges. + A flip must raise the pair's worst angle, keep both normals within 20 deg of the old ones, and not duplicate an edge.""" + T = [list(t) for t in T]; nflip = 0 + crease_pos = hull.crease + for _ in range(20): + ef = defaultdict(list) + for fi, t in enumerate(T): + for k in range(3): + a, b = t[k], t[(k + 1) % 3]; ef[(min(a, b), max(a, b))].append(fi) + changed = False + for e, fs in ef.items(): + if len(fs) != 2 or e in loopedges: + continue + f1, f2 = fs + if prov[f1] or prov[f2] or not (f1 in band_faces or f2 in band_faces): + continue + he = hull.edge_of(V[e[0]], TOL), hull.edge_of(V[e[1]], TOL) + if any(h is not None and h in crease_pos for h in he) and he[0] == he[1]: + continue + a, b = e; c = [x for x in T[f1] if x not in e][0]; d = [x for x in T[f2] if x not in e][0] + if (min(c, d), max(c, d)) in ef: + continue + old = min(tri_min_angle(V, T[f1]), tri_min_angle(V, T[f2])) + # keep orientation: T[f1] has a->b in some order; build new faces with the same winding + t1 = T[f1]; i = t1.index(c); n1 = [t1[i], t1[(i + 1) % 3], t1[(i + 2) % 3]] # c, p, q + p, q = n1[1], n1[2] + new1, new2 = [c, p, d], [c, d, q] + new = min(tri_min_angle(V, new1), tri_min_angle(V, new2)) + if new <= old + 1e-9: + continue + on = [np.cross(V[t[1]] - V[t[0]], V[t[2]] - V[t[0]]) for t in (T[f1], T[f2])] + nn = [np.cross(V[t[1]] - V[t[0]], V[t[2]] - V[t[0]]) for t in (new1, new2)] + on = [x / np.linalg.norm(x) for x in on]; nn = [x / max(np.linalg.norm(x), 1e-300) for x in nn] + if min(o @ n for o in on for n in nn) < FLIP_DIHEDRAL_COS: + continue + T[f1], T[f2] = new1, new2; nflip += 1; changed = True + break # edge map is stale after a flip + if not changed: + break + return np.array(T), nflip + + +def flip_hull_all(V, T, prov, loopedges, band_faces, cap=4000): + total = 0 + for _ in range(cap // 20): + T, n = flip_hull(V, T, prov, loopedges, band_faces); total += n + if n == 0: + break + return T, total + + +def cdt_caps(V, T, prov, cut, opts): + """Replace every cap (cutter-provenance) face set with a CDT of its boundary loop in the cutter plane.""" + loops, _ = loops_of(T, prov) + capF = np.where(prov == 1)[0] + old_n = face_normals(V, T[capF])[0].mean(0) + keep = [i for i in range(len(T)) if prov[i] == 0] + newV = [V]; newT = []; nv = len(V) + for L in loops: + P = V[L]; n, _ = cut.frame(P.mean(0)) + e1 = np.cross(n, [1, 0, 0] if abs(n[0]) < .9 else [0, 1, 0]); e1 /= np.linalg.norm(e1); e2 = np.cross(n, e1) + o = P.mean(0); P2 = np.stack([(P - o) @ e1, (P - o) @ e2], 1) + seg = np.array([[i, (i + 1) % len(L)] for i in range(len(L))]) + r = triangle.triangulate({'vertices': P2, 'segments': seg}, opts) + rv, rt = r['vertices'], r['triangles'] + assert np.allclose(rv[:len(L)], P2), 'Triangle moved a boundary vertex' + idx = list(L) + list(range(nv, nv + len(rv) - len(L))) + if len(rv) > len(L): + newV.append(o + rv[len(L):, :1] * e1 + rv[len(L):, 1:2] * e2); nv += len(rv) - len(L) + for t in rt: + tt = [idx[k] for k in t]; newT.append(tt) + V2 = np.vstack(newV); NT = np.array(newT) + nn = face_normals(V2, NT)[0].mean(0) + if nn @ old_n < 0: + NT = NT[:, ::-1] + T2 = np.vstack([T[keep], NT]); P2 = np.concatenate([prov[keep], np.ones(len(NT), int)]) + return V2, T2, P2 + + +def side_measures(V, T, prov, ref_loop_pts, plane=None): + loops, le = loops_of(T, prov); lv = sorted({v for L in loops for v in L}) + h = float(np.median(hull.vlen[hull.kd.query(V[lv])[1]])) + kd = cKDTree(densify(V, loops)); cen = V[T].mean(1); inb = kd.query(cen)[0] < 2 * h + FN, FA = face_normals(V, T); lmax = np.linalg.norm(V[T] - V[np.roll(T, -1, 1)], axis=2).max(1) + sl = (lmax ** 2 / np.maximum(2 * FA, 1e-300) > 10) | (FA < 1e-4 * FA[prov == 0].mean()) + ang = np.array([tri_min_angle(V, t) for t in T]) + hs, cs = inb & (prov == 0), prov == 1 + flips = 0 + for fi in np.where(hs)[0]: + n, _, _ = hull.frame(cen[fi]); flips += int(FN[fi] @ n < 0) + out = dict(h=h, hull_band_tris=int(hs.sum()), hull_slivers=int((sl & hs).sum()), hull_min_angle=float(ang[hs].min()), + hull_p5=float(np.percentile(ang[hs], 5)), cap_tris=int(cs.sum()), cap_slivers=int((sl & cs).sum()), + cap_min_angle=float(ang[cs].min()) if cs.any() else None, flipped_hull=flips, + loop_verts=len(lv), loop_to_naive_max=float(cKDTree(ref_loop_pts).query(V[lv])[0].max())) + if plane is not None: + n, d = plane; out['seam_off_plane_max'] = float(np.abs(V[lv] @ n - d).max()) + ls = [np.linalg.norm(V[a] - V[b]) for a, b in edge_faces(T) if a in set(lv) or b in set(lv)] + out['loop_edges_under_bevel'] = int(sum(l < 0.001 for l in ls)) + return out + + +def save(stem, V, T, prov): + save_obj(f'{outd}/{stem}.obj', V, T) + _, le = loops_of(T, prov) + with open(f'{outd}/{stem}.loop', 'w') as f: + for a, b in le: + f.write(f'{a} {b} 0\n') + + +def boundary_loops(T): + nxt = {} + ef = edge_faces(T) + for t in T: + for k in range(3): + a, b = int(t[k]), int(t[(k + 1) % 3]) + if len(ef[(min(a, b), max(a, b))]) == 1: + nxt[a] = b + loops, seen = [], set() + for s0 in list(nxt): + if s0 in seen: + continue + L = [s0]; seen.add(s0); x = nxt[s0] + while x != s0 and x not in seen: + L.append(x); seen.add(x); x = nxt[x] + loops.append(L) + return loops + + +def short_loop_merge(pieces, bevel_w=0.001): + """Split only: merge consecutive seam vertices closer than the bevel width, both pieces jointly. Both endpoints lie + on the cutter plane, so the seam stays on it; only boolean-inserted seam vertices are merge sources (hull vertices + survive). Caps are dropped first (rebuilt afterwards by the CDT), so the guard sees hull faces only.""" + import owner_weld as OW + Ps = [OW.Piece(V, T[p == 0], np.zeros(int((p == 0).sum()), int)) for V, T, p in pieces] + L = boundary_loops(np.array([f for f in Ps[0].faces])) + st = defaultdict(int); n = 0 + for lp in L: + for i in range(len(lp)): + a, b = lp[i], lp[(i + 1) % len(lp)] + pa, pb = Ps[0].V[a], Ps[0].V[b] + if not (Ps[0].alive[a] and Ps[0].alive[b]) or np.linalg.norm(pa - pb) >= bevel_w: + continue + src, dst = (pb, pa) if hull.kd.query(pb)[0] > TOL else (pa, pb) + if hull.kd.query(src)[0] < TOL: + st['refused_both_hull_vertices'] += 1; continue + plan = [] + for P in Ps: + v, w = P.at(src, TOL), P.at(dst, TOL) + if v is None or w is None: + plan = None; st['refused_missing'] += 1; break + r = P.check_collapse(v, w) + if r: + plan = None; st['refused_' + r] += 1; break + plan.append((P, v, w)) + if plan: + for P, v, w in plan: + P.apply_collapse(v, w) + n += 1 + out = [] + for P in Ps: + V, T, _ = P.result(); out.append((V, T)) + return out, n, dict(st) + + +def cdt_boundary(V, T, cut, opts, flip_orient): + newV, newT, nv = [V], [], len(V) + for L in boundary_loops(T): + P = V[L]; n, _ = cut.frame(P.mean(0)) + e1 = np.cross(n, [1, 0, 0] if abs(n[0]) < .9 else [0, 1, 0]); e1 /= np.linalg.norm(e1); e2 = np.cross(n, e1) + o = P.mean(0); P2 = np.stack([(P - o) @ e1, (P - o) @ e2], 1) + r = triangle.triangulate({'vertices': P2, 'segments': np.array([[i, (i + 1) % len(L)] for i in range(len(L))])}, opts) + idx = list(L) + list(range(nv, nv + len(r['vertices']) - len(L))) + if len(r['vertices']) > len(L): + newV.append(o + r['vertices'][len(L):, :1] * e1 + r['vertices'][len(L):, 1:2] * e2); nv += len(r['vertices']) - len(L) + # boundary runs a->b in the hull faces, so the cap must run b->a: orient each cap triangle against the loop + tri = [[idx[k] for k in t] for t in r['triangles']] + newT += tri + V2 = np.vstack(newV); NT = np.array(newT) + # orientation: the cap must use each boundary edge in the opposite direction to the hull face that owns it + hd = {(int(t[k]), int(t[(k + 1) % 3])) for t in T for k in range(3)} + bad = sum(1 for t in NT for k in range(3) if (int(t[k]), int(t[(k + 1) % 3])) in hd) + if bad: + NT = NT[:, ::-1] + return V2, np.vstack([T, NT]), np.concatenate([np.zeros(len(T), int), np.ones(len(NT), int)]) + + +rows = [] +for base in [b for b in ('split_fwd', 'probe_crease') if os.environ.get('SKIP_SPLITS') != '1']: + c = cases[base + '_sub']; cut = Cutter(c) + lo, hi = np.array(c['lo']), np.array(c['hi']) + pieces = {'sub': load_run(base + '_sub'), 'int': load_run(base + '_int')} + V0, T0, p0 = pieces['sub']; L0, _ = loops_of(T0, p0); refl = V0[sorted({v for L in L0 for v in L})] + n, _ = cut.frame(refl.mean(0)); plane = (n, float(refl.mean(0) @ n)) + out = {} + for variant, opts in ((('naive', None), ('flip+cdt', 'p'), ('flip+cdt-q', 'pq20Y')) if os.environ.get('ONLY_SHORT') != '1' else (('naive', None),)): + for pk, (V, T, p) in pieces.items(): + if variant != 'naive': + loops, le = loops_of(T, p); lset = {(min(a, b), max(a, b)) for a, b in le} + kd = cKDTree(densify(V, loops)); h = float(np.median(hull.vlen)) + band = set(np.where(kd.query(V[T].mean(1))[0] < 2 * 0.0256)[0].tolist()) + T1, nf = flip_hull_all(V, T, p, lset, band) + V2, T2, P2 = cdt_caps(V, T1, p, cut, opts) + else: + V2, T2, P2, nf = V, T, p, 0 + m = side_measures(V2, T2, P2, refl, plane) + out[(variant, pk)] = (V2, T2, P2) + rows.append(dict(case=f'{base}_{pk}', variant=variant, flips_applied=nf, **m)) + save(f'{base}_{pk}_{variant}', V2, T2, P2) + A, B = out[(variant, 'sub')], out[(variant, 'int')] + la = A[0][sorted({v for L in loops_of(A[1], A[2])[0] for v in L})]; lb = B[0][sorted({v for L in loops_of(B[1], B[2])[0] for v in L})] + rows.append(dict(case=base, variant=f'{variant} seam crack', crack=float(max(cKDTree(lb).query(la)[0].max(), cKDTree(la).query(lb)[0].max())))) + merged, nm, stm = short_loop_merge([pieces['sub'], pieces['int']]) + naive_dense = cKDTree(densify(V0, L0, 64)); res = {} + for pk, (Vm, Tm) in zip(('sub', 'int'), merged): + p0 = np.zeros(len(Tm), int); L = boundary_loops(Tm) + lset = {(min(a, b), max(a, b)) for lp in L for a, b in zip(lp, lp[1:] + lp[:1])} + kd = cKDTree(densify(Vm, L)); band = set(np.where(kd.query(Vm[Tm].mean(1))[0] < 2 * 0.0256)[0].tolist()) + T1, nf = flip_hull_all(Vm, Tm, p0, lset, band) + V2, T2, P2 = cdt_boundary(Vm, T1, cut, 'pq20Y', None) + m = side_measures(V2, T2, P2, refl, plane) + chord = float(naive_dense.query(densify(V2, loops_of(T2, P2)[0], 16))[0].max()) + rows.append(dict(case=f'{base}_{pk}', variant='short+flip+cdt-q', flips_applied=nf, merges=nm, merge_stats=stm, + seam_chord_dev=chord, seam_chord_dev_pct=chord / hull.diag * 100, **m)) + save(f'{base}_{pk}_short+flip+cdt-q', V2, T2, P2); res[pk] = (V2, T2, P2) + A, B = res['sub'], res['int'] + la = A[0][sorted({v for L in loops_of(A[1], A[2])[0] for v in L})]; lb = B[0][sorted({v for L in loops_of(B[1], B[2])[0] for v in L})] + rows.append(dict(case=base, variant='short+flip+cdt-q seam crack', crack=float(max(cKDTree(lb).query(la)[0].max(), cKDTree(la).query(lb)[0].max())))) + print('done', base, nm, stm, flush=True) + +# Box cut-ins into the curved hull: the flat faces own nothing (the hull owns every stretch, corners kept), so the +# hull-side repair is the same edge flips; the pocket walls and floor are the cutter's own faces, not a cap. +for base in [b for b in ('probe_box_flank', 'probe_box_ridge') if b in cases]: + c = cases[base]; cut = Cutter(c); V, T, p = load_run(base) + L0, le = loops_of(T, p); refl = V[sorted({v for L in L0 for v in L})] + lset = {(min(a, b), max(a, b)) for a, b in le} + kd = cKDTree(densify(V, L0)); band = set(np.where(kd.query(V[T].mean(1))[0] < 2 * 0.0256)[0].tolist()) + for variant in ('naive', 'flip'): + T2, nf = (T, 0) if variant == 'naive' else flip_hull_all(V, T, p, lset, band) + m = side_measures(V, T2, p, refl) + rows.append(dict(case=base, variant=variant, flips_applied=nf, **m)); save(f'{base}_{variant}', V, T2, p) + print('done', base, flush=True) + +json.dump(rows, open(f'{outd}/measures3.json', 'w'), indent=1, default=float) +cols = ['case', 'variant', 'merges', 'seam_chord_dev_pct', 'flips_applied', 'hull_band_tris', 'hull_slivers', 'hull_min_angle', 'hull_p5', 'cap_tris', 'cap_slivers', + 'cap_min_angle', 'flipped_hull', 'seam_off_plane_max', 'loop_to_naive_max', 'loop_edges_under_bevel', 'crack'] +f = lambda v: '' if v is None else (f'{v:.3g}' if isinstance(v, float) else str(v)) +with open(f'{outd}/table3.md', 'w') as o: + o.write('| ' + ' | '.join(cols) + ' |\n|' + '---|' * len(cols) + '\n') + for r in rows: + o.write('| ' + ' | '.join(f(r.get(k)) for k in cols) + ' |\n') +print(open(f'{outd}/table3.md').read()) diff --git a/spikes/boolean-weld/py/run2.py b/spikes/boolean-weld/py/run2.py index 960783b..ca3e6d1 100644 --- a/spikes/boolean-weld/py/run2.py +++ b/spikes/boolean-weld/py/run2.py @@ -46,6 +46,8 @@ def row(name, variant, V, T, prov, c, ref, extra): for name, c in cases.items(): + if os.environ.get('CASES') and name not in os.environ['CASES'].split(','): + continue if c['kind'] == 'box' and c['op'] == 'int': continue cut = Cutter(c) diff --git a/spikes/boolean-weld/py/weld.py b/spikes/boolean-weld/py/weld.py index 660569d..cad4b44 100644 --- a/spikes/boolean-weld/py/weld.py +++ b/spikes/boolean-weld/py/weld.py @@ -87,6 +87,8 @@ class Cutter: def __init__(s, c): s.c = c; s.kind = c['kind'] Vc, Fc = load_obj(c['cutter']); s.V, s.T = Vc, triangulate(Fc); s.adj = vertex_adj(s.T, len(Vc)) + if s.kind == 'obox': + s.p = np.array(c['p']); s.R = np.array(c['R']); s.lo = np.array(c['lo']); s.hi = np.array(c['hi']) if s.kind == 'cyl': s.p, s.r = np.array(c['p']), c['r'] s.a = np.array(c['n']) / np.linalg.norm(c['n']) @@ -107,6 +109,10 @@ class Cutter: d = x - s.p; er = d - (d @ s.a) * s.a; er = er / np.linalg.norm(er) et = np.cross(s.a, er) return er, np.outer(et, et) / s.r + if s.kind == 'obox': + xl = s.R.T @ (x - s.p); dd = np.concatenate([xl - s.lo, s.hi - xl]); i = int(np.argmin(np.abs(dd))) + nl = np.zeros(3); nl[i % 3] = -1 if i < 3 else 1 + return s.R @ nl, np.zeros((3, 3)) lo, hi = np.array(s.c['lo']), np.array(s.c['hi']) dd = np.concatenate([x - lo, hi - x]); i = int(np.argmin(np.abs(dd))) n = np.zeros(3); n[i % 3] = -1 if i < 3 else 1 diff --git a/spikes/boolean-weld/results/round3/table2-owner-r3a5.md b/spikes/boolean-weld/results/round3/table2-owner-r3a5.md new file mode 100644 index 0000000..9b35120 --- /dev/null +++ b/spikes/boolean-weld/results/round3/table2-owner-r3a5.md @@ -0,0 +1,24 @@ +| case | variant | applied | tris_band | slivers_band | min_angle_band | p5_angle_band | flipped_hull_band | dev_max_pct | dev_max_h | outside_band_moved | shortest_loop_edge | loop_edges_under_bevel | hull_owned | cutter_owned | split_loop_gap | hull_vertex_surface_dev_h | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| port_flank_s16 | naive | | 157 | 32 | 0.00707 | 1.14 | 0 | 0.0198 | 0.00961 | 0 | 5.29e-05 | 6 | | | | | +| port_flank_s16 | owner-weld[r3a5] | 12 | 133 | 5 | 5.09 | 6.68 | 0 | 0.0198 | 0.00961 | 0 | 0.00226 | 0 | 0 | 36 | | 0.0175 | +| split_fwd_sub | naive | | 507 | 97 | 0.0584 | 1.46 | 0 | 0 | 0 | 0 | 0.000263 | 15 | | | | | +| split_fwd_sub | owner-weld[r3a5] | 0 | 507 | 97 | 0.0584 | 1.46 | 0 | 0 | 0 | 0 | 0.000263 | 15 | 124 | 0 | | 0 | +| split_fwd_int | naive | | 445 | 92 | 0.0555 | 1.52 | 0 | 0 | 0 | 0 | 0.000263 | 14 | | | | | +| split_fwd_int | owner-weld[r3a5] | 0 | 445 | 92 | 0.0555 | 1.52 | 0 | 0 | 0 | 0 | 0.000263 | 14 | 124 | 0 | | 0 | +| split_fwd_sub | seam-gap-naive[r3a5] | | | | | | | | | | | | | | 0 | | +| split_fwd_sub | seam-gap-welded[r3a5] | | | | | | | | | | | | | | 0 | | +| probe_crease_sub | naive | | 342 | 44 | 0.0326 | 2.41 | 0 | 0 | 0 | 0 | 1.46e-05 | 12 | | | | | +| probe_crease_sub | owner-weld[r3a5] | 0 | 342 | 44 | 0.0326 | 2.41 | 0 | 0 | 0 | 0 | 1.46e-05 | 12 | 84 | 0 | | 0 | +| probe_crease_int | naive | | 319 | 47 | 0.0326 | 2.7 | 0 | 0 | 0 | 0 | 1.46e-05 | 11 | | | | | +| probe_crease_int | owner-weld[r3a5] | 0 | 319 | 47 | 0.0326 | 2.7 | 0 | 0 | 0 | 0 | 1.46e-05 | 11 | 84 | 0 | | 0 | +| probe_crease_sub | seam-gap-naive[r3a5] | | | | | | | | | | | | | | 0 | | +| probe_crease_sub | seam-gap-welded[r3a5] | | | | | | | | | | | | | | 0 | | +| probe_switch_s48 | naive | | 488 | 112 | 1.21e-06 | 0.624 | 0 | 0.0128 | 0.00624 | 0 | 6.38e-06 | 31 | | | | | +| probe_switch_s48 | owner-weld[r3a5] | 65 | 364 | 53 | 1.21e-06 | 2.65 | 0 | 0.323 | 0.157 | 0 | 4.71e-05 | 16 | 24 | 108 | | 0.021 | +| probe_crease_cyl_s16 | naive | | 206 | 27 | 0.208 | 2.6 | 0 | 0.0434 | 0.0146 | 0 | 0.000247 | 4 | | | | | +| probe_crease_cyl_s16 | owner-weld[r3a5] | 27 | 133 | 10 | 4.31 | 6.83 | 0 | 0.0609 | 0.0229 | 0 | 0.00135 | 0 | 6 | 35 | | 0.00665 | +| probe_box_flank | naive | | 105 | 3 | 0.285 | 6.62 | 0 | 0 | 0 | 0 | 0.0025 | 0 | | | | | +| probe_box_flank | owner-weld[r3a5] | 2 | 103 | 2 | 0.285 | 9.61 | 0 | 0.0108 | 0.00534 | 0 | 0.0025 | 0 | 16 | 0 | | 0 | +| probe_box_ridge | naive | | 314 | 67 | 0.0703 | 1.36 | 0 | 0 | 0 | 0 | 0.000637 | 4 | | | | | +| probe_box_ridge | owner-weld[r3a5] | 1 | 312 | 66 | 0.0703 | 1.41 | 0 | 0.00142 | 0.000687 | 0 | 0.000637 | 3 | 69 | 0 | | 0 | diff --git a/spikes/boolean-weld/results/round3/table3-boxes-flip.md b/spikes/boolean-weld/results/round3/table3-boxes-flip.md new file mode 100644 index 0000000..b71509f --- /dev/null +++ b/spikes/boolean-weld/results/round3/table3-boxes-flip.md @@ -0,0 +1,6 @@ +| case | variant | flips_applied | hull_band_tris | hull_slivers | hull_min_angle | hull_p5 | cap_tris | cap_slivers | cap_min_angle | flipped_hull | seam_off_plane_max | loop_to_naive_max | loop_edges_under_bevel | crack | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| probe_box_flank | naive | 0 | 83 | 2 | 0.952 | 8.55 | 22 | 1 | 0.285 | 0 | | 0 | 0 | | +| probe_box_flank | flip | 24 | 84 | 0 | 6 | 12.1 | 22 | 1 | 0.285 | 0 | | 0 | 0 | | +| probe_box_ridge | naive | 0 | 243 | 19 | 0.715 | 4.9 | 71 | 48 | 0.0703 | 0 | | 0 | 4 | | +| probe_box_ridge | flip | 118 | 243 | 7 | 1.75 | 7.24 | 71 | 48 | 0.0703 | 0 | | 0 | 4 | | diff --git a/spikes/boolean-weld/results/round3/table3-short-merge-and-boxes.md b/spikes/boolean-weld/results/round3/table3-short-merge-and-boxes.md new file mode 100644 index 0000000..59f9f82 --- /dev/null +++ b/spikes/boolean-weld/results/round3/table3-short-merge-and-boxes.md @@ -0,0 +1,18 @@ +| case | variant | merges | seam_chord_dev_pct | flips_applied | hull_band_tris | hull_slivers | hull_min_angle | hull_p5 | cap_tris | cap_slivers | cap_min_angle | flipped_hull | seam_off_plane_max | loop_to_naive_max | loop_edges_under_bevel | crack | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| split_fwd_sub | naive | | | 0 | 410 | 37 | 0.676 | 3.86 | 122 | 85 | 0.0584 | 0 | 3.05e-16 | 0 | 15 | | +| split_fwd_int | naive | | | 0 | 348 | 32 | 0.539 | 4.36 | 122 | 85 | 0.0555 | 0 | 3.05e-16 | 0 | 14 | | +| split_fwd | naive seam crack | | | | | | | | | | | | | | | 0 | +| split_fwd_sub | short+flip+cdt-q | 3 | 0.00692 | 138 | 407 | 26 | 0.676 | 5.3 | 313 | 1 | 5.5 | 0 | 3.05e-16 | 0 | 16 | | +| split_fwd_int | short+flip+cdt-q | 3 | 0.00692 | 99 | 343 | 17 | 0.539 | 6.69 | 313 | 1 | 5.5 | 0 | 3.05e-16 | 0 | 16 | | +| split_fwd | short+flip+cdt-q seam crack | | | | | | | | | | | | | | | 0 | +| probe_crease_sub | naive | | | 0 | 260 | 22 | 0.377 | 3.73 | 82 | 22 | 0.0326 | 0 | 7.22e-16 | 0 | 12 | | +| probe_crease_int | naive | | | 0 | 237 | 25 | 0.0405 | 5.61 | 82 | 22 | 0.0326 | 0 | 7.22e-16 | 0 | 11 | | +| probe_crease | naive seam crack | | | | | | | | | | | | | | | 0 | +| probe_crease_sub | short+flip+cdt-q | 5 | 0.00404 | 65 | 255 | 12 | 2.41 | 6.64 | 147 | 3 | 0.714 | 0 | 7.22e-16 | 0 | 6 | | +| probe_crease_int | short+flip+cdt-q | 5 | 0.00404 | 79 | 234 | 10 | 0.0405 | 7.14 | 147 | 3 | 0.714 | 0 | 7.22e-16 | 0 | 5 | | +| probe_crease | short+flip+cdt-q seam crack | | | | | | | | | | | | | | | 0 | +| probe_box_flank | naive | | | 0 | 83 | 2 | 0.952 | 8.55 | 22 | 1 | 0.285 | 0 | | 0 | 0 | | +| probe_box_flank | flip | | | 24 | 84 | 0 | 6 | 12.1 | 22 | 1 | 0.285 | 0 | | 0 | 0 | | +| probe_box_ridge | naive | | | 0 | 243 | 19 | 0.715 | 4.9 | 71 | 48 | 0.0703 | 0 | | 0 | 4 | | +| probe_box_ridge | flip | | | 118 | 243 | 7 | 1.75 | 7.24 | 71 | 48 | 0.0703 | 0 | | 0 | 4 | | diff --git a/spikes/boolean-weld/results/round3/table3-splits-flip-cdt.md b/spikes/boolean-weld/results/round3/table3-splits-flip-cdt.md new file mode 100644 index 0000000..f2804ad --- /dev/null +++ b/spikes/boolean-weld/results/round3/table3-splits-flip-cdt.md @@ -0,0 +1,20 @@ +| case | variant | flips_applied | hull_band_tris | hull_slivers | hull_min_angle | hull_p5 | cap_tris | cap_slivers | cap_min_angle | flipped_hull | seam_off_plane_max | loop_to_naive_max | loop_edges_under_bevel | crack | +|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---| +| split_fwd_sub | naive | 0 | 410 | 37 | 0.676 | 3.86 | 122 | 85 | 0.0584 | 0 | 3.05e-16 | 0 | 15 | | +| split_fwd_int | naive | 0 | 348 | 32 | 0.539 | 4.36 | 122 | 85 | 0.0555 | 0 | 3.05e-16 | 0 | 14 | | +| split_fwd | naive seam crack | | | | | | | | | | | | | 0 | +| split_fwd_sub | flip+cdt | 138 | 410 | 28 | 0.676 | 4.69 | 122 | 80 | 0.122 | 0 | 3.05e-16 | 0 | 15 | | +| split_fwd_int | flip+cdt | 99 | 346 | 18 | 0.539 | 5.97 | 122 | 80 | 0.122 | 0 | 3.05e-16 | 0 | 14 | | +| split_fwd | flip+cdt seam crack | | | | | | | | | | | | | 0 | +| split_fwd_sub | flip+cdt-q | 138 | 410 | 28 | 0.676 | 4.69 | 332 | 0 | 6.76 | 0 | 3.05e-16 | 0 | 23 | | +| split_fwd_int | flip+cdt-q | 99 | 346 | 18 | 0.539 | 5.97 | 332 | 0 | 6.76 | 0 | 3.05e-16 | 0 | 22 | | +| split_fwd | flip+cdt-q seam crack | | | | | | | | | | | | | 0 | +| probe_crease_sub | naive | 0 | 260 | 22 | 0.377 | 3.73 | 82 | 22 | 0.0326 | 0 | 7.22e-16 | 0 | 12 | | +| probe_crease_int | naive | 0 | 237 | 25 | 0.0405 | 5.61 | 82 | 22 | 0.0326 | 0 | 7.22e-16 | 0 | 11 | | +| probe_crease | naive seam crack | | | | | | | | | | | | | 0 | +| probe_crease_sub | flip+cdt | 67 | 260 | 19 | 0.377 | 4.25 | 82 | 21 | 0.575 | 0 | 7.22e-16 | 0 | 13 | | +| probe_crease_int | flip+cdt | 79 | 238 | 11 | 0.0405 | 7.1 | 82 | 21 | 0.575 | 0 | 7.22e-16 | 0 | 12 | | +| probe_crease | flip+cdt seam crack | | | | | | | | | | | | | 0 | +| probe_crease_sub | flip+cdt-q | 67 | 260 | 19 | 0.377 | 4.25 | 162 | 4 | 0.714 | 0 | 7.22e-16 | 0 | 18 | | +| probe_crease_int | flip+cdt-q | 79 | 238 | 11 | 0.0405 | 7.1 | 162 | 4 | 0.714 | 0 | 7.22e-16 | 0 | 17 | | +| probe_crease | flip+cdt-q seam crack | | | | | | | | | | | | | 0 |