Commit Graph
2347 Commits
Author SHA1 Message Date
ajandClaude Fable 5.2 cd5a400a22 refactor(core): move clamps into OpenNest.Clamps
Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:38:20 -05:00
ajandClaude Fable 5.2 70ca4c4be6 refactor(core): split Plate into partial files by concern
Moves whole members verbatim into Plate.CommonLines.cs, Plate.Clamps.cs, Plate.CutOffs.cs, Plate.Transforms.cs and Plate.Sizing.cs. No behavior or API change.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:32:51 -05:00
ajandClaude Fable 5.2 b77b3b7a33 refactor(core): swap an immutable part palette instead of mutating Drawing statics
Drawing.PartColors (a public array aliasing the active scheme's), Drawing.UseGoldenAngleColors and a non-atomic colour counter were process-global display state. PartPalette now holds a copy of the colours and the golden-angle flag; PartColorPalette.Current/Use swap it as a whole, Drawing.GetPartColor/GetNextColor read it, and the counter increments atomically. Colours shown are unchanged.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:20:22 -05:00
ajandClaude Fable 5.2 986167a411 refactor(core): refresh Drawing's derived state through ProgramChanged
Area and the canonical angle were refreshed together only by the Program setter; an in-place program edit needed two separate calls, and UpdateArea kept a stale Area when no material was left. ProgramChanged is now the single entry point (the setter calls it), a program with no material or a null program gives Area 0, and UpdateArea/RecomputeCanonicalAngle are private.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:20:22 -05:00
ajandClaude Fable 5.2 dec685361c refactor(core): make Math and Geometry Core's bottom layers
Move ContourSampler to OpenNest.CNC and DrawingAligner with its
AlignmentResult types to OpenNest.Converters: they were the only Geometry
files importing CNC and Converters. Their tests move with them.

CoreLayeringTests scans the Math and Geometry sources against the type
names reflection finds in Core, so neither can name a higher layer again.
Geometry keeps six root-namespace types on an explicit allowlist, and the
files declaring them may reference only Math and Geometry.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:12:13 -05:00
ajandClaude Fable 5.2 9b893c7a09 refactor(core): remove Plate.HasOverlappingParts
Its last callers were four strategy tests; they now use
PartOverlapChecker.FindOverlappingPairs, which also reports
contained and stacked parts.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:11:37 -05:00
ajandClaude Fable 5.2 e5d4337681 fix(core): report overlapping pairs through PartOverlapChecker
The console overlap check and the MCP check_overlaps tool counted
crossing points, so a part contained in or stacked on another reported
no overlap. Both now list pairs from PartOverlapChecker.FindOverlappingPairs,
which uses the Intersects verdict and skips cut-off parts.

Plate.HasOverlappingParts stays for now: two test files outside this
change still call it.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:10:26 -05:00
ajandClaude Fable 5.2 a451cf5ef3 fix(core): narrow the perimeter-build catch to geometry failures
BuildPartPerimeter swallowed every exception. It now guards a missing
program and an outline that forms no shape, and catches only geometry
failures; a null perimeter still makes a cut-off exclude the part's whole
bounding-box span.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:10:26 -05:00
ajandClaude Fable 5.2 32edc18ede fix(core): stop a part turning a program its offset copies share
Part.CloneAtOffset hands the copy the source's Program instance. The copy
cloned before rotating, but the source still owned the program, so rotating
the source turned the program under every tiled copy while their location
and bounds stayed put.

Program now carries a sticky SharedByParts flag that CloneAtOffset sets, and
a part copies its program before rotating when it does not own it or the
program is shared. The flag lives on the program rather than the part so a
restored cutting state, which reinstates ownership field for field, cannot
re-alias a shared program. Unrotated copies keep sharing one instance, which
the fill and overlap caches key on.

Update, ApplyLeadIns and ApplySingleLeadIn install a fresh program and now
own it, saving a needless copy on the next rotate.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:09:28 -05:00
ajandClaude Fable 5.2 febeb76836 refactor(core): remove unused Entity.MoveTo and point-based OffsetEntity
Nothing called MoveTo on a geometry entity or the OffsetEntity(distance,
point) overload. Shape and Arc threw NotImplementedException for them, and
the Line and Polygon versions were wrong and untested. Remove the abstract
declarations and every override; the OffsetSide overload stays.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-12 19:04:08 -05:00
ajandClaude Opus 5.5 c8e1b97786 fix(ui): sign new nests with Made By instead of on viewing Nest Info
Nest Info filled a blank Made By with the current user every time it
opened, so pressing OK on an existing nest's info (from a tab or Saved
Nests) signed someone else's nest. The author is now stamped once when a
nest is made (File > New, drawing set Make Nests, BOM import), and Nest
Info shows the stored value as is, leaving a blank one blank.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 18:21:22 -05:00
ajandClaude Opus 5.5 5d00715948 chore(help): ship opennest-help v0.8.0
v0.8.0 documents the drawing library's Filter dialog and its Size and Holes
columns (b9fc618a), with recaptured library screenshots and a new
library-filter topic. The zip's SHA-256 was checked against the release's
published hash.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:51:23 -05:00
ajandClaude Opus 5.5 e6d438629f feat(cutting): slice slugs the support grid can't hold level
On an enclosed machine a cutout slug that tips once cut free can stand up
above the sheet and catch the enclosure door when the pallet changes. The
head already keeps off cut cutouts, so this is about clearing the door, not
the head.

