Commit Graph
17 Commits
Author SHA1 Message Date
aj 07cba5719f fix(io): write an empty sheet whose rail differs only in removal time
The writer's sentinel rule compared zones only, so an empty sheet following
a rail that differs from the default only in clamp removal minutes was
dropped on save although ChangePlateDefaults treats it as configured stock.
The rule now also requires the sheet to follow the default rail itself.

PM 1469b4e0-dc59-416b-8118-560b726aa1e1
2026-10-13 18:15:50 -04:00
aj 1bcead4b3a fix(io): write empty clamp sheets whose spacing differs from the defaults
The writer omitted an empty rail sheet when its size, quadrant and zones
matched the defaults, ignoring spacing, so a sheet left at the old part or
edge spacing after a defaults change (configured stock under C17) was
dropped on save. The comparison now includes edge and part spacing.

PM 455f6763-f0aa-40da-b9ad-11de4b2ea95b
2026-10-13 18:15:50 -04:00
aj d9273c889f fix(io): keep a sheet's own clamp rail and its removals through save and reload
Commit can attach the stock's rail to a sheet whose nest has no (or another)
default rail, but only the default rail was saved and the reader re-attached
sheets only to it, so the removed clamp and its time vanished on reload. A
sheet following a rail other than the default now saves it in an optional
per-plate clampRail (read leniently, omitted otherwise), and the reader
re-attaches the sheet to it.

PM 22daec3d-dabb-4aae-95f7-c1d090d5dcdf
2026-10-13 18:15:50 -04:00
aj 6fc1a0ebdc fix(core): move an untouched trailing sheet to new nest defaults
Changing the nest defaults (Nest Info, Set as default, Edit Plate on an
empty sheet) left the editor's trailing empty sheet on the old size and
quadrant, so the writer's clamp-preserving empty-sheet rule saved it as a
stale extra sheet. PlateManager.ChangePlateDefaults wraps a defaults edit:
a trailing empty, cutoff-free sheet that exactly matched the old defaults
(and followed their rail with no removed clamps or explicit zones) takes the
new defaults; every other empty sheet keeps its configuration and is still
written.

PM cade6901-5f4a-4503-85bf-0ea681d9ca7f
2026-10-13 18:15:50 -04:00
aj 04d538b0a4 fix(io): omit removedClamps from sheets without a removal
Every plate was written with "removedClamps": [], so files without the
feature changed. The key is now omitted unless a clamp was removed, like
the other additive sections; nest-file-format.md documents removedClamps
and removalMinutes and their lenient reading.

PM 6ded8ac4-84fd-4e8b-9a3b-6f41e494542c item (c)
2026-10-13 18:15:50 -04:00
aj 65754957cc fix(io): keep the clamp rail when its saved removal time is invalid
A negative removalMinutes in a saved nest made the ClampRail constructor
throw, so the reader dropped the whole rail and every sheet lost its
removed-clamp state and time. An invalid value now falls back to the
default five minutes, like a missing one.

PM 6ded8ac4-84fd-4e8b-9a3b-6f41e494542c item (b)
2026-10-13 18:15:49 -04:00
aj 53db543d6b fix(io): read a malformed removedClamps value without failing the nest
The optional per-plate removedClamps list was deserialized as a typed
List<string>, so a string or a list of numbers threw JsonException and the
whole nest failed to open. It now reads leniently: a non-array value reads
as none removed and non-string entries are skipped. The saved zones load
either way, so a removed clamp's area is never silently re-blocked or lost.

PM 9f3d676b-8a1c-4d9a-b877-b5e01e70c3f0
2026-10-13 18:15:49 -04:00
aj ec058d270c feat(core): remove individual rail clamps on one plate
Slice 3b task 1 (D8/D9): ClampRail gains RemovalMinutes (D9, default
5 minutes, validated finite >= 0) and Plate gains RemovedClamps (rail
clamp ids taken off this sheet as a last resort) plus the computed
ClampRemovalMinutes. Plate.SetClampRail gains an overload that
attaches a rail with a removed-clamp set; zones become the rail's
resolution for the sheet minus those ids, re-resolved on every
resize/rotate so a removed clamp stays off and reappears removed if a
later resize brings it back onto the sheet. ClampRail.Matches gains an
overload comparing against the reduced resolution.

IO: PlateDto.RemovedClamps and ClampRailDto.RemovalMinutes persist
(missing removalMinutes falls back to the default; missing
removedClamps means none). The writer's empty-plate drop logic no
longer drops a sheet whose only content is a removal state the
defaults can't recreate. The reader re-attaches the nest rail to a
plate only when its saved zones equal the rail's resolution minus its
saved removed ids, so a reduced-clamp sheet keeps following the rail
(and the removal) through later resizes instead of freezing as static
zones.

Engine/desktop wiring (minimal-removal computation, Auto Nest
last-resort removal, cut-time, UI) is Slice 3b tasks 2-3, not yet
done.

