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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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.
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.
Slice 3 of common-line cutting. On costly plate (N26-295: 3/16 RA310S at about
$2,500 a sheet) pairing parts one kerf apart fits the part the spacing cannot:
the last plate's 38 parts fit on one 48x120 with Auto Nest only with pairs.
- Drawing Properties gains "Allow common line" (Drawing.AllowCommonLine, off by
default because customer edge-quality needs vary). The nest's CommonLineUsage
(Off / When it adds parts / Whenever available) is set in Auto Nest and seeded
from Tools > Nest Defaults; both persist (nest.json, defaults.json).
- NestPipeline runs the ordinary solve, then, with a level on, trials where
eligible parts are offered as kerf-gap pair compounds. Placed pairs expand into
parts plus explicit NestJobCommonLine groups, are validated again, and
CommonLineRanking keeps a trial only by the level: count first, then sheets;
"when it adds parts" keeps the plain layout on a tie, "whenever" also takes a
shorter cut or less material, never at the cost of a part. Engines, FillScore
and their comparers are unchanged; Off asks the engine exactly what it did.
- NestLayoutCheck exempts members only through a sheet's explicit group list and
only when the job allows common lines; materializing and committing create
Plate.CommonLines.
- Server nesting writes common-line jobs as version 3 and replays the recorded
trial answers, so the desktop binds the same groups.
- Fill Area honors the level through a PlateFillService overload.
- The benchmark gains --common-lines <level> --kerf <k>.
Benchmark (synthetic nests + N26-295, --parallel 1): with no flag and with
--common-lines off every engine matches the base commit; with a level on no
layout becomes invalid and three runs save a sheet at equal nested area.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
N26-295's PT42 pairs saved spacing in slice 1 but every shared edge was
still cut twice, costing a pass per edge and risking a freed part
tipping under the head on the second pass.
Plan Cutting now cuts a plate's common lines once unless the plate is set
to cut them twice. Each group is cut member by member, owner before
partner (a captured dependency that search and replay enforce, so a kept
order cutting the partner first is a constraint conflict): the member's
holes, then the shared cuts it owns as open CommonLine centerline cuts
entered through the notch on the span's axis, then its remainder pieces,
each ramped in from the kerf the shared cut opened. Capture marks the
shared spans as tabs on the owned clean copy, so replay's contour
accounting proves each perimeter is cut exactly once; a lead that fits
at neither end refuses as UnsupportedGeometry naming the span, never
shortened. Material still comes from the drawing, so utilization,
quantity and overlap results do not change. Members are never
support-tabbed; locked or excluded members and broken groups refuse.
The CI Fiber post writes shared cuts in the cut-off form with a lead-in
and no G41/G42, and remainders in the contour form on their perimeter's
compensation side, named by a remainder comment. Cincinnati CL (whose
PreApplied mode offsets nothing) and Gravograph refuse such nests, as do
the desktop and console for any post without ICommonLinePostProcessor;
nothing falls back to a silent double cut. Plate > Arrange > Cut Common
Lines Twice keeps slice 1's behavior, persists with the plate and makes
an open plan stale.
Pre-post review now checks each span is cut once, on its centerline, by
the member cut first, and the rapid check treats a finished member's
remainders as an obstacle. Compensation start in void and cancel in
kerf are unconfirmed on the CI FIBER 4020; release waits on a 3/16
stainless coupon trial.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Expensive alloy jobs (N26-295, 310 stainless) could not fit their last
PT42 at full part spacing; the plate was squeezed to 0.2 spacing with a
part 0.14 from the edge. Parts whose straight edges face each other one
kerf apart are cut on the same centerline, saving the spacing along each
shared edge.
Plate.CommonLines holds groups of parts and the kerf they were joined
at. Shared spans are derived from the placed clean perimeters, never
stored, so a moved member breaks its group instead of leaving a stale
span. Plate > Arrange > Join Common Line slides the selected parts
together at the plate's kerf; Remove Common Line ungroups them. The
plate draws shared spans as a dashed centerline.
Both spacing checks hold group members to the kerf instead of the part
spacing, and a new pre-post Common lines check reports broken groups, a
changed kerf, and leads that run into a partner. Groups persist as part
positions in an optional commonLines array that older readers ignore.
This first slice still cuts every shared edge twice; single-pass cutting
through Plan Cutting and the posts comes next.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delta review fixes:
- ReleaseRenest throws when a commit does not call finish, or calls it
twice: either would leave ledger plates uncoded or count a sheet twice
for later nests of the run. The ledger sheet count is the codes given.
- A stage whose layout is not kept now lists the copies its layout
missed with their own reasons (a part no sheet fits stays "does not
fit"); only the copies it placed are "the proposal cannot be kept".
- A valid ledger layout that places nothing says "Auto Nest placed
nothing on it" rather than claiming no part fits.
- ReleaseLedgerOffer rejects null rows; docs list every ledger note.
Review fixes for ledger-first Make Nests:
- The release renest (offer, staged run, codes, note) moves into
OpenNest.Desktop.ReleaseRenest, shared by the headless panel, the
live main window and the tests. Codes are given inside each commit, so
the live tab's Auto Nest undo step records them; before, undo and redo
dropped every ledger code.
- A bundle repeats one code per sheet: a later nest of the run lost the
whole bundle once an earlier nest took one sheet. Taken sheets are now
counted per code (LedgerCodeFilter.ExcludeCopies).
- A stage whose layout is not kept lists every copy it asked for as not
placed (the proposal cannot be kept), and a rejected ledger layout
says "MaterialLedger not used: Auto Nest could not keep a layout on
its stock" instead of a bare "No MaterialLedger stock used".
- "no free stock" is said only when the ledger had none; otherwise the
note counts what was left out (on other nests, taken by earlier nests
of this release, too small, unknown shape).
- The ledger is not read for a nest with nothing to nest, the live read
runs under the tab's stop token, a reason's own full stop is not
doubled, and the sheet count is the codes given.
Reopening a nest regenerated its cut-offs with built-in defaults (no
overtravel, a different minimum segment) rather than the machine's
settings it was made with, so a nest saved, reopened and posted could
cut scrap that never separates. Plates now record the settings their
cut-offs were last generated with, and files carry them; older files
still load with the defaults.
Saving over a nest file truncated it before writing, so any failure
part way lost the only good copy, and the error reached the user as an
unhandled exception after the save path had already changed. Nest and
settings files are now written beside the target and swapped in, and a
failed save is reported with the old file left intact.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
"Fill" said nothing about what the engine does, since every engine fills.
It is now Lattice, and Fill stays an alias, so saved selections, scripts
and API requests keep working. Engine descriptions are rewritten in plain
language and shown beside each name in the desktop engine lists; the main
toolbar's tooltip also says when Fill Area falls back to Lattice.
Docs also cover the leftover placement from the previous commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The repo mixed variants such as center/centre and color/colour, so a
name could be spelled either way and searches missed half the hits.
Standardize on the US spelling, matching .NET's own APIs (Color, Center).
Wire values owned by other systems keep their spelling: the material
ledger's "Cancelled" hold status and GitHub's "cancelled" job status.
No behavior change beyond the spelling of a few status messages.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Auto Nest nests the remaining quantities on sheets like the shown plate
with the saved engine selection (plug-ins loaded as in the desktop app),
solving off the UI thread with shared progress text in the status bar.
The button becomes Stop, which discards the whole proposal; Open is
refused while it runs. A refused result changes nothing and says why.
--auto-nest runs it on the opened nest before --snapshot renders, and
the snapshot now writes the status line to <png>.txt for headless checks.
AutoNestJob builds the desktop dialog's default job, each drawing's
remaining quantity (cut-offs excluded) on stock like a template plate,
and commits a NestPipeline result only when it is valid, keepable and
places something, onto the nest's empty plates first. RefusalReason
names why nothing was committed instead of NestRenest's release-flow
note.
The engine selection and the whole-job progress adapter use only Engine
and Data, so they move into the toolkit-neutral library for the Avalonia
front end to share. The namespace stays OpenNest.Forms, so WinForms
callers are unchanged.
Linux tests now cover the adapter's stage routing, one-based plate
numbers and descriptions, and the fill-strategy mapping; the saved
selection is left out because it reads and writes the real per-user file.
Document and Undo/ (ChangeScope, NestFingerprint, NestUndoState) use
only Core, Data, IO and Engine, so they move into the toolkit-neutral
library both desktop front ends share. Namespaces are unchanged, so the
WinForms app compiles as before; it keeps using the internal scope
factories and fingerprint through InternalsVisibleTo.
The scope, fingerprint and SaveAs/save-point rules now have Linux tests
in OpenNest.Desktop.Tests; before, only the Windows-only WinForms suite
covered them.
Add OpenNest.Desktop (net8.0), the toolkit-neutral layer the WinForms app
and OpenNest.Avalonia will share, and OpenNest.Desktop.Tests with a guard
that the library references no WinForms/WindowsDesktop, System.Drawing.Common
or Avalonia assembly, project setting or (transitive) package.
Both desktop front ends reference it; OpenNest.Avalonia joins OpenNest.sln
and the new tests join the Linux CI matrix. No code moves yet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>