From b2c864a328675da5579bee13b8264588ccca3347 Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Fri, 25 Sep 2026 19:59:02 -0400 Subject: [PATCH] docs(fill): synchronize performance workflow guidance --- CLAUDE.md | 25 ++++++++++++++++++++++++- docs/performance/fill-performance.md | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 12a87c7..d4d6703 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,28 @@ Cross-platform CAD import tests: `dotnet test OpenNest.IO.Tests/OpenNest.IO.Test NuGet dependencies: `ACadSharp` 3.1.32 (DXF/DWG import/export, in OpenNest.IO), `Clipper2` 2.0.0 (region offsetting, in OpenNest.Core), `System.Drawing.Common` 8.0.10, `ModelContextProtocol` + `Microsoft.Extensions.Hosting` (in OpenNest.Mcp), `Microsoft.ML.OnnxRuntime` (in OpenNest.Engine for ML angle prediction), `Microsoft.EntityFrameworkCore.Sqlite` (in OpenNest.Training). +### Fill performance verification + +Opt-in synthetic fill measurements live in `OpenNest.Tests/Fill/FillPerformanceTests.cs`: + +```bash +OPENNEST_RUN_FILL_PERF=1 dotnet test OpenNest.Tests/OpenNest.Tests.csproj -c Release \ + --filter 'Category=FillPerformance' --logger 'console;verbosity=detailed' +``` + +Only the exact environment value `1` enables these tests; otherwise they skip during normal suite runs. In PowerShell, set `$env:OPENNEST_RUN_FILL_PERF = '1'` before running `dotnet test`, then remove it with `Remove-Item Env:OPENNEST_RUN_FILL_PERF`. + +The category includes count-first comparer, default/custom-comparer group-pattern, and repeated extents-column workloads. Run the latter two individually with `--filter 'FullyQualifiedName~GroupPattern_ReportsDefaultAndCustomComparer'` or `--filter 'FullyQualifiedName~Extents_ReportsRepeatedColumnRebuilds'`. Keep the same harness, inputs, warmups and batch sizes for before/after measurements; exclude setup and correctness checks from timing. Synchronous comparer/extents cases report current-thread allocations; the parallel group-pattern case deliberately omits allocation totals. These are measurements, not elapsed-time CI gates or whole-job speedup guarantees. Preserve measured evidence and limitations in [the fill performance report](docs/performance/fill-performance.md). + +Run behavior and deterministic skipped-work checks in Debug: + +```bash +dotnet test OpenNest.Tests/OpenNest.Tests.csproj -c Debug \ + --filter 'FullyQualifiedName~DefaultFillComparerWorkTests|FullyQualifiedName~FillHelpersTests|FullyQualifiedName~FillExtentsTests|FullyQualifiedName~StrategyOverlapTests' +``` + +`PerfCounters.FillScoreComputations` and `PartBoundaryPreparations` increments compile away in Release; zero Release counters do not prove work removal. Counter assertions belong in the existing nonparallel `FillCacheCollection` and must reset counters in `finally`. Keep `OpenNest.Tests/Fill/LegacyFillExtents.cs` frozen as the pre-optimization differential reference, not a second production implementation; use the actual baseline production code for before timings. + ## Architecture Nine projects form a layered architecture: @@ -140,7 +162,8 @@ Always keep `README.md` and `CLAUDE.md` up to date when making changes that affe - **Spacing offsets**: polygon consumers (`PolygonHelper`, `PartBoundary`, `NestValidator`, `CutOff`, the `LayoutPart` Draw Offset display) use `ClipperBridge.Offset`/`OffsetPerimeter`: one Clipper pass over the flattened region (perimeter positive, cutouts negative) with round joins at 1e-4 precision, so features narrower than twice the spacing collapse and closed-up holes disappear. `circumscribe: true` is the conservative mode (perimeter arcs circumscribed with endpoints kept on the arc, cutout arcs inscribed, inflation padded by the join chord error) and never under-estimates the spacing. `NestValidator` uses `OffsetForValidation` instead: the same flattening with fine joins and no padding, inflated by the spacing less `NestTolerances.SpacingSlack` (0.0005), so a layout exactly at the spacing passes even after rotation and coordinate rounding leave it ~1e-4 short. `NestJobPlacementValidator` applies the same slack to its edge-distance check. `PartGeometry.GetOffsetPerimeterEntities`/`GetOffsetPartEntities` stay on the arc-preserving per-entity `Shape.OffsetOutward`/`OffsetInward` (internal) because directional-distance loops are much faster on native arcs; their chains are closed but may keep zero-area spikes inside the envelope. Clipper is allowed only for cached CPU preparation, never in per-pair hot loops. - **Marks are not material**: scribe/etch moves are marked on the surface, never cut through, so they are left out of nesting. `SpecialLayers.IsMaterial(layer)` (excludes `Rapid` and `Scribe`) is the filter for every consumer that builds part material from a program: drawing area, canonical angle, part collision, `PartGeometry`, plate perimeters, best-fit/pair evaluation, rotation analysis, the GPU evaluators, and both validators (`NestJobPlacementValidator`, benchmark `NestValidator`). Cutting time, on-screen display, splitting, and post-processors still see marks. Older `.nest` files (e.g. `tools/PepNestExport` output) saved etch as cut moves while their source entities kept the `SCRIBE` layer; `NestReader` runs `ScribeLayerRepair` on load to move matching program moves back to `Scribe`. - `Compactor` performs post-fill gravity compaction — after filling, parts are pushed toward a plate edge using directional distance calculations to close gaps between irregular shapes. -- `FillScore` uses lexicographic comparison (count > utilization > compactness) to rank fill results consistently across all fill strategies. +- `FillScore` uses lexicographic comparison (count > utilization > compactness) to rank fill results consistently across all fill strategies. After its null/empty guards, `DefaultFillComparer` decides unequal counts without scoring; equal counts still use scores, and exact ties retain the current layout. `FillHelpers.FillPattern` computes eager scores only when no custom comparer is supplied; custom comparers remain authoritative and may perform their own scoring. +- **Extents column pitch**: for finite valid geometry, finite pair height, and finite nonnegative spacing, `FillExtents.BuildColumn` uses `pair.Bbox.Width + partSpacing` directly. The old vertical slide calculation clamps to the same pitch, so it need not prepare boundaries or temporary test clones. Negative/nonfinite spacing or nonfinite pair height retains the legacy calculation: public/interactive callers do not all validate spacing. Do not remove `BuildPair` boundary preparation or the adjusted-column overlap fallback, or turn this shortcut into a geometry/validation policy change. - **Cut-off materialization lifecycle**: `CutOff` objects live on `Plate.CutOffs`. Each generates a `Drawing` (with `IsCutOff = true`) whose `Program` contains trimmed line segments. `Plate.RegenerateCutOffs(settings)` removes old cut-off Parts, recomputes programs, and re-adds them to `Plate.Parts`. Regeneration triggers: cut-off add/remove/move, part drag complete, fill complete, plate transform. Cut-off Parts are excluded from quantity tracking, utilization, overlap detection, and nest file serialization (programs are regenerated from definitions on load). - **User-defined G-code variables**: Programs can contain named variable definitions (`name = expression [inline] [global]`) referenced in coordinates with `$name`. Variables resolve to doubles at parse time for geometry/nesting. `VariableRefs` on `Motion`/`Feedrate` track the symbolic link so post processors can emit machine variable references. Cincinnati post maps non-inline variables to numbered machine variables (`#200+`) with descriptive comments. Global variables share a number across programs; local variables get per-drawing numbers. `ProgramReader` uses a two-pass parse (collect definitions, then parse G-code with substitution). `NestWriter` serializes definitions and `$references` back to text for round-trip fidelity. - **CAD import pipeline**: All "DXF → Drawing" conversion goes through `OpenNest.IO.CadImporter`. The UI form uses `Import` on file load (storing the mutable result in a `FileListItem`) and `BuildDrawing` on save (passing the user's current visible entities and bends). MCP, API, and Training projects use `ImportDrawing` for headless conversion. The console uses `Import` followed by `BuildDrawing` so it can report bend-repair outcomes. This guarantees all callers produce drawings with the same shape: pierce-point `Source.Offset`, stable `SourceEntities` with GUIDs, `SuppressedEntityIds`, detected bends, and metadata. diff --git a/docs/performance/fill-performance.md b/docs/performance/fill-performance.md index 21c201d..c0452a9 100644 --- a/docs/performance/fill-performance.md +++ b/docs/performance/fill-performance.md @@ -1,5 +1,7 @@ # Fill performance measurements +Documentation follow-up — 2026-09-25: the user explicitly approved the previously blocked `CLAUDE.md` update. It now documents the delivered comparer/group/extents workflow, Debug work counters, measurement limitations, and compatibility safeguards already reflected in README. The instruction-document sync blocker is resolved; references to it in the historical slice records below describe their delivery-time status. No production code or measured results changed. + ## Count-first comparer slice — 2026-09-25 This historical Task 1 section covers only the count-first `DefaultFillComparer` change. Geometry, custom-comparer score elimination, ML work, and whole-job optimization had not been implemented or measured in that slice. Task 1b is reported separately below.