Commit Graph
2324 Commits
Author SHA1 Message Date
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
ajandClaude Opus 5.5 beccbae106 perf(fill): skip copy-distance searches whose result is thrown away
One Irregular decode of a small job took seconds because each block in the
catalog is built by the full Fill pipeline (PrivatePlateFill), and inside it
StripeFiller's estimate rows spent about 85% of the CPU in FillLinear's
perpendicular TilePattern. A stripe estimate is exactly one pattern tall, so
the pattern's copy distance, an all-pairs slide search, always exceeded the
room left and the copies were discarded after the search had run in full.

TilePattern now passes FindPatternCopyDistance the largest distance that
could still place a copy: for a whole copy, the room past the pattern; for a
pattern that may tile partially, the most room past any one of its parts. The distance
is the maximum over every pair, so the search stops as soon as it passes that
bound and the tile returns empty, which is what the full search led to. The
public CopyDistance is unchanged. StripeFiller's angle searches also reuse
each rotated pattern's bounding box per angle instead of cloning and
rotating the parts again on every probe.

Output is identical: the layouts of every benchmark job that finishes match
the previous build. Measured A/B on this machine, alternating builds per job
(Benchmark --engines Irregular --parallel 1, benchmark ms):

- N26-2A5: 15.5 s -> 8.8 s (12.6 s -> ~7.2 s in quieter runs)
- 73 of the 80 bench jobs (the other 7 time out at 5 minutes in both
  builds): 1,990 s -> 1,578 s total (-21%), process CPU 6,486 s -> 3,842 s
  (-41%), median job 0.91x. N26-295 now finishes in 242 s with the same
  layout where it hit the 5-minute limit before.
- Problem-nest corpus auto-nest and fill areas: all at baseline.

A parallel herringbone pair build was tried and dropped: it saved 1.2 s on
N26-2A5 but cost 10 s and 45 s more CPU on P241017-01.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 18:08:12 -05:00
ajandClaude Opus 5.5 6867ae2ee4 feat(arrange): let Fix Spacing clear overlaps on packed plates and tilt as a last resort
On a plate packed edge to edge, an overlap left by hand-placing extra
parts could not be fixed: straight push chains ran into the edge and
the relaxation gave up on pairs that share material, which have no
contact normal. The relaxation now pushes such a pair along its
shortest straight way out and lets the rest settle, which cleared a
0.92 sq in overlap on a 38-part plate with every part kept square.

When moves alone still cannot settle, the relaxation runs again as a
rigid one: an off-center push also turns a part, up to 3 degrees, if
its drawing is free to rotate. Moves stay first because a tilt turns
every part the push reaches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:45:54 -05:00
ajandClaude Opus 5.5 821c97e8dc feat(arrange): add Fix Spacing to push apart only flagged parts
Expand Spacing opens up a whole selection; nests that are a hair short
of fitting need the opposite: move only the parts the overlap check
flags, by the shortfall, and leave everything else where it is.

Fix Spacing pushes one part of each flagged pair straight away from the
other and ripples the push into neighbors it crowds, so rows already
locked in at the spacing can make room. When a plate is packed edge to
edge and no straight push fits, a constraint relaxation shares each
shortfall between both parts along the closest-point normal. Every
result is confirmed by the overlap check before any part moves, and is
applied only when it has fewer findings and nothing new.

NativeContours gains a coarse-flattening overload, Translated, Nested
and a closest-points GapTo for the search.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 14:29:10 -05:00
ajandClaude Opus 5.5 9828b9d6bc fix(drawing-list): paint the whole blank area when the side panel resizes
Windows erases a resized window before WinForms updates its cached
ClientSize, so the erase handler filled the blank area below the rows only
out to the old width. Windows then treated the new strip as erased, and it
kept whatever was on screen before. The handler now reads the client rect
from Windows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 13:30:58 -05:00
ajandClaude Opus 5.5 9273a44258 feat(engine): squeeze the Rectangles sheet that finishes the job
Auto Nest left N26-2A5 (14 door stops, 14 lock plates on a clamped 26x26
sheet) 8.0 high, while the shop's hand layout holds all 28 under 7.5 and
leaves a half-inch-deeper drop. Every MaxRects fit rule packs into the
whole sheet, so with room to spare it spreads the parts out and never
looks for the shortest strip.

When a rule places all the remaining demand, Rectangles now repacks it
into shorter and narrower bins, every rule and mode per probe, and offers
the tightest complete layout per axis to the stock loop, which still
picks by cost and offcut. Fit is not monotonic in bin size (7.5 packs,
7.62 and 7.74 do not), so probes walk down through stack heights (sums of
spaced part sides from the bin edge or a reached clamp) instead of
bisecting. A placement budget skips sheets finishing ~2000+ parts.

Benchmark, Rectangles on 80 recent production nests with salvage credit:
11 cheaper, 0 worse, all valid, total time 14.7 s -> 13.2 s.