Tests: 16 new Core (ClampRailTests) + 5 new IO (ClampRailPersistenceTests)
covering validation, resize/resolve behavior, round-trip, legacy
missing-key defaults, and the empty-plate-with-removal-only retention
case. Mutation check (reverting the RemovedClamps subtraction) turns
3 Core + 2 IO assertions red, confirming real coverage.

Gates: Release OpenNest.Tests 3884 passed/31 skipped, OpenNest.IO.Tests
105 passed, OpenNest.Engine.Tests (Clamp filter) 98 passed; scoped
dotnet format --verify-no-changes clean.
2026-10-13 18:15:49 -04:00
aj 1e37c661ff fix(io): harden clamp rail persistence
Omit an empty clamp-carrying plate only when a new plate from the saved
defaults would recreate it: same size and quadrant and zones the saved
rail recreates. A rail-following empty sheet with another size or
quadrant is written again instead of being lost.

Read plateDefaults.clampRail through a lenient converter so a wrongly
typed rail (a string or null width, non-numeric centers, an array in
place of the object) loads as no rail while every plate's saved zones
still load, as the file format documents, instead of failing the whole
nest.

Review findings 4 and 5, Project Memory 4e5f566d.
2026-10-13 18:15:49 -04:00
aj 5674764447 feat(io): persist the nest clamp rail
Write the nest's machine clamp rail under plateDefaults.clampRail
(width, depth, centers) so plates created or resized after a reload
still get the clamps. The key is omitted for nests without a rail, and
a missing, empty or malformed rail loads as none while each plate's
saved zones still load.

On load a plate follows the rail again only when its saved zones are
exactly the rail's for that sheet; other zones are kept as written.
The empty-plate filter now drops an empty sheet whose zones the saved
rail recreates, so the editor's trailing empty sheet is not written
for every clamped nest; empty sheets with other zones are still kept.
2026-10-13 18:15:49 -04:00
aj d001abcf69 feat(io): persist clamp zones in nest files
Add the additive per-plate clamps array (id, x, y, length, width) to the
nest DTO with writer and reader support. Files without the key load with
zero zones; an individually malformed entry is dropped while the rest of
the nest loads. A plate carrying only clamp definitions now survives the
writer's empty-plate filter; genuinely empty plates remain omitted.
Format documented in docs/nest-file-format.md.
2026-10-13 18:15:49 -04:00
aj 5dbf887b68 feat(engine): score nesting jobs by per-sheet stock cost 2026-10-10 12:38:20 -04:00
aj b254a0bd3e feat(io): persist part cutting programs and plate parameters 2026-09-28 19:17:59 -04:00
aj 451841401a refactor(engine): align namespaces with directory layout under OpenNest.Engine
All 132 OpenNest.Engine source files now declare namespaces matching
their nested directories: Jobs/, Jobs/Placement/, Jobs/Adapters/,
Fill/, RectanglePacking/, CirclePacking/, and engine-root types moved
from 'OpenNest' to 'OpenNest.Engine'. RootNamespace updated accordingly.
Consumers (Api, Console, Mcp, Benchmark, Training, desktop app, tests)
gained the explicit usings the move requires; CLAUDE.md updated.
2026-09-21 13:18:34 -04:00
aj 6f38c11383 feat(io): workbook part-quantity reader and cut-only import coverage
PartQuantityReader parses a Parts worksheet (Part Name / Qty Required)
with exact-name matching and strict rejection of invalid, fractional,
negative, or duplicate quantities. Cut-only import drops case-insensitive
ETCH/SCRIBE mark layers before geometry so bend detection cannot
regenerate them. Tests cover both.
2026-09-21 10:05:39 -04:00
aj aec0523062 style: apply CSharpier formatting to all C# sources
Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and
line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests
pass after reformat, full solution builds 0 errors.

Added .csharpierignore so csproj/config XML keeps its existing layout
(CSharpier's XML wrapping churns attributes with zero benefit).

Formatting is now enforceable: dotnet csharpier check . passes.
2026-09-20 16:41:50 -04:00
aj 589d341455 feat(io): conservative opt-in bend repair with tests and console CLI
Add OpenNest.IO/Bending/BendRepair: opt-in repair of unambiguous paired
ETCH/SCRIBE bend ticks, bounded to <=3.175 mm endpoint movement with
explicit source units. Cut geometry is never modified.

- CadImportOptions.BendRepair configures it; CadImportResult exposes
  per-bend BendRepairReports; CadImporter/Dxf wire it into import.
- Console: --repair-bends-mm <limit> --cad-units inches|mm prints
  per-bend reports for newly imported DXFs.
- New OpenNest.IO.Tests project (net8.0, synthetic DXFs, 30 tests)
  covering bend detection and repair, added to the solution.
- Update README.md and CLAUDE.md for the new pipeline and build/test
  instructions.
2026-09-20 15:22:46 -04:00