Cutting Settings > Cutouts > "Slice slugs the support grid can't hold
level" (CuttingParameters.SliceTippingSlugs, off by default, saved per
machine thickness and written only when on) makes Plan Cutting cut each
such slug along the bed's contact lines (grid rows and columns, slat center
lines) before the cutout, so no piece keeps a contact and the pieces drop
between the supports. Each slice stops a kerf short of the cutout, so the
pieces stay attached until the cutout's own kerf frees them together. A
slug the bed holds level, or one on no contact, is left whole; a cutout
holding another part or another of its part's cuts is never sliced.

Slices are break cuts emitted right after the drawing's own break cuts,
pierced on themselves and led straight back to their start. The
released-contour proof now leaves out straight break cuts lying wholly
inside one of the part's closed holes, since the grid cells they free are
scrap; break cuts in material still take part. The plan fingerprint only
changes when the setting is on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 17:04:27 -05:00
ajandClaude Opus 5.5 b9fc618a50 feat(library): filter the drawing library by thickness, size, holes, date and customer
Finding a part on the nest server meant scrolling 40k drawings: the library
could only match an exact thickness or customer and knew nothing about a
drawing's size or holes. The library browser gets a Filter dialog with
inclusive ranges, and the query API takes them.

The server never parses packages and stays off IO, so the server worker now
measures each package beside its nest jobs (bounding sides, net area, closed
cutouts inside the outline) and stores the summary keyed by package hash
(schema v19). Identical geometry is measured once, an unreadable package is
recorded once instead of retried forever, and size or hole limits match only
measured drawings. Thickness ranges compare in inches across units and keep
the library's rule that a drawing saved without a thickness suits any.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 16:47:51 -05:00
ajandClaude Opus 5.5 1382d00496 chore(help): ship opennest-help v0.7.3
The guides now use the regrouped File menu labels from ecd0bba4 and show a
recaptured File menu. The release also carries the earlier unreleased guide
updates for part printouts, plate list delete/duplicate and clone push
placement, all of which are on master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 10:15:09 -05:00
ajandClaude Opus 5.5 314644b74b fix(ci): point the PDF acceptance area at the renamed report tests
ecd0bba4 removed File > Export Nest Report..., so its cancel test went
away and three report tests were renamed for the viewer path. The Windows
acceptance manifest still required the old names and failed with "expected
at least 1 cases, found 0" although every test passed. List the current
names, and add the two viewer tests now that the viewer is the only way to
get the PDF.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 10:11:41 -05:00
ajandClaude Opus 5.5 ecd0bba4eb refactor(file-menu): regroup the export and report commands
The File menu ran six output commands together with no separators, and
"Export" / "Export All" didn't say what they exported. Group them by what
they produce:

- Save Copy as .nest File... (was Export .nest...) moves next to Save As,
  since it writes the nest file rather than exporting a plate.
- Export Plate... / Export All Plates... (were Export / Export All) get their
  own group and the "..." their save dialogs call for.
- Nest Report... / Part Printouts... (were View ...) form a reports group.
- Storage Mode... gets its own separator above Exit.

Export Nest Report... is removed: the report viewer already has Save As
(available even without WebView2), so it was a second path to the same PDF.
Its guard and failure tests now drive the viewer path, and the atomic-write
test calls WriteNestReport directly. The Avalonia prototype's matching
command is renamed too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 08:41:12 -05:00
ajandClaude Opus 5.5 755f021092 feat(reporting): dimension part printouts like the Shape Library
A part printout showed the part but not its size on the drawing, so the
operator had to read the table to know which way was which. Each page now
draws the overall X extent below the part and the Y extent to its right,
as the Shape Library does: arrows, a boxed label and extension lines that
stop 6 pt short of the outline itself rather than its bounding box.

Each dimension line moves out far enough to keep its label 14 pt clear of
the part (a wide label centered on an 18 pt line nearly touched it), and
the part and its dimensions sit 28 pt inside the drawing border, double
the old padding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 08:02:09 -05:00
ajandClaude Opus 5.5 72e16139ae feat(reporting): name the thickness on part printouts
The nest report already leads its thickness with the machine config's
Name / Gauge; the part printout's THICKNESS cell now does the same
(16 ga - 0.0598"), from the same lookup in the desktop.

Thickness names are typed into the machine config, so both writers now check
them against the bundled font's Latin-1 limit like other free text, instead
of failing partway through rendering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 07:51:40 -05:00
ajandClaude Opus 5.5 2f3f105a5e feat(machine-config): name blank standard thicknesses on open
Thicknesses saved before the auto-fill kept a blank Name / Gauge until someone
edited their value, so their nest reports showed only the decimal. Opening
Machine Configuration now names every blank thickness that matches a standard
gauge or plate fraction and marks the form changed, so closing offers to save
them. Thicknesses matching none stay blank, and once saved a reopen finds
nothing to fill and does not ask again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 07:25:41 -05:00
ajandClaude Opus 5.5 35d77239b9 feat(machine-config): fill a blank thickness name from the standard gauges
Reports now take a thickness's name from the machine config, so a blank
Name / Gauge leaves the report with just the decimal. Machine Configuration
now fills a blank name from the standard steel gauge or plate fraction table
when the thickness value changes, and keeps following the value while the
name is still the old value's standard one; a typed name is never replaced.
A new thickness starts named for its default 0.25 (1/4").

Existing blank names are not filled on open, so viewing a machine never marks
it dirty; editing the value fills them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 07:25:41 -05:00
ajandClaude Opus 5.5 76e1acbc51 fix(reports): take the thickness name from the machine config
Each machine thickness already has a Name / Gauge field the shop fills in, so
the report now shows that name instead of guessing one from the standard
gauge table. It is matched the way the machine cutting defaults match the
nest's material, grade and thickness; a thickness without a name shows just
the decimal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 07:25:40 -05:00
ajandClaude Opus 5.5 21c1e0f2c7 feat(reports): drop the Units row from the summary
Every size, thickness and weight on the report already carries its own unit,
so the summary's Units row only repeated them and took a header line from the
part cards. The plate pages' ruler note still names the units, since ruler
numbers are bare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 07:25:40 -05:00
ajandClaude Opus 5.5 d2da7396ce feat(reports): move generated time to the footer and name standard thicknesses
The generated date sat among the summary's report details and printed a UTC
offset nobody on the shop floor reads. It now goes in every page's footer,
left of "Page X of Y", in the computer's own time zone without the offset.

The thickness now leads with its gauge or plate fraction ("16 ga - 0.0598 in")
when it matches one, as the thickness box lists it. Reporting references Core
only, so the desktop looks the name up in StandardThicknesses and passes it on
the snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 07:25:40 -05:00
ajandClaude Opus 5.5 696e0bdac6 feat(reporting): print one-page part printouts from the nest
Shops used CutSend to get a part printout (the part drawn to fit a Letter
page over a details table) because OpenNest had no way to make one. File >
View Part Printouts prints every part drawing of the nest, and the drawing
list's View Part Printout prints one; both open the report viewer to save
or print.

Cut length and pierce count walk the program as it is cut, so etch marks,
tab spans and display-only moves add neither, a tabbed cutout counts one
pierce per piece, and breakoff and common-line cuts count. The drawing list
item reaches the main window directly so nests made by BOM import get it
too. Rendering shares the nest report's lock, fonts and Latin-1 limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 07:10:56 -05:00
ajandClaude Opus 5.5 f146cf63f5 fix(avalonia): keep default view toggles out of settings and copy boxes for drawing
Write PlateViewDrawRapid/PlateViewDrawBounds only when changed from the default
or already saved, as the WinForms settings provider does, so a later default
change still applies. Give the render thread a copy of a broken common-line
part's box, and prune the label-point cache with the part paths.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 22:18:14 -05:00
ajandClaude Opus 5.5 8f0bd24534 docs: note the Avalonia canvas draws from the shared plate geometry
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 22:18:14 -05:00
ajandClaude Opus 5.5 af31d783cc feat(avalonia): draw plates with everything the WinForms view shows
The plate canvas now draws from the shared OpenNest.Desktop geometry
(ProgramPaths.cs is gone) and maps through ViewTransform: tabbed cutouts
fill as holes, break cuts stay out of the fill, display-only geometry is
dashed, and etch-only parts select by their marks. It adds the origin
axes, irregular sheets with hatched obstacles and their usable outline,
part numbers at the label point, cut-off lines with their segment
numbers, active and removed clamps, common lines, and the selection box.

View gets Draw Rapids, Pierce Points, Bounds, Offset, Cut Direction and
Support, per nest as in WinForms. Rapids and bounds are read from the
shared user settings (PlateViewDrawRapid/PlateViewDrawBounds) when a
nest opens and written when it closes; --snapshot ignores them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 22:18:14 -05:00
ajandClaude Opus 5.5 5edebc6144 test(desktop): pin sub-program frame origin and height-limited fit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 22:18:14 -05:00
ajandClaude Opus 5.5 049b45b394 feat(desktop): share plate-view geometry, view transform and overlays
Port the WinForms GraphicsHelper program walker to a toolkit-neutral
figure list (move/line/arc) with its layer routing: cuts without tabs,
leads, etches, and a material fill without break cuts whose tab-gapped
pieces are bridged. Suppressed moves are skipped and marks never bound
the fill. PartDisplayGeometry picks the fill as LayoutPart does
(common-line members, break cuts, open cuts) and keeps display-only
entities; PartLabels ports the label points.

ViewTransform carries DrawControl's world/screen mapping and zoom rules.
PlateOverlays computes what the view draws around parts (sheet, edge
spacing, irregular usable area, clamps, cut-off strokes, rapids, pierce
points, cut direction, offset outline, support) through the existing
Core code. PlateViewOptions reads and writes the View toggles under the
WinForms PlateViewDrawRapid/PlateViewDrawBounds names.

WinForms still draws with GraphicsHelper; nothing there changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 22:18:14 -05:00
ajandClaude Opus 5.5 8fe38398e9 feat(avalonia): load posts and machines at startup
The main window loads the post processors in Posts/ beside the app (their
materials registered) as it loads engine plug-ins, and reports any that
fail in the status line. It then reads the machines off the UI thread
through the shared MachineSettingsSource, following storage.json's mode
(the nest server's copy in Database mode), copies legacy clamps onto the
defaults' or only machine as WinForms does, and shows the offline-copy or
clamp warning in the status line. The machines and posts are kept in
DesktopSettings for later screens; nothing uses them yet.

The two posts the WinForms app ships now build with the Avalonia app and
copy to its Posts/ directory, with their settings JSON unless an edited
copy is already deployed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 22:18:14 -05:00
ajandClaude Opus 5.5 476bed1a05 refactor(desktop): share machine, nest-default and shared-settings sources
The rules behind the WinForms MachineSettings and SharedSettings move into
OpenNest.Desktop as MachineSettingsSource and SharedSettingsSource, so the
Avalonia app reads machines, clamps and nest defaults the same way: in
Database mode the nest server's copy once it holds one (machines as a
whole, nest defaults and clamps as single documents), otherwise this PC's
files; the copy is re-read after two minutes (one while offline), and while
the server is unreachable the last copy read is used, read-only. Legacy
clamps still move onto the defaults' or the only machine once, asking at
most once per session, and cutting settings edited for a nest still save
onto the machine's thickness, or this PC (stamped for that job when the
machine cannot be saved).

Nothing in the shared code shows UI: it returns the warning texts, takes
the clamp-machine question as a delegate and reads the DefaultUnit and
CuttingParametersJson settings through IMachineUserSettings (with a
user-settings.json implementation under the WinForms names). Cut-off
defaults take CutOffSettings instead of a PlateView. MachineSettings and
SharedSettings stay in the WinForms project as thin wrappers with the same
members, showing the same dialogs and reading Properties.Settings and
NestStorage as before, so their callers are unchanged.

Tests cover File vs Database mode precedence, the offline read-only copy
and its once-per-session warning, staleness and retry timing, uploads,
post settings, legacy clamp migration, the cutting-settings save and the
user-settings file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 22:18:14 -05:00
ajandClaude Opus 5.5 bfff2a3546 refactor(desktop): move post processor loading into OpenNest.Desktop
PostProcessorLoader and PostProcessorMaterials move unchanged (namespace
OpenNest kept) into the toolkit-neutral library, so the Avalonia app can
load the posts in Posts/ and register their materials as the WinForms app
does. Each DLL still loads on its own and a failing one is reported in the
result instead of stopping the others; the WinForms MainForm shows the
failures as before.

New tests load a copy of the test assembly as a post plug-in beside a
broken DLL: the broken one is reported, the good one's posts load and its
materials are registered once each, ignoring case and blanks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 22:18:14 -05:00
ajandClaude Opus 5.5 550a460778 chore: let planner and reviewer agents inherit the session model
Pinning a model in shared agent files forces that tier on anyone who
runs them; each user picks their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 21:35:00 -05:00
ajandClaude Opus 5.5 02e698ed0d chore: add planner and reviewer Claude Code agents
Track .claude/agents/ so every worktree gets the shared agent
definitions; the rest of .claude/ stays ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 21:28:27 -05:00
ajandClaude Opus 5.5 641e07b766 feat(avalonia): draw plates in the saved colour scheme
The Avalonia app applies the colour scheme saved as ActiveColorScheme in
the shared user-settings file when the main window opens, and recolours a
nest's drawings from it when opening the nest, as WinForms does.

The plate canvas draws the background, sheet, edge spacing, etch, part
outlines and selection from the active scheme through SkiaSchemePalette,
a cache of Skia colours rebuilt when the scheme's colours change, instead
of hard-coded colours. Part outlines follow the scheme's outline colour
or the fill darkened as WinForms darkens it. There is no Options screen
to change the scheme yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 21:23:48 -05:00
ajandClaude Opus 5.5 cc3711c11c refactor(desktop): move colour schemes into OpenNest.Desktop
ColorScheme, ColorSchemeRegistry, ColorSchemeSerializer and the Schemes
folder move into the toolkit-neutral library, keeping the OpenNest
namespace, so the Avalonia app can share the built-in palettes, the
scheme files and the recolour-on-open rule.

ColorScheme is now pure colour data with a Version that changes with
any colour. Its GDI+ pens and brushes move to GdiSchemeTools in the
WinForms project (still one set per thread, rebuilt after a colour
change), reached through scheme.Gdi(). The legacy part outline uses
ColorShade.Dark, a port of ControlPaint.Dark's HLS arithmetic; a
WinForms test compares the two across a colour sweep on Windows.

The registry no longer walks Application.OpenForms: Apply raises
Applied, and EditNestForm subscribes once to recolour every open nest
window as before. The saved scheme is read under the same
ActiveColorScheme setting (SavedSchemeName/ApplySaved for the shared
user-settings file); WinForms still reads it through Settings.Default.
Schemes/*.json now copies beside both desktop apps from OpenNest.Desktop.

Pure scheme tests move to OpenNest.Desktop.Tests, with new ones for
built-in round trips, scheme files, bad files, the saved name and the
recolour rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 21:23:48 -05:00
ajandClaude Opus 5.5 a7302d3224 refactor(desktop): share the large browse dialogs' sizing
LargeDialogBounds computes the bounds the large browse dialogs open at:
nine tenths of the owner screen's working area, centered, never below the
dialog's minimum size. WinForms' DialogBounds now calls it with the
screen's working area, with unchanged behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 21:13:53 -05:00
ajandClaude Opus 5.5 cb5e2a5854 refactor(desktop): share the one-time nest template migration
NestTemplateMigration converts the legacy NestTemplatePath template into
the nest defaults file when the template exists and no defaults file does,
and says whether to clear the setting (converted or nothing to convert)
or the warning to show when the conversion failed, which keeps the
setting. A settings-map overload clears it to the empty text WinForms
stores, so it runs once. WinForms' MainWindowSettings now calls it and
still shows the same warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 21:13:53 -05:00
ajandClaude Opus 5.5 725e90dada refactor(desktop): share the new cut-off head-lock defaults
SavedCutOffHeadLock reads and writes CutOffLockHeadHeight and
CutOffHeadLockOvercutInches in the WinForms format and converts the
overcut, always stored in inches, to and from each nest's units, using
1.5 in when the saved overcut is not a positive number. The WinForms
CutOffHeadLockDefaults now calls it, with unchanged behaviour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 21:13:53 -05:00
ajandClaude Opus 5.5 a687e8b193 feat(desktop): shared last cut-time settings in the WinForms format
SavedCutTimeSettings reads and writes the cut-time values last used in
Calculate Cut Time or the simulator under the names and text the WinForms
app already stores (LastFeedrate, LastHoleFeedrate, LastRapidFeedrate,
LastPierceTime as invariant decimals, CutTimeSkipEtch "True"/"False",
CutTimeMachine as a machine id or "none"), with each unreadable value
falling back to its WinForms default. It also holds the rules: never-saved
values use the cut-parameter defaults, no hole feed uses the feed, and the
saved machine falls back to the first by name. The WinForms app's
SavedCutParameters and machine box now call it; their behaviour is
unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 21:13:53 -05:00
ajandClaude Opus 5.5 3493c3bd49 feat(avalonia): open dropped .nest files, and exit with Ctrl+Q
Files dropped anywhere on the main window, over the nest view too, are
taken (as a copy) only when one is a .nest file; each opens in its own
tab, or shows its tab if already open, and other files are ignored, as
in the WinForms app. Load warnings follow as for File > Open, and as
File > Open, a drop works while Auto Nest runs.

File > Exit shows and answers Ctrl+Q, asking about unsaved nests as
closing the window does; the window placement is still saved on exit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:55:52 -05:00
ajandClaude Opus 5.5 146876b13c feat(desktop): pick the nest files out of a file drop
DroppedNestFiles.Select keeps the dropped paths with the .nest extension
in any case, as the WinForms main window does, ignoring everything else.
It returns full paths in the order dropped, each once however its path is
written, compared as NestTabs compares open files. Any says whether the
drop holds a nest file, so a window accepts it only then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 20:51:37 -05:00
ajandClaude Opus 5.5 5a627e5cc3 Merge branch 'claude/shared-post-contours'
Master added head-raise markers, common-line shared cuts and remainders to
the CI Fiber's own contour extractor, which this branch replaced with Core's
shared PostContours. Those now live in the shared model: PostContour carries
Raised, IsCommonLine and RemainderWinding, and Classify gives remainders the
perimeter macro on their perimeter's side and shared cuts no kerf side.
ContourWinding uses PortableMath for trig, as master's Core guard requires.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 19:10:16 -05:00
ajandClaude Opus 5.5 16635b7791 feat(avalonia): save, save as and export nests, and ask to save on close
File > Save (Ctrl+S), Save As and Export .nest, with Save and Save As on
the toolbar. Save writes to the file saved to this session, else asks
for one in the nest's folder; a failed save says why, keeps the nest
unsaved and points to Save As. One save or export runs at a time, a
nest cannot be closed while one is under way, and the nest Auto Nest
is working on is not written, checked again after the file picker.

Closing a nest with unsaved changes asks Save / Don't Save / Cancel, as
the WinForms nest window does; Exit asks about each unsaved nest in
turn, showing it, and reads the list again after each answer, since
Auto Nest can finish while a question is open. Cancel, or a save that
does not happen, keeps the app open; an unsaved nest still being
nested stops Exit with how to stop Auto Nest.

Nest load warnings are shown after opening, one dialog at a time and
scrolling when long; for nests opened from the command line they appear
once the window opens, and --snapshot only notes them in the status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 17:35:22 -05:00
ajandClaude Opus 5.5 8e87c0881a feat(desktop): save a nest tab to a file or export a copy
NestTabs.SaveAs writes the tab's nest, makes the file its file and name,
and clears its unsaved mark; a write that fails throws and changes
nothing. ExportCopy writes the nest without touching its file, name or
unsaved mark.

Both refuse, before writing anything, a file another open tab shows (so
one file never has two tabs) and a tab that has been closed; Export
also refuses the tab's own file, which only Save may write. Refusals
are a SaveTargetRefusedException whose message says what to do.

SavePath is the file Save writes without asking: only one saved to this
session, as in the WinForms app, so a nest that was only opened asks
for a file on its first Save.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 17:32:13 -05:00
ajandClaude Opus 5.5 fa6f664296 fix(posts): pick Cincinnati CL kerf side from the shared contour winding
The CL post wrote the configured default side (G41) on every contour,
so a contour wound against OpenNest's convention was compensated into
the part. It now takes each feature's side from Core's shared
PostContours winding rule, as the CI Fiber post does: the perimeter is
picked across the part's cut features and hole sub-program contours
are always holes. Correctly wound drawings post unchanged.

Remove the "Default kerf side" post setting; saved configs that still
carry it load as before. Expose PostContours.Flatten and Classify so a
post can classify one part's features itself.

Add CL tests for a square with a hole: the exact inline program, cuts
that match the shared contours on the sheet, G41/G42 keeping the part
out of the kerf inline and through part sub-programs, and identical
output from absolute and incremental part and hole programs. The
replay now records the compensation in force on each cut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:52:10 -05:00
ajandClaude Opus 5.5 cd623ff1aa refactor(posts): share contour geometry between posts through Core
Move the CI Fiber post's contour extraction and winding into Core as
OpenNest.CNC.Posting.PostContours and ContourWinding, so every post can
take sheet- or part-frame contours with the perimeter and kerf side
already chosen instead of re-deriving them. CI Fiber now writes from
the shared model; its golden output is unchanged.

Scribe marks are never picked as the perimeter and get no kerf. A cut
that resumes after suppressed moves starts a new contour where they
end, so it is no longer cut as a chord across the gap.

Add tests for a square with a hole in sheet coordinates, identical
contours from absolute and incremental part/hole programs, rotation,
and the kerf side for both windings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:36:44 -05:00
ajandClaude Opus 5.5 fa6781d8b1 fix(options): name clone push orders by axis, not direction
The labels said left and down, but every push follows the plate quadrant:
on quadrant 4 "Push left, then down" pushed left, then up.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 14:03:31 -05:00
ajandClaude Opus 5.5 cafb34b510 feat(clone): choose Shift+click push order and add box-only Ctrl+click
Shift+click in Clone Parts always kept whichever axis order landed the
copy nearest the origin, which can park it in a pocket the user didn't
want. Options > Clone push placement now picks Closest to origin (the old
behavior, still the default), Push left then down, or Push down then left.

Ctrl+click settles the copy on bounding boxes alone, so it stops square
to its neighbors' boxes instead of nesting into their shapes. Shift+click
keeps its box pass then geometry settle; both follow the chosen order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 07:53:24 -05:00