Capture the report target and reject whole-job nesting, open progress
windows, interactive fill and busy plate actions across every view
sharing the nest; revalidate after the save dialog, capture the snapshot
synchronously on the UI thread and render it through OpenNest.Reporting.
Windows adapter tests cover enablement, guards, cancel, success and a
write failure against an existing destination (compile-only on Linux;
Windows runtime acceptance still owed).
SQLite-backed (Microsoft.Data.Sqlite, WAL) minimal API storing NestRecord
metadata plus the .nest archive as a BLOB. Endpoints: GET/POST /api/nests,
GET /api/nests/{id}[/file], PUT /api/nests/{id}/file, PUT
/api/nests/{id}/metadata, DELETE /api/nests/{id}, GET /healthz. Multipart
upload contract matches RemoteNestRepository (metadata JSON part + file
part). Added to OpenNest.sln, builds standalone on Linux. Dockerfile
publishes to a runtime image listening on :8090 with a /app/data volume
for the SQLite file. docs/nest-storage.md documents the wire contract,
endpoints and deployment.
Full curl round trip verified manually against a running instance:
upload (server-assigned id + computed fileSize), list, get, byte-exact
file download, metadata-only update (archive unchanged), file update
(new archive persisted), 404s for unknown ids, delete, post-delete 404.
Add NestStatus (Quote/ToBeCut/HasBeenCut) plus MadeBy on Nest, written as
additive camelCase nest.json fields with PascalCase enum strings matching
the units convention. Legacy files and unknown status values fall back to
Quote. The nest info dialog gains a Status dropdown and Made By box.
Slice 2 of the nest report plan: pagination, overflow and dense-label
coverage on top of the Slice 1 one-plate library.
- NestPdfWriter: general multi-plate pagination. Summary/plate tables
continue across pages with repeated (HeadingFormat) header rows; notes
flow as an ordinary paragraph instead of a bounded cell; page headers
are wrapped and sized into the top margin; drawing areas are located
per page via DocumentRenderer.GetRenderInfoFromPage.
- ReportText: lossless pre-wrapping (MigraDoc clips an over-tall row and
lets an unbroken token overflow a narrow cell without warning), capped
table-cell line counts, plate-range compression ("1-2, 4"), and
A/B/.../AA map-grid row names.
- NestReportDiagram: label placement centers each part ID on its
PolyLabel pole (matching PlateView's LayoutPart), computed on a
placement-independent quantized copy so identical parts share one
label position. Parts whose ID cannot fit legibly at overview scale
get a lettered/numbered map grid and a zoomed, framed detail page per
crowded cell; the writer fails with plate/part/ID when even that
cannot place a label, or a plate would need more than 24 detail views.
- Tests: NestPdfLayoutTests (multi-plate totals/ranges/same-named
references, table continuation with no lost rows, long name/notes
wrapping without column overflow, mm units in all four quadrants,
dense-label detail views with hole avoidance, save/reload of the
tabbed/lead-in fixture with stale tab flags, invalid later-plate data,
late write failures against an existing destination, source
unchanged on success/failure), ReportPdf test helper (page/word/
content-stream extraction).
- docs/nest-reports.md: replace the Slice 1 one-plate limits section
with the general pagination/dense-label contract and the process-wide
PDFsharp/MigraDoc render lock.
Verification: Reporting filter 70/70; full OpenNest.Tests 2524
passed/21 skipped/0 failed; Engine 351/351; IO 77/77;
EnableWindowsTargeting=true full-solution build 0 errors; scoped
dotnet format --verify-no-changes exit 0. Preview PDFs rendered and
visually inspected (evidence: /home/aj/extracted/2026-09-29/opennest-report-slice2/).
Windows runtime, the desktop adapter and packaged-app font deployment
remain unverified (Slice 3).
Add OpenNest.Reporting (net8.0, PDFsharp-MigraDoc 6.2.4) with a detached
report snapshot and a PDF writer for an empty/demand-only job or a single
plate layout: Letter portrait summary with vector part thumbnails and a
landscape plate page with a vector sheet diagram, both with Page X of Y.
- Snapshot uses reference identity for document-local R### IDs, recounts
nested quantities from placements x copies with checked wide integers,
excludes cutoffs from accounting, and retains no live domain objects.
- Geometry keeps Cut/Display material paths, drops rapid/scribe/lead paths,
preserves native arcs, holes and intentional tab gaps, and rejects
malformed programs with plate/part identification.
- Open contours and cutoffs are stroked per contour so PDFsharp cannot join
a tab gap to the next contour.
- Bundled DejaVu Sans via a custom resolver (including MigraDoc's error
font); unsupported text fails with the field and code point.
- Layouts beyond this slice (multiple plates, page overflow, labels that
do not fit) fail with NotSupportedException before the destination is
replaced; output is rendered to a temporary sibling and moved atomically.
Nest windows now rerun the overlap check once the layout has been
unchanged for 0.5 s, instead of leaving 'Overlaps: not checked'. Edits
show 'Overlaps: check pending...'; drags are caught by the paint-time
pose stamp, collection edits by their events. The check waits while a
mouse button, modal dialog or fill is active, supersedes a running check
when the layout moves again, and does not retry a canceled or failed
layout until it changes. Rechecks use the incremental analyzer, so only
the moved parts' neighbors are recomputed.
Automatic results update only the canvas label (the status bar keeps the
last command's message) and keep Display > Off. Check Active Plate still
runs immediately. InvalidateOverlapCheck now also drops cached material.
A straight lead-in at a convex outside-perimeter corner now runs along the
extension of the edge cut first, so the torch enters on that line and keeps
cutting it. The result no longer depends on which of the corner's two edges
auto-assign or the manual cursor picked, which made placement flip between
straight and 90 degrees. The approach angle is ignored at such corners.
The straight lead falls back to the first-cut edge normal when its pierce
would be closer than PierceClearance to the contour (very flat or tessellated
corners). Reflex perimeter corners bisect the notch. Line lead-outs run on
straight past a convex corner along the last-cut edge, except on tabbed
perimeters. Program generation and the Place Lead-in preview share
ResolveLeadIn/ResolveLeadOut.
b0997f6 moved every cut-off after the parts. Plate.Parts order is the
cut sequence, and the user sets each cut-off's place in it (Set
Sequence), so the post must follow it rather than reorder. Cut-offs
still post as uncompensated open lines with no lead-in.
The machine owner confirmed M50 swaps pallets (the sample's M50 before
M30 moves the cut sheet out for unloading). Only the single-program
between-sheet sequence remains unconfirmed.
Multi-sheet nests previously went into one program with a single header
size and one M50 at the end, so sheet 2 would cut into sheet 1's
skeleton. New Sheets settings:
- One program per sheet (default on): JOB.cnc -> JOB-1.cnc, JOB-2.cnc,
each a full program with its own size and pallet change (CL-series
batch rule, EM-423 7.4). Single-sheet nests keep the chosen name.
- Off: one program, with /L "L0" + pallet change between sheets;
mixed sheet sizes are rejected.
- Pallet change code (default M50, unconfirmed for multi-sheet CI Fiber
runs; documented as a release blocker).
All sheets are validated and rendered before any file is written.
IMultiFilePostProcessor lets the desktop app confirm overwrites of every
target file and list what was saved, and the console print each file.
Replace the generic PropertyGrid for configs that opt in via
PostSettingAttribute: a section list (Machine, Material, Program output,
Macros) with labelled fields, help text, numeric ranges and an editable
material-code table. Edits apply only when OK validates every field.
Unannotated configs (Cincinnati CL, GravographIS) keep the PropertyGrid.
Posting a nest with a sheet cut-off threw because the CI Fiber writer
treated every contour as a compensated part contour and requires a
linear lead-in after G41/G42 (TF5200 13.2.4.1). Cut-offs are open
centreline cuts with no lead-in and no inside/outside, so they now post
without G41/G42 and run after every part on the sheet so the sheet is
not severed before the parts are cut (matching the CL post).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After 1b, triangulating both polygons on every pair was the largest
remaining overlap cost (27% of main-thread samples on the corpus job).
PartOverlapChecker now triangulates each part at most once per check,
lazily after the bounding-box gate, and passes the triangles to a new
internal Collision.HasOverlap overload that runs the unchanged
OverlapRegions body. Triangles are only read by clipping and hole
subtraction, so reuse gives identical verdicts.
Verification:
- 49,000 seeded decisions with reused triangles match LegacyCollision;
triangles stay bit-identical to a fresh triangulation afterwards.
- Debug PolygonTriangulations: 246 -> 40 and 64 -> 36 per grid check;
sharing triangles per Program instead fails 23 tests.
- Corpus job (169 parts, --engines Default --parallel 1): median
13,398 -> 12,702 ms over 4+4 alternating runs vs 1b, identical
outcomes; serialized layout byte-identical to the base.
Also records the Follow-up B' (Slices 1a, 1b, 2a) measurements in
docs/performance/fill-performance.md.
Both HasOverlappingParts loops rebuilt each part's polygon from its
Program on every pair. PartOverlapChecker prepares each distinct Program
(reference identity) once and each part's world polygon once per call,
then uses the overlap-only Collision.HasOverlap. Loop order, bounding-box
prefilter, early exit and returned indices are unchanged; Part.Intersects
shares the material/polygon recipe and still returns crossing points.
Verification:
- Frozen LegacyPartOverlap differential (original Intersects and both
loops): verdicts, indices and world polygons bit-identical across fill
grids, patterns, touching/epsilon gaps, scribe/rapid/empty programs.
- Debug OverlapPolygonPreparations: 246 -> 1 and 64 -> 2 per check.
- Corpus job (169 parts, --engines Default --parallel 1, with 1a):
median 18,885 -> 13,464 ms over 4+4 alternating runs, identical
outcomes; serialized layout byte-identical to the base.
FillLinear re-prepared offset perimeter geometry (ConvertProgram ->
ShapeProfile -> OffsetOutward) for every part it measured, although
tiled copies share one Program and differ only by Location. A CPU
profile of a 169-part Default job put 62% of wall time there.
Prepare each distinct Program (reference identity) once per public
Fill/FillRow call in local frame, then clone and translate for each
location. The cache is created per call and passed down privately
because FillHelpers.FillPattern calls Fill concurrently on one
instance. PartGeometry gains a local-frame Program overload that the
Part overload now delegates to.
Evaluation order, lazy preparation, fallbacks and tiling are
unchanged. Differential tests against a frozen copy of the previous
FillLinear check bitwise equality, including concurrent calls; Debug
work tests pin preparation counts. With the thread pool capped at one
worker, before/after whole-job layouts are byte-identical. The
Default corpus job median drops from 40,715 to 18,810 ms.