Commit Graph
1252 Commits
Author SHA1 Message Date
aj 648b0eaca5 fix(cnc): bisect inside cutout corners for straight lead-ins 2026-09-28 19:25:40 -04:00
aj b254a0bd3e feat(io): persist part cutting programs and plate parameters 2026-09-28 19:17:59 -04:00
aj 74302761ce fix(io): restore hole sub-programs with negative IDs 2026-09-28 19:05:11 -04:00
aj 4da48eed68 fix(core): keep cut-offs in their cut sequence through regenerate and save
A cut-off's place in Plate.Parts is its cut sequence number, but
RegenerateCutOffs removed every cut-off part and appended it again, so
any part drag, fill or cut-off move sent the cut-offs to the end. The
nest file didn't store the position either, so reopening did the same.

RegenerateCutOffs now puts each cut-off back at its previous index (new
cut-offs go at the end), and CutOffDto.Sequence saves the index. Older
files without it load the cut-offs at the end, as before.
2026-09-28 18:44:04 -04:00
aj 6803d7519f fix(posts): post CI Fiber cut-offs in their sequence, not last
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.
2026-09-28 18:41:06 -04:00
aj 86dbd8819b style(core): apply dotnet format to Plate.cs
Formatter-only: drops the UTF-8 BOM (.editorconfig charset = utf-8).
No behavior change.
2026-09-28 18:40:50 -04:00
aj 20395eed9b fix(core): keep lead-in state and rotation through part copy and rotate
Part.Clone and CloneAtOffset copied the lead-in program but not
HasManualLeadIns, LeadInsLocked, CuttingParameters or the tracked
pre-lead-in rotation. A copy of a rotated lead-in part reported
rotation 0 (the rebuilt program's), so saving it wrote the wrong
rotation and Remove Lead-ins restored an unrotated part.

Part.Rotate on a lead-in part set the tracked rotation from the
lead-in program's own Rotation, which starts at zero when the cutting
strategy rebuilds it, so a further quarter turn left Rotation unchanged.
Lead-in parts now accumulate the applied angle instead.
2026-09-28 18:22:36 -04:00
aj a886735040 fix(cnc): keep hole sub-programs private to each program copy
Program.Clone deep-copied the SubPrograms dictionary but left every
SubProgramCall pointing at the source's sub-program, and
SubProgramCall.Clone went through the Rotation setter, which re-rotated
that shared program to the call's stale angle. Copying a program with
hole lead-ins therefore rotated the source's holes, and rotating the
copy rotated the source again.

Program.Rotate also rotated a shared sub-program once per call, so two
identical holes (one deduplicated sub-program) turned twice.

Clone now binds calls to one private copy per shared sub-program
without re-aligning it, and Rotate turns each distinct sub-program once.
2026-09-28 18:22:36 -04:00
aj 75d41f3bb7 style(cnc): apply dotnet format to Program and SubProgramCall
Formatter-only: re-indents braced switch sections in Program.cs and drops
the UTF-8 BOM from SubProgramCall.cs (.editorconfig charset = utf-8).
No behavior change.
2026-09-28 18:22:36 -04:00
aj 26252b333a docs(posts): record M50 as the confirmed CI Fiber pallet change
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.
2026-09-28 18:22:36 -04:00
aj c219ec4e66 feat(posts): CI Fiber one program per sheet, configurable pallet change
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.
2026-09-28 18:22:36 -04:00
aj 32586149c6 refactor(posts): remove CI Fiber part comment override
A fixed override wrote the same text on every part, making parts
indistinguishable in the program. Each part's ( PART:... ) comment now
always uses its source file or drawing name. Saved configs that still
contain PartComment load normally; the key is ignored.
2026-09-28 18:21:21 -04:00
aj 9ec28add80 fix(posts): keep CI Fiber material codes case-insensitive after reload
System.Text.Json rebuilds MaterialCodes with the default ordinal comparer,
so a saved config matched 'Mild Steel' but not 'mild steel' and silently
fell back to the default code. The setter now re-keys assigned maps
case-insensitively.
2026-09-28 18:21:07 -04:00
aj 35aa64fe4e feat(posts): sectioned settings editor for CI Fiber post
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.
2026-09-28 18:21:07 -04:00
ajandClaude Opus 5.5 c33337cea2 fix(cnc): stop double-counting first incremental rapid in rapid display
RapidEnumerator primed the walk position at the first pierce point, then
the skipped first rapid advanced it again. Raw programs start with a zero
rapid so this was invisible, but lead-in programs start with a real
incremental offset to the pierce, which shifted every later rapid by that
delta and drew rapids off the sheet. Start the walk at the program origin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 14:21:37 -04:00
ajandClaude Opus 5.5 b0997f614b fix(post): post sheet cut-offs in CI Fiber without comp or lead-in
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>
2026-09-28 14:14:07 -04:00
aj 6d7c98ef4a docs: document defaults.json nest-defaults mechanism replacing .nstdot 2026-09-28 08:22:49 -04:00
aj f44c1d465f feat(ui): defaults.json-backed new nests; retire .nstdot templates
New nests now load plate defaults from %APPDATA%/OpenNest/defaults.json
instead of a .nstdot nest template:

- Tools > Nest Defaults... edits the file directly (new dialog).
- Tools > Save Current Plate as Defaults captures the active plate with
  no dialog (visible when a nest is open).
- New_Click no longer unzips a template; a corrupt defaults file warns
  once per session and falls back to built-in values.
- First run converts an existing NestTemplatePath .nstdot to
  defaults.json and clears the legacy setting (kept readable on a
  failed conversion); the setting itself stays, marked legacy.
- Save As no longer offers the .nstdot filter; SaveTemplate removed.
- BomImportForm and OptionsForm template plumbing removed in favor of
  the shared NestDefaults store.
2026-09-28 08:20:49 -04:00
aj bdb78a689d feat(defaults): JSON nest defaults store with per-field fallback
Replaces the .nstdot nest-template mechanism for new-nest plate
defaults. NestDefaults persists units, plate size, quadrant, part
spacing, and edge spacing to a single JSON file (DefaultPath:
%APPDATA%/OpenNest/defaults.json). Load never throws: a missing,
corrupt, or partially valid file degrades field-by-field to the
built-in fallback values, and unknown fields or a future version
number are ignored.
2026-09-28 07:09:52 -04:00
aj 3f89f8f5dd feat(ui): Tools > Expand Spacing for selected parts
Mirrors PushSelected: PlateView/SelectionManager.ExpandSelected runs
Expander.Expand on the selection, marks parts dirty, regenerates cut
offs, and reports achieved spacing plus blocked-pair count in the
status bar. Menu item joins Align Selected's enable surface.
2026-09-28 03:17:55 -04:00
aj 3cab90747a feat(expand): plateview spacing expander
Grows part-to-part spacing of a selected group with the work area and
non-selected parts as hard boundaries. Doubling + bisection search over
the target spacing; Gauss-Seidel straight-line relaxation with anchor
mover policy (later-index selected part moves, first selection never).
Overlapping input is separated along penetration MTVs instead of being
rejected. Cancel/failure never mutates part positions. Clearance gains
BoundaryDistance for ring-pair gaps (part-in-cutout legality).
2026-09-28 03:05:45 -04:00
aj fff3bef4e4 feat(geom): signed clearance kernel between polygons
Omnidirectional minimum distance with separating direction (positive)
and penetration depth with minimum-translation direction (negative),
for the PlateView spacing expander. Overlap verdict defers to
Collision.HasOverlap so kernels never disagree. Basis for the fixed-s
separation solver.
2026-09-27 23:36:26 -04:00
aj 2b78fb3a75 fix(geometry): resolve blocking contacts across directional slide paths 2026-09-27 23:24:31 -04:00
aj a04460c57b refactor(ui): drop duplicate plate preview from nesting progress dialog
The dialog rendered the same best-parts layout as transparent ghost
parts already shown live on the main plate view, and its embedded view
reset zoom on every improvement while lacking placed parts, work-area
and cut-off context. The progress dialog is now a compact stats strip
(266px wide) with the plate counter preserved for multi-plate runs.
Removes the never-called SetStationaryParts stationary-preview path
along with PreviewPlate/UpdatePreview/CreatePreviewPlate.
2026-09-27 20:43:34 -04:00
aj 27948ba8cf docs(posts): replace bundled manual with implementation references 2026-09-27 19:52:25 -04:00
aj 1fdede396b ci(release): bundle and verify external nesting engines v0.3.0 2026-09-27 19:11:14 -04:00
aj 1b9988a1ba ci(release): build and verify Windows desktop packages 2026-09-27 18:57:38 -04:00
aj ca1902e075 fix(bestfit): stop at internal arc tangency 2026-09-27 16:35:53 -04:00
aj bc5fd86996 test(fill): characterize unchanged-row validation reuse 2026-09-27 15:10:01 -04:00
aj 1b23ad79f2 perf(fill): reuse part triangulations within an overlap check
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.
2026-09-27 13:49:46 -04:00
aj a27290a29c perf(fill): prepare overlap polygons once per check
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.
2026-09-27 13:34:04 -04:00
aj f44a1a3d2c style(console): consolidate usage text into one multiline write 2026-09-27 13:17:35 -04:00
aj 2b5485f6cf perf(core): skip unused crossing points in overlap-only checks
Collision.HasOverlap only needs the verdict, but it went through Check,
which also collected crossing points. Triangulation, clipping and hole
subtraction now live in one private OverlapRegions method shared by Check
and HasOverlap, so verdict arithmetic stays single-sourced; Check output is
unchanged.

Tests: a frozen copy of the previous Collision is the oracle. 50,000 seeded
HasOverlap verdicts and 2,400 bitwise Check results match it, plus
containment, contact, hole and input-immutability cases. A Debug-only
PerfCounters.CrossingPointScans counter proves HasOverlap no longer scans.
Malformed polygons with null outer vertices still throw when the bounding
boxes overlap (now ArgumentNullException from triangulation rather than
NullReferenceException from ToLines).

Measured (Release, same harness in both trees): about 44% less time per
overlap-only polygon check, allocations 10.0 -> 7.9 MB per 155-pair sweep.
The 169-part serialized corpus layout is byte-identical.
2026-09-27 12:54:20 -04:00
aj a98a49c00a test(fill): add opt-in overlap-check micro benchmark
Times Collision.HasOverlap over the box-overlapping neighbour pairs of two
FillLinear grids, and the FillHelpers.HasOverlappingParts grid checks
themselves. Opt-in via OPENNEST_RUN_FILL_PERF=1 (Category=FillPerformance).
Uses only APIs that predate the overlap-check work, so the same file can be
copied into a before tree for same-harness comparisons.
2026-09-27 12:54:20 -04:00
aj fdbc1fb99f style(console): combine adjacent output with readable multiline strings 2026-09-27 12:48:52 -04:00
aj 82feb78b0f feat(posts): add Cincinnati CI Fiber (TF5200) post-processor
New post-processor plugin OpenNest.Posts.CincinnatiCIFiber for the CI
Fiber laser family (nLight CLX / Beckhoff TF5200 / Precitec ProCutter,
e.g. the CI Fiber 4020 8kW). Named by machine family, not table size.

Emits the machine program contract of the Cincinnati-supplied sample NC
(12992-4SS_NEST.nc): V.E.* header, restart jump, per-part V.E.R4 blocks,
per-contour N labels with V.E.R3, skippable /L macro lines (L0/L2+G41
interior, L4+G42 exterior, L6 cut-on, ZHSOFF cut-end), G162 incremental
arc I/J, trimmed 3-decimal spaceless coordinates, CRLF, M50/M30/%.

Contour classification (interior vs exterior) derives from the material
side of the closed cut path, not hardcoded winding. SubProgramCall holes
are flattened to sheet coordinates (rotation-safe). Arc lead-ins are
rejected per TF5200 13.2.4.1 (first motion block after G41/G42 selection
must be linear). Table envelope (default 160.25 x 81.25 in) validated.

Tests: structure golden on a square-with-hole nest, rotated-hole flatten,
coordinate format, arc-lead-in rejection, table validation, suppressed/
scribe skipping, plus a SkippableFact regression against the real sample
NC (109 parts, 2071 contours, L2=1962, L4=109, perimeter vertices match
within 0.001). Fixtures configure through OpenNest.Tests/test-config.json
and the regression skips when absent.
2026-09-27 04:10:22 -04:00
aj c825f40213 refactor(posts): move post-processor projects under Posts/
Group the Cincinnati and GravographIS plugin projects in a Posts/
folder so new machine posts have one home. Project names, namespaces,
and the runtime Posts/ deploy target are unchanged; only relative
paths in the solution and project references move.
2026-09-26 22:35:23 -04:00
aj 0df2587cf2 perf(fill): reuse offset geometry for translated copies
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.
2026-09-26 20:24:34 -04:00
aj 094c4c196b fix(bestfit): never reject pair candidates by utilization
Thin-framed, hollow, or concave parts (e.g. SULLYS-035's frame) have
inherently low part-to-bbox utilization yet nest tightly, so the 30%
MinUtilization floor wrongly dropped every candidate for them. Pair
quality is judged by the rotated pair bounding-box area the results
are already sorted on; utilization now only ever serves as the
high-aspect exception (UtilizationOverride), never as a rejection.

Adds a hollow-frame helper plus regression tests that kept pairs
exist with low utilization and results stay sorted by pair area.
2026-09-26 14:14:44 -04:00
aj 77b729e58c docs(readme): drop opt-in fill performance measurements section
Keeps the README approachable for new users; microbenchmark details
remain in docs/performance/ for maintainers.
2026-09-26 13:00:32 -04:00
aj 1d1c60daa7 docs(fill): record combined initial-batch acceptance 2026-09-26 11:25:17 -04:00
aj 1e8e532063 perf(fill): skip feature extraction without an angle model 2026-09-26 10:02:15 -04:00
aj d70505b7c0 docs(fill): sync shared guidance for scalar-only extraction 2026-09-26 07:36:25 -04:00
aj 8188533d72 perf(ml): support scalar-only angle features 2026-09-26 00:02:28 -04:00
aj 6863c8bdb1 docs(fill): clarify Task 3 evidence retention 2026-09-25 21:46:31 -04:00
aj 4553f8afad perf(fill): avoid redundant bounds recomputation 2026-09-25 21:45:03 -04:00
aj 4ec92c95ec docs: share coding-agent guidance through AGENTS.md 2026-09-25 20:03:42 -04:00
aj b2c864a328 docs(fill): synchronize performance workflow guidance 2026-09-25 19:59:02 -04:00
aj 6efa6b1117 perf(fill): remove discarded extents pitch geometry 2026-09-25 19:52:19 -04:00
aj cec7396da6 perf(fill): avoid unused scores with custom comparers 2026-09-25 17:18:15 -04:00