The zero-clamp hash is re-captured (the squeezed layout is narrower);
the far-clamp test now compares poses, since the clamp can reorder an
identical layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 11:10:06 -05:00
ajandClaude Opus 5.5 7dc471bbf8 fix(engine): keep Strip layouts clear of clamp keep-outs
The Strip whole-job engine laid its strips, settled them and picked its pack
remnant from the bare work area, never adding the plate's clamp keep-outs the
Lattice orchestration uses. On a clamped sheet it put parts over the clamp, the
runner's layout check rejected the candidate, and Strip produced nothing (N26-2A5
rev 5: door stop at (21.09,1.50) over clamp-1).

NestStrips now treats InteractiveClamps.ClearanceRectangles as occupied space for
the iterative shrink strips (through RemnantFiller), settles with the clamp
obstacles when there are zones, and finds the pack remnant around them even
before any strip is placed. Unclamped plates take the old path unchanged.

A FixedStrategyNestingEngine theory runs all four fixed strategies on a clamped
26 x 26 sheet; only Strip failed it before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 10:59:21 -05:00
ajandClaude Opus 5.5 6894fcfade feat(reporting): drop the generic notes under the plate diagram
The fill/cutoff legend, the part-ID note and the "verify geometry and CNC
output" reminder repeated on every plate page and added clutter without
telling the operator anything plate-specific. Only the clamp legend (on
plates with clamps) and the ruler-units line remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 08:12:50 -05:00
ajandClaude Opus 5.5 f4fb6e1f3d feat(reporting): show clamps and sheet rulers on the plate layout view
Operators reading a printed nest report could not see where the machine
clamps sit or which ones must be pulled before cutting, and had no way to
measure positions off the layout. Plate diagrams now draw active clamps as
hatched rectangles (labels keep clear of them) and removed rail clamps as
dashed, crossed-out rectangles over the parts, with a legend line only on
plates that have clamps. A horizontal ruler below and a vertical ruler left
of the drawing read the sheet's plate coordinates in job units; the
diagram is fitted in two passes so the ruler bands match their numbers,
and part labels never enter them. Rulers draw as their own content stream
so the diagram stream holds only the sheet and what is on it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 08:02:24 -05:00
ajandClaude Opus 5.5 bffcd52db1 fix(plate-view): recompute part labels after a drawing edit
A part's sequence-number label point was computed once from the drawing's
program and cached forever. Editing a drawing in the converter replaces
that program, sometimes in a different frame, so the labels of every
placed copy stayed at the old frame's point and drifted off the parts.
Keep the cached point only while the drawing program (and its rotation)
is the one it came from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 07:48:35 -05:00
aj b329dd6c6b feat(avalonia): open several nests in tabs along the bottom
Each open nest gets a tab along the bottom of the window, above the
status bar: the shown one is white with a blue line, a dot marks unsaved
Auto Nest results, x closes it and + opens another. Inside a tab, Plates
and Drawings tabs sit at the top of the left panel (Drawings lists each
drawing's nested and required quantity) and the plate header with
first/previous/next/last sits over the plate view. Each nest keeps its
own plate and zoom when you switch.

File > Open takes several files, and a file already open switches to its
tab. File > Close and Close All, View > Next/Previous Nest and
Ctrl+Tab/Ctrl+Shift+Tab switch or close nests. This version cannot save,
so closing a nest with Auto Nest results, or exiting with any, asks first
(Cancel is the default). Other nests can be opened and viewed while Auto
Nest runs; the nest being nested cannot be closed until it finishes.
Several nest paths on the command line open in tabs.
2026-11-09 22:30:12 -05:00
aj c32f3e5df3 feat(desktop): track the open nests as tabs
NestTabs holds the nests open in the desktop app and which one is shown.
Opening a file that is already open switches to its tab instead of
loading it again, and a file that fails to load adds no tab. Closing the
shown tab shows the one to its right, else the one to its left. Each tab
records whether it has unsaved changes, so the host can ask before
closing it or exiting.
2026-11-09 22:30:12 -05:00
aj 0d7207e58e feat(avalonia): remember the main window's size, position and state
The Avalonia main window restores its last size, position and maximized
state from the shared user settings and saves them when it closes, keeping
the normal bounds while maximized. On Windows it reads what the WinForms
app saved: WinForms stores the outer window and Avalonia sets the client
area, so the size differs by the frame between the two apps. --snapshot
skips it, so its image never depends on the user's settings.
2026-11-09 22:05:09 -05:00
aj 53e5b78e3c feat(desktop): shared window placement in the WinForms settings format
WindowPlacement reads and writes a window's position, size and maximized
state under the names the WinForms app already stores (MainWindowLocation
"x, y", MainWindowSize "width, height", MainWindowState), in screen pixels.
A minimized window restores normal. FitTo drops a position that is on no
current screen and a size too big for its screen, as the WinForms main
window does when it restores.
2026-11-09 22:05:09 -05:00
aj b92d079e77 feat(data): read and write the desktop user settings outside WinForms
UserSettingsFile in OpenNest.Data reads and writes
%APPDATA%\OpenNest\user-settings.json (~/.config/OpenNest on Linux) so the
Avalonia app can share the settings the WinForms app keeps there.
AppDataSettingsProvider now delegates its file reads and writes to it, so
there is one implementation of the format.

A missing, unreadable or corrupt file reads as empty without touching it,
and Update keeps entries the caller does not change. One difference for
WinForms: an entry whose value is not text is now skipped instead of the
whole file reading as empty.
2026-11-09 22:05:09 -05:00
aj 3beaf53942 docs(agents): call PortableMath for trig in layout projects 2026-11-09 20:25:14 -05:00
aj da13801d25 ci: run the Linux unit tests in Release
The unit-test matrix was the only test job still built in Debug, and the
Engine suite was taking 26-27 of its 30 minutes. In Debug the JIT does not
optimize PortableMath (64 ns per Sin against 17 ns for the runtime's), so
after 49603edd the suite reached the 30-minute limit and was cancelled
(run 42728099456). Every other test job (windows-desktop, the Windows
release build, the server image) already runs Release, which is what
ships; no production project has Debug-only code. The Windows release
workflow still runs OpenNest.Tests in Debug.
2026-11-09 19:58:50 -05:00
aj 9627bb9625 perf(math): fully optimize PortableMath from its first call
Without tiering, PortableMath's Sin took 25.5 ns against the runtime's
15.0 ns in Release; marked AggressiveOptimization it takes 17.2 ns (Atan2
18.1 against 17.0). Results are unchanged: the cross-platform digest test
passes in Release and Debug on Linux and in Release on Windows.
2026-11-09 19:58:50 -05:00
aj 8e9946aa47 docs(engines): keep layouts the same across platforms with PortableMath 2026-11-09 19:24:25 -05:00
aj 49603eddf9 fix(geometry): nest the same on Windows and Linux
Geometry, nesting and import code now call PortableMath instead of
System.Math for Sin, Cos, Tan, Atan, Atan2, Asin and Acos (Core, Engine,
IO, Data; 269 calls in 64 files). A test fails if those projects call the
runtime's trig again.

With the runtime's trig, Windows and Linux disagreed in the last bit at
non-quarter angles and the same job could nest differently: the
learned-angle nests (a cross fixture) placed 23 parts in Strip on Windows
and 28 on Linux. Now every Engine.Tests pin gives the same layout on both
(3600 passing on Linux and on DesktopPC), Linux layouts are unchanged, and
the learned-angle rows are re-pinned to the shared layouts, which pass the
overlap and work-area checks, and run on every platform again.

Fixes Project Memory 518f3b58.
2026-11-09 19:18:00 -05:00
aj 921d100aa0 feat(math): add trig that returns the same bits on Windows and Linux
System.Math's Sin, Cos and Atan2 call the C runtime, and Windows' UCRT and
Linux's glibc differ in the last bit at many angles that are not quarter
turns. Nesting turns those bits into different tie and tolerance decisions,
so the same job nests differently on the desktop and on Linux (Project
Memory 518f3b58).

PortableMath ports fdlibm's Sin, Cos, Atan and Atan2, which use only IEEE
exact operations, and builds Tan, Asin and Acos on them with Sqrt. Results
stay within one ulp of the runtime (a few for the derived functions) and
hash to the same digest on Linux and on Windows (DesktopPC). Nothing calls
it yet.
2026-11-09 19:08:07 -05:00
aj 071531dbfc test(engine): run the learned-angle fill pins only on Windows
Nest_LearnedAngleItems_PinnedLayout has failed on every Linux CI run since
735c076a re-pinned it: the pins were captured on Windows, where they still
pass, and Linux lays the same nest out differently (Strip 28 instead of 23;
the other rows keep their count with different poses).

The split comes from the platforms' trig differing in the last bit at
non-quarter angles, which the fill stack turns into different decisions:
FillLinear starts a copy's slide exactly at the contact snap tolerance, and
equal-score candidates are ordered by raw doubles. Fixing the start gap moves
about 190 bit-exact FillLinear and layout pins and still leaves Strip and both
Remnant rows split, so the rows run on Windows only until the cause is fixed
(Project Memory 518f3b58). Nest_LearnedAngleItems_DependsOnLearnedAngles
still runs everywhere.
2026-11-09 18:40:26 -05:00
ajandClaude Opus 5.5 5668952e6b fix(import): read source drawings that are open in another program
AutoCAD keeps an open DWG/DXF locked for writing, and ACadSharp's path
constructors (and File.OpenRead / XmlReader.Create) deny write sharing,
so the CAD Converter could not import a part while it was open in
AutoCAD. Open source files read-only with read/write sharing instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 14:01:56 -05:00
ajandClaude Opus 5.5 e58e8b85c4 feat(report): add sheet weight to the plates table
The shop needs each sheet's weight for handling, so the summary's plates
table gains a Sheet weight column: one full sheet from its area, the nest
thickness and the nest (or catalog) density. A drawing's own material is
not used, since it doesn't say what the stock is; with no density the
column is left out.

Plates with copies now put the total cut time (and total weight) on a
second line instead of after a comma, which wrapped mid-phrase.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 12:59:31 -05:00