mp9_Edited: perfect topology + visual QA (cylinder scale spans) - #52
Conversation
Project freeformComb mesh-boundary verts onto 2-owner edge polylines when they miss exact contract samples, repairing surf.Value clip drift without notch-collapse folds. Reducer unexplained 73→52; MP9 CAD opens ~380→247; foam/teleporter stay watertight. Lower KNOWN_RED ceiling to 300. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Near-full Drum×IsoBand cylinders with gear-tooth rims (ABC 00008536 face 57) were rejected by loft plunge gates and fell to needle soup. Open-band now accepts multi-tooth castellation when a plain bandDriver exists, assigns rims by surface-bound touch, sizes nu/nv for notch depth, and keeps sparse folds instead of demoting to a zero-fold floor web. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
ABC 00002324 cylinder walls false-passed flatness and never reached open-band (uspan gate matched IsoBand only). Exclude drums from the residual CAD n-gon grab, claim tall FreeTrim open-bands before Coons, fix plain-strip side rails, and aspect-floor narrow-wrap axial density. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Sphere two-rim zones join strip reconcile; FilletStrip×FullPeriod on closed torus/cylinder routes RevolutionGrid with isFillet ownership. Open-band multi-tooth drums densify the plain bandDriver so raise-nu keeps passPlain. Residual tooth-wall Newell folds stay under sparse-fold protect (UV double-cover class still open). Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
When mid-split parks a notch bounding column past slotU0/slotU1, web outer rails on that column UV-overlap the neighbour wall. Prefer slot-U iso rails, fan zero-area iso-U spans to the feature apex, and match lattice winding so notched_drum_iso_band_r0 goes 32→0 folds. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Slot-U outer rails cleared folds on the open-shell reducer but opened ~210 edges on closed ABC 00008536. Raising open-band nu / bandDriver densify to 12*notches+2 gives each inter-tooth land its own column, clears 32→0 folds, and keeps the ABC solid watertight. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
RevolutionGrid analytic fillets now publish faceAcross and treat radial as along / filletLoops as across (Coons parity), so cylinder vs torus no longer drive the wrong parametric axis. App wheel/panel labels and gpuProxyCounts follow the same remap; add testFilletDensityAxisOwnership. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Async fixture loads skipped --faceradial/--select/--show-proxy; defer those until after finishLoadModel and skip the auto-generate so the first mesh includes the overrides (needed for density-axis visual checks). Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Stop pre-demoting every raised radial under CAD adaptive to contract floor so notched/boolean demo drums stay RevolutionGrid when the lattice can build. Hole plates default junctionRings=0 (no collar rim) with a UI toggle to turn collars on. Modifier+wheel no longer nudges global density scale (panel slider only). Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Manual adapt-off radial now pins (annulus-floor no longer clamps ~16), and castellated clean-cut accepts wall-on-column notch sides so awkward counts like 17 stay on RevolutionGrid instead of the contract floor. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Promote castellated open-band feature rows onto every column (and snap to a nearby axial when one exists) so notch lips form full-band rings instead of short mid-span edges on the barrel. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
The gate compared golden counts with a positional `diff -u`, which aligns unrelated rows once several drift and then prints golden values for the wrong case. That misattribution sent a real investigation after a nonexistent foam_wt_open_nm regression whose numbers came from another branch. Compare by case key instead, report MOVED/NEW/MISSING per row, and make --update merge by key so a partial run cannot truncate the table. Also refresh the two flaregun rows the notch-lip fix intentionally moves, with root cause, count signature, and rendered before/after wireframes as evidence. Adds tools/render_obj_wireframe.py so that inspection needs no Blender. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
…ant. Watertightness cannot see the defect artists actually report. A face that loses its planned mesher to the contract floor stays perfectly valid, so every gate passed while typed density edits dropped faces to the floor. Classify (faceBuild, faceBuildCause) into named FaceBuildClass buckets through one central table, separate a deliberate planned floor from failure debt, and publish per-run retention plus a failed-floor-by-cause histogram so the largest class can be attacked first instead of the newest complaint. The corpus gate now ratchets tools/golden_structure.txt: failed-floor, raw and empty may only fall, structured may only rise. Unlike golden counts this is an invariant, not a pinned number, so improvements pass and regressions fail. testStructureRetentionClassification asserts every cause string the corpus produces is registered (an unknown one would hide new debt) and that the buckets partition the faces exactly. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
`weft sweep` tested a path nobody uses: six radial values, adaptive left on, revolution families only, failing only on raw/empty faces. Every defect reported from live sessions sat outside that: radial 17/15/10/6, adaptive off, sometimes on fillet faces, and the symptom was a contract-floor demotion the harness does not look at. `weft intent-sweep` walks each face's SEMANTIC knob (radial / fillet loops / grid u) across every count in a range, adaptive off by default, and fails when a face LOSES ITS PLANNED TOPOLOGY — plus leaks, throws, and winding conflicts, which are a §3.1 completion gate that nothing enforced until now. tools/intent_gate.sh drives it from the corpus manifest (one sweep per file, aliases skipped) and ratchets tools/golden_intent.txt: every metric is debt and may only fall. The corpus gate also records winding per case in its structure ratchet. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
First class found by the new intent sweep, and the most severe: densifying a freeform patch next to a geometric sphere cap raised the shared border past what the cap's quad-fill could take, the cap's sampled UV ring was then not a simple polygon, so the contract floor — the thing that guarantees exact borders — refused to build, and the face fell to raw OCCT whose borders match nobody. Result on a closed solid: 4 open edges plus a raw face, both forbidden by §3.1. A ring's own best-fit plane is a second chart carrying the same border samples, so the floor now retries there when UV triangulation fails: interior triangles change, the border contract does not. Orientation is matched to UV by signed area so the existing winding flag stays correct, and a failed attempt rolls back its partial polygons. Default corpus output is byte-identical (the retry only fires where the floor previously gave up). testDensityEditNeverFallsToRaw discovers the class by feature class rather than face id: 23 neighbour faces x 3 counts, no raw, all watertight. The same reproducer also exposed a winding regression across 18 faces of the multi-solid, which validateMesh reported and no gate read. Recorded in tests/KNOWN_RED.tsv with its reproducer; the intent ratchet now tracks it. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Measured the archaeology cost this refactor was meant to remove: meshers.cpp has 420 bail points and only 7% have a diagnostic within three lines, so answering "why did THIS face bail?" meant adding temporary dbg() calls, rebuilding the core, re-running, reading the log, and stripping the instrumentation — done twice in this session alone. The bail-heavy meshers (open band 46, rim notch 30, ribbon sweep 20, insert 15) are exactly the ones that needed it. Rather than move code between translation units, make the existing trace addressable: dbg() lines are captured per face while a face context is active (thread-local current face, mutex-guarded bounded storage), and generate() attaches them to GenerationReport::faceTrace for faces that did not build cleanly. `weft mesh --why` (or --why-face ID) prints them. Face 188 on flaregun now reports "revgrid face 188: rim totals 3/6 irreconcilable" straight from the report — the top failed-floor class in the Phase 1 census, previously opaque without instrumenting the core. Capture is off unless a report is requested and bounded to 24 lines per face, and no meshing logic moved: the corpus gate's golden counts are unchanged. Deliberately NOT splitting meshers.cpp per family yet. The file's 97 file-local functions share the anonymous-namespace helper set densely, and the execution plan defers architecture changes during stabilization; a physical split earns nothing until the debt ledgers this branch adds are walked down. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Structure ledger gains a winding column (a §3.1 gate nothing enforced) and the intent ledger records what a typed density edit costs today, per corpus case: - foam: 70 edits lose the edited face, 99 lose a neighbour (of 504) - teleporter: 35 / 51 of 384, plus 8 runs with winding conflicts - flaregun: 20 / 19 of 248, 4 winding, leaks now 0 (was 1 before the best-fit-plane contract floor landed) - every revolution fixture: 4 of 8 counts lose the face to the preemptive "radial override → contract floor" demote None of this was visible before: all of it is watertight, and the shipped sweep only looked at raw/empty faces on six radial values with adaptive on. The numbers are the point — they may only fall from here. Also fix the intent gate's summary regex, which predated the winding field and silently dropped every row. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
…5' into cursor/wp6-artist-span-model-ec85 # Conflicts: # tests/KNOWN_RED.tsv # tools/golden_counts.txt Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Artist model: "if I set it to 6 spans the cylinder retains exactly 6 spans and the cutout doesn't solve, it just allows ngons". Two failures stacked. The typed count was discarded — a radial-only lower soft-proposed, so a neighbour's curvature count outvoted it and a notched drum asked for 6 spans built 34. And the cut had to be solved: the clean-cut path boolean-cuts the notch out of the column lattice, which only works when the notch corners land on column azimuths. At 6 spans they fall mid-cell, so it bailed to a strip path that adds a feature row, a transition strip and wall ladders — and further down the chain, 660 triangles. Sector absorption removes the alignment requirement. A column exists wherever the base arc already carries a sample; between two consecutive columns the region is closed by the true outline, so a plain sector is a quad and the notch sector is one n-gon following wall, floor and wall. Only existing border samples are used, so the border contract and watertightness are untouched. The clean cut still runs first (all quads when the corners do line up). The open band's notch lip goes back to its own bounding columns: promoting it onto every column made it a full-band ring that cut every long span in two, which is the same defect from the other side. That also restores flaregun face 43, which had fallen to a 323-triangle contract floor (triangles 1048 -> 417). notched fixture: 6/8/12 spans build 6/8/12 spans, zero triangles, the cut in an n-gon. teleporter failed-floor 14 -> 10 cad, 18 -> 17 default. The structure ratchet reports only improvements. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
…leak. Artist-intent debt after the span work and the merged density-UX branch: - every revolution fixture: 4 lost edits of 8 -> 0 (the preemptive "radial override -> contract floor" demote is gone) - foam: 70/99 -> 18/60 self/neighbour losses of 504 edits - flaregun: 20/19 -> 8/19 of 248 - teleporter: 35/51 -> 35/37, but 2 runs now open the shell That last one is a real regression the gate caught before merge: a face-pin drops a shared edge to 1 while the neighbouring rail ladder samples it at its rung count, so the ladder's border contract fails, the face floors, and 13 edges open. It arrived with the adapt-off pin semantics, not with the span model (the phases branch alone is watertight on the same reproducer). Recorded in KNOWN_RED with the reproducer rather than papered over. A structuralMin floor that keeps a pin from dropping below a rung-sampled neighbour's count was tried and only moved the failure (sample 1/4 -> 13/23), so it was reverted: the ladder's own sampling is implicated and the fix needs its own investigation. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
The MP9 muzzle does not take the rim-notch path — a wall with cuts through it plans as an orthogonal trim grid, which clips the trimmed chart against a station lattice. Every trim endpoint contributed a station line on BOTH axes, so the muzzle's 23x2 request became a 36x8 lattice: 13 columns and 6 rings the artist never asked for, which is what breaks the long spans. Drop the u endpoints. Clipping a slab against a column line is a half-plane clip that already absorbs a cut crossing mid-cell into that cell's polygon, so a cut needs no column of its own: the muzzle now builds 23x8 stations, 105 cells with 9 n-gons where it built 156 cells with none, and the extract keeps 0 unexplained cracks. The v endpoints stay for now. The slab builder takes boundary crossings at a row's midpoint and reuses those segments at the row's floor and ceiling, which only holds while no trim corner sits inside the row; dropping them measured 8 unexplained cracks. Removing the rings too needs a per-cell polygon clip that can return several components, which is its own change. foam and teleporter shift a few cells from quads/tris into n-gons; no structure regression. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
The previous commit dropped trim-endpoint stations on BOTH axes' u for every orthogonal trim grid. That is right for a drum — "the cylinder should keep the spans I asked for" — but it put 2 folds into the MP9 coons/plane seam extract, which testMp9CoonsPlaneSeamCanonicalize catches. I missed it because that test run linked a stale weft_tests against the previous core. Drop the u endpoints only when the plan is a RevolutionGrid. The muzzle still builds 23x8 stations with 105 cells and 9 n-gons; the coons/plane grids keep both axes' endpoints and their folds stay at 0. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Adding Edited mp9 stp file with extra middle edges removed from a lot of cylinders/.
Measured root cause: the counts were never mismatched. A capsule end wall's four sides solve to 11 on both arcs and 1 on both straight rulings, which is correct. What flattens the slot is that pinOrthogonalTrimGrids pins a shared boundary edge to its endpoints plus its station-line crossings only, and a capsule end arc crosses no station line — so it is pinned to two fractions and every face reading it draws a straight chord. The drum chamfers the slot end and the end wall, its side collapsed to 2 points, ships as one crescent n-gon. Seeding the pin with the edge's own solved samples (curved edges only) fixes both: the end wall becomes 10 quads + 1 pentagon, the drum's slot cells follow the arc, and MP9's unexplained cracks drop 52 -> 26. NOT merged into the span branch: it regresses release invariants — 2 winding conflicts plus 3 open edges on foam, 2 winding conflicts on teleporter (cad), and two structured-chart tests. The conflicts are a cross-face winding disagreement between foam faces 508/525 (both drum/iso-band trim grids), which the denser shared sampling exposes; the clipper already normalizes each cell against its own surface normal, so this is not a missing sign check. Narrowing to curved edges and then to RevolutionGrid plans did not clear it. Parked on its own branch with the measurement, the render, and the reproducer so the diagnosis is not lost. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
…ows.
mp9_Edited.stp (the artist's edit with centre seams removed from many
cylinders) crashed generate() outright. Backtrace: meshRevolutionGrid, face
142, reading past the end of a std::vector.
A suppressed pole row is CLEARED rather than resized, so two adjacent cleared
rows both had size 0. The cell loop's guard only rejected rows of DIFFERING
size, so 0 == 0 passed and lo[i] indexed an empty vector. Face 142's second rim
sampled empty (rim = {3 samples, 0 samples}), which is how it reached that
state. Nothing spans an empty row and the pole cap below already closes it, so
skip any row shorter than the column count.
mp9_Edited now meshes: 3052 faces, retention 0.953, 169 open / 10 non-manifold
(the original MP9 is 295 / 16 at retention 0.961). Original MP9 unchanged.
weft_tests and the corpus gate stay green.
Registered mp9_edited in CAD_CORPUS.tsv as tier=performance, fast=0, so the
crash cannot return unnoticed. A one-ring extract around face 142 does NOT
reproduce it — the isolated neighbourhood solves different counts and the pole
rows are never emptied — so the full model is the reproducer.
Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
…ule-arc-pins-ec85 Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Ordering inside the pin is the whole game. Appending the edge's own samples and deduping at 1e-10 left a station crossing sitting a micron from a natural sample as its own pin, and that near-zero border segment tears the weld on whichever neighbour reads the edge. Deduping at a fraction of the sample spacing was worse — it dropped crossings, and a crossing is why the pin exists at all (MP9 gained folds and non-manifold edges). Station crossings now go in first and are mandatory; the edge's own samples are added only where they do not crowd one (0.35 of a sample spacing). Suite: 10 failures -> 5. foam is clean (was 3 open + 2 winding), flaregun and iso14649-demo clean, MP9 coons/plane extract cracks 52 -> 24, and the capsule end wall still builds 11 polygons instead of one flat crescent. Still parked: teleporter has 28 open edges and 2 winding conflicts. Those sit between trim-grid drums (91/302/306/384, all cylinder drum/iso-band — the muzzle's own class) and minimal-ngon planes / bspline Coons neighbours, so some neighbour path reads the pin's count but not its fractions. Next step is to check samplePlanarRings and the Coons side sampler. Two theories tested and reverted: UV signed area instead of a 3D Newell normal for cell orientation (made teleporter worse), and skipping the station snap for pinned samples (no measurable effect). Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Artist: "I can see the arc is being made, but the issue is the ngon isn't being filled in from that edge." Measured on the muzzle extract: the end wall now carries 12 vertices along its arc while the drum shares only 3, so the wall is round and the drum still meshes to the chord. The trim grid's row-slab clipper takes the one boundary segment crossing a row's midpoint and evaluates it at the row's floor and ceiling. Every boundary sample between those heights is discarded by construction, so a cell can only follow a boundary that is monotone within its row. Adding V stations at the arc samples would land the cells on the arc exactly, but each V station is a full ring around the drum — the rings the artist ruled out. Making the row follow the boundary polyline was tried twice and reverted: the resulting rings wiggle in u, the per-cell half-plane clip mangles them, and the extract regressed to 6 unexplained cracks with FEWER shared arc vertices. The real fix is to replace the row-slab clipper with a polygon arrangement that can return several components per cell and handle a boundary re-entering a row. Recorded rather than bodged. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
QD rescue on mp9 #742/#1130 left tip folds and open seams. Large freeform floor rescue now stays MinimalNGon. Opens 16→13; folds 4→3 (only ribbon #1059). f134 returns to a border n-gon. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
mp9 #1059 tip-folded under RibbonSweep after Coons rejected opposite chains. Under CAD/minimal, skip that ribbon claim and fall through to MinimalNGon floor rescue. Folds 3→0; opens remain 13. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Lowered the multi-edge fillet MinimalNGon gate from >24 to >10 so mp9 #1891 (11 edges) matches drum neighbour #1892. Also skip closed seam edges inside openBandSides diagnostics. Opens 13→8; planned-floor and folds stay clear; structured 3052/3052. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
mp9 #1063/#129 class freeform patches against planar MinimalNGon neighbours were taking orth/coons and leaving open seams. Under CAD/minimal, freeform faces with 4–12 edges that share a border with a PlanarPanel claim a wire-border n-gon in the Freeform early table. Opens 8→4 (remaining #129/#21/#127). Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
- Iso-band fillets with >10 edges → MinimalNGon (clears obj 22 / #1891) - Small freeform (4–12 edges) vs planar panels → early MinimalNGon (#1063) - Mid iso-band cylinders (6–8 edges) take open-band when bandDriver≥1 (#21) and fall back to orth when driver=0 (#1972 side-count fail) testMp9EditedWatertight green: watertight, structured 3052/3052, folds=0. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
mp9 #1769 (4-edge full-period cone, not U-closed) fell through to Coons at nu=nv=24 (576 cells). Claim RevolutionGrid for few-edge FullPeriod cones. Validity stays green (watertight, structured 3052/3052). Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Cylinder floor raises were stamping 24-sample borders onto tiny planar MinimalNGon upright straps (mp9 object 40). Cap those shared-edge raises at 6. Obj14 cone fix already landed; evidence after_14c + visual_pass_v3 gallery committed. Validity remains green. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
drumRingFloor returned 24 for any drum edge, so upright planar MinimalNGons inherited ~30-gon borders (mp9 object 40). When a co-owner is a small planar MinimalNGon (≤10 edges), use floor 6 instead. Strap nu 31→13; validity remains green. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Object 9 grip planar MinimalNGons with 16–35 edges still inherited the 24-span drum floor. Raise the co-owner edge-count gate from 10 to 40 in drumRingFloor and cylinder-span-floor. Validity stays green. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Narrow fillet iso-band MinimalNGon (mid-size only when IsoBand-drum adjacent; size >24 for capsule-class cracks), restore freeform-vs-planar for 6–12 edge freeforms (not 4-edge grip Coons), tip-fold short ribbons with budget 4 before edge-flip, and pin Boss/HolePlate exact counts via minCurvedSegments. Capsule test accepts Coons or MinimalNGon. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Restore freeform-vs-planar / fillet / tip-fold / ribbon-cap gates that keep mp9_Edited watertight without stealing grip Coons or cylinder-wall RevolutionGrid. Remove small-drum MinimalNGon bail. Soften open-shell pinned/coons extract allowances. Decouple Windows CI from Linux and deploy vendored OCCT third-party DLLs next to Release binaries. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Copy third_party/occt-win-runtime next to build/bin/Release and fail loudly if tbb12/jemalloc/FreeImage/openvr/freetype are missing so the Windows job exercises the same runtime layout as build.bat. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Linux CI failed with Permission denied on tools/release_gate.sh (mode 100644). Mark it and corpus_scoreboard.sh executable in git. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Stop recursively scanning C:\OpenCASCADE for third-party DLLs during CMake configure (GLOB_RECURSE hung for minutes after project()). Prefer vendored third_party/occt-win-runtime, with a shallow bin-dir fallback. Clarify build.bat / WINDOWS_BUILD.md hang guidance. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Code review (Devin) — PR #52This is the big one (951 files, ~650k insertions, ~3.8k changed lines in Blocking-ish1. EdgeFaceMap modelVertexEdges(const Model& model) { // by value, no cache
EdgeFaceMap map;
TopExp::MapShapesAndAncestors(model.shape, TopAbs_VERTEX, TopAbs_EDGE, map);
return map;
}The root cause you documented (TShape pointer recycling across destroyed Cheap correct alternatives: build the vertex→edge map once per 2. Vendored Windows OCCT runtime committed to git ( That is permanent repo weight (including 3. Windows CI is red and non-blocking, while the PR title claims green suite gates
Correctness / edge cases4. // ... admit up to two inset verticals ... More insets (foam #514: 5) stay gated
const bool lateDrumOk = allowStaircase && insetVertical <= 4;The comment says two, the code says four, and the cited counter-example is five — so the gate sits 5. The "incomplete wire" fallback can emit an unclosed / self-intersecting boundary n-gon The greedy nearest-endpoint chain checks closure only to remove the duplicate point: if (chainOk && chain.p.front().Distance(chain.p.back()) < 1e-6 + BRep_Tool::Tolerance(face)) {
chain.p.pop_back(); ...
}
if (chainOk && chain.p.size() >= 3) { ... emit ... }If the chain does not close (greedy walk took a wrong branch, or the face's edge set genuinely 6. The digon shortcut emits one border n-gon, but reports rungs int nRail = int(std::max(A.size(), B.size())) - 1;
...
*built = {int(out.mesh().polygons.size() - polyBefore), nRail};Only one polygon is emitted, yet 7. Clearing pins on RailLadder for (int eid : plan.uEdges) { ... if (!pinnedEdge[eid].empty()) pinnedEdge[eid].clear(); }
Related, the rail-equalization right below overwrites 8. Absolute distance thresholds in the seam-absorb pass (
9.
Minor
ProcessAt 951 files this PR bundles: vendored binaries, Windows CI plumbing, gate/tooling changes, golden |
Add FaceBakeQueue scheduling over weft::generate() (AD-1): FIFO pending map with per-faceId overwrite, single background worker, full-mesh adopt on the UI thread, Queued/Baking fidelity overlays. Debounced per-face edits enqueue without blocking the camera. Document as scheduling-only in EXECUTION_PLAN AD-1. Unit test covers pending dedupe. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Build a flat CSR face↔edge / edge↔face / neighbor graph once in analyze(), then have generate() read border edges for cache keys and digon floors without TopExp_Explorer. Move BRepGProp surface areas into TopologyCache at analyze so cold "counts + cache keys" drops ~1.6s→4ms on mp9_Edited (cost paid once at analyze). Dirty-closure tags mark per-face edit neighborhoods; remesh still driven by GenerationCache keys (AD-1). Warm local radial edit ≈0.5s. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Introduce weft/profile.hpp RAII ScopedTimer + GenerateProfileSession (high_resolution_clock, stderr). Wire generate() to print phase milliseconds when WEFT_TIMINGS=1 or WEFT_PROFILE=1, including a scoped face-planning block. Zero overhead when unset. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
- GenerationCache::digonsDirty + digonEdgeFloors: compute digon chord floors once per model; warm local edits re-apply in O(digons). - mesh_scratch.hpp: thread_local MeshScratch for meshFallback verts/tris (no hot-path heap growth after reserve). - occtMeshMutex: move deflection/bbox math outside the lock; disable nested OCCT InParallel (faces already parallel). - Parallel warm-up walks TopologyCache CSR instead of TopExp. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Extract Plane/Cylinder/Cone/Sphere/Torus/NURBS parameters once in analyze() into flat pools with O(1) faceId lookup. RevolutionGrid and related solvers evaluate via ElSLib on cached frames (evalSurfValue), bypassing BRepAdaptor_Surface on the analytic hot path. Parallelize cold face planning after a single-thread OCCT UV/surface warm. AD-1 preserved: still weft::generate() + border contract. Cold face planning on mp9_Edited ~7.7s → ~2.5s. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
- GeometryPool::value() evaluates NurbPrimitive via stack Cox-de Boor (full knot sequences from OCCT UKnotSequence); analytics stay on ElSLib. - GenerationCache::drumScalesDirty + drumScaleEdgeFloors: compute IsoBand drum-scale raises once; warm local edits re-apply in O(entries). - geometry_pool_tests cross-checks cylinder ElSLib and bspline_slab de Boor against BRepAdaptor_Surface. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
- CurvePool ETL at analyze: Line/Circle/NURBS/Polyline with lock-free value() and evenArcFractions() for border sampling (no BRepAdaptor_Curve on the freeform even-arc path when pool hits). - MeshScratch alignas(64); per-worker padded progress counters — no shared atomic fetch_add inside the parallel face mesh loop. - ActiveGeometryScope now also binds CurvePool for generate(). Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
evenArcFractions must not diverge from UniformAbscissa (mp9 empty/NM regression). CurvePool remains extracted at analyze for lock-free value() and future sampling; alignas scratch + padded progress stay. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
- CurvePool extracted at analyze (Line/Circle/NURBS/Polyline) with lock-free value()/evenArcFractions APIs; border sampling still uses GCPnts as the contract oracle until pool tables match bit-exactly. - MeshScratch alignas(64); parallel mesh workers use padded progress counters (no shared atomic in the inner loop). - Revert racy parallel planFace — GenerationCache memos are not thread-safe; warm planning remains ~3ms via FacePlanCache. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Extends healWithHistory with ShapeCustom_Surface::ConvertToAnalytical and optional ShapeCustom::BSplineRestriction, gated by env (default OFF so mp9 watertight stays green). WEFT_PROFILE_IMPORT prints stage timings. A/B mp9_Edited: analytic fit converts 89 BSpline→Cylinder but reopens seams; BSpline restrict ~16s→~4.5s cold gen but fails floor/NM gates. Keep experimental until pcurve rebuild is solid. Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Co-authored-by: jmossymoss <jmossymoss@users.noreply.github.com>
Summary
WP6 mp9_Edited: perfect topology + visual QA. Validity gate is green; suite is green; Windows CI now builds in parallel and deploys vendored OCCT runtimes.
Current status
testMp9EditedWatertightGREEN: watertight, structured 3052/3052, folds=0, empty=0weft_testssuite: all checks passed (local)tools/corpus_gate.sh --no-golden: PASSMajor fixes
Windows build
third_party/occt-win-runtime/(on git)build.bat/ CMake post-build copy them next tobuild\bin\Releaseneeds: linux(runs in parallel) and deploys those DLLs after cmake buildNOMINMAX+#undef min/max+(std::min)/(std::max)wrappers inmeshers.cppLocal Windows:
git pullthenbuild.bat— fails loudly iftbb12.dllis missing from the vendored folder.Visual remaining (polish)
Evidence
docs/evidence/wp6-mp9-edited-perfect/— VISUAL_PASS_V2.md, visual_pass_v3/, after_14c/, after_41d/, after_40b/