ContourEntryFeasibility: one adapter over the EXISTING LeadPathValidator
for one owned candidate — emit through the S06 diagnostic seam, read at
the placement position, certify emitted lead-in AND lead-out against ALL
placed material. Only complete+clear qualifies; blocked and incomplete
keep their reasons as distinct verdicts (an incomplete check never
reads as clear). Not a new collision implementation and not a plan
approval: NoLeadIn passes vacuously and the complete-plan missing-lead
check still runs later; rapids and pierce clearance stay with their
existing checkers.
One instance is one captured planning attempt: verdicts cache per exact
owned choice + node context (settings/placement are fixed per instance),
never static, never across instances. Lazy: only the checked candidate
is emitted; EvaluationCount counts validator executions for cost tests.
Foreign/malformed emissions are Incomplete verdicts with reasons, never
crashes; cancellation propagates without poisoning the cache. No search
changes and no broad-phase skips.
EmitCandidateForValidation emits exactly one owned contour with its
normal lead-in and lead-out and its ORIGINAL contour type — the
perimeter keeps External even with no holes before it — through a small
shared EmitChosenContours seam, so a perimeter candidate's emitted leads
can be validated before the hole choices exist. The probe is a throwaway
program: never installed on a Part, never a complete plan. Normal Emit
and EmitPrefix keep the perimeter-last gate untouched (still throwing
for Emit([perimeter]) on a holed part); foreign and re-stamped choices
still fail ownership/geometry validation and source shapes/settings are
never used directly. Differential tests pin per-contour block fidelity
(External-vs-hole lead geometry, five lead styles, two hole
choice/orders, scribes once, reversed winding, rotated capture, circle
rounding/clamping without cross-contour state) — contour lead geometry
is choice-independent, only absolute accumulated coordinates drift by
ulps with program head.
Pure deterministic ordering of the automatic catalogue: classify each
point by its nearest bounding-rectangle side(s) with the preparation
tolerance (a corner belongs to two sides), choose the facing side pair
from the look-ahead target against the centre (right/left and top/bottom
per the source plan), then order by matched facing sides descending,
rank tier ascending (corner, midpoint/tangent peer tier, fallbacks),
then arrival->entry + entry->target travel, then the stable geometric
key. Without a target (last part) the tier leads and distance to the
arrival breaks ties — never the plate origin. The input list is never
mutated, no candidates are added, no cap is applied and entity order is
never the tie-break; reversed and cyclically reindexed drawings rank to
the identical geometric order.
AutomaticEntryCandidatesWithFallbacks completes the uncapped catalogue
with tier-3 fallbacks after the preferred points: native arc midpoints,
the eight compass points of a whole circle (a pure-circle contour is now
usable instead of refusing), near-convex-corner points on each incident
straight edge back from the corner by 2 x the applicable lead-in length
using the emitter's own SelectLeadIn semantics (non-length lead-in styles
contribute 0 and the fallback is omitted, never approximated), and the
exact target-facing closest point toward the caller's look-ahead. Every
fallback runs through the same reflex/cusp exclusion — a raw closest
point or an inset landing on a forbidden inside corner is dropped — and
the same geometric duplicate merge; short edges omit an out-of-segment
inset by projection parameter instead of extrapolating. No ranking and
no lead-safety verdict yet.
Internal PreparedContours.AutomaticEntryCandidates builds the uncapped
preferred start catalogue per contour from the contour's own winding:
convex corners, then straight-edge midpoints, then line/arc tangent
joints, deduplicated geometrically with the preparation epsilon (the
most preferred metadata wins at equal points). Reflex and cusp vertices,
collinear splits and circles are never enumerated for automatic placement;
hole slugs classify from their own travel. Manual Entry/ClosestEntry and
the legacy Entries API are unchanged; emission wiring comes in S09.
Scoped dotnet format removed the unused entityIndex parameter from
TraversedBackwards_SquareCornersStayConvex, leaving MemberData supplying
two values to a one-parameter theory (8 xUnit failures at test discovery).
The parameter is now genuinely used: each geometric corner is classified
from both adjacent entities of the reversed contour.
Expose TryClassifyAutomaticStartCorner: an internal read-only query over
the emitter's existing TryGetCorner/ClassifyCorner with the same winding
derivation EmitContour uses, so start-point planning can prefer convex
corners without copying tangent math or touching lead generation. Corner-
kind characterization covers convex, reflex, tangent-smooth and cusp
vertices from either adjacent edge, both windings, under rotation, and
rejects midpoints and open contours.
The editor header and the Plan Cutting dialog counted the trailing empty
new-plate workspace PlateManager.EnsureSentinel maintains, so a one-plate
nest read 'Plate 1 of 2'. PlateDisplayNumbering now derives display-only
numbers that exclude only a trailing empty sentinel; interior empty plates
keep their slot and number, and navigation, storage indexes, exported
names and batch selection are untouched. The sentinel itself is labeled
'New plate (empty)' instead of being numbered beyond the shown total.
The saved-nest browser opened maximized with MaximizedBounds set from the
owner screen's working area in absolute coordinates. Windows reads that
position relative to the monitor, so on a secondary screen the offset was
applied twice and the modal dialog was placed off-screen, leaving the
disabled main window with no way back to it.
Open it instead as a normal window sized to 90% of the owner screen's
working area and centered there; it still clears the taskbar and moves
between monitors like any other window.
The engine argument's description now names every built-in engine with what
it suits, so a model can choose one; omitting it still means Default. The fill
tools resolve an omitted strategy to Fill directly instead of through the
session's whole-job default. Front-end tests cover the MCP and console
defaults and keep the description in step with the registry's built-ins.
Default is the engine every front end uses when none is named. It now runs
Irregular, then Rectangles, checks both layouts with NestLayoutCheck and keeps
the best: valid first, then fewest unplaced parts, then lowest salvage-credited
cost; ties keep Irregular. A candidate that throws or returns nothing is
skipped, cancellation stops the search, and only the chosen layout's plate
commits are reported. Neither engine wins every job in the lane benchmarks,
and Rectangles adds little time while also covering Irregular's invalid
layouts.
The registry lists Default first and no longer maps the name to Fill; fill
strategy callers still read Default as Fill. A future circle/ring engine joins
as another candidate.
The multi-phase lattice fill (linear, pairs, rectangle best-fit, remainder) is
no longer meant to be the engine used by default, so it gets a name for what
it does. The registry lists it as Fill and maps the old name Default to it;
PlateFillService, PlateNesterFactory and NestJobOptions use Fill, and every
fill-strategy caller still accepts Default. Console --autonest now validates
engine names through NestingEngineRegistry.ResolveName so renamed names work.
Layouts are unchanged: Default and Fill resolve to the same fillers and the
golden layouts pass under the new name.
Plan Cutting refused ordinary filleted parts with "Native contact query is
numerically uncertain." Material capture checks every curve pair of a ring,
adjacent ones included. Where a line meets a tangent arc at their shared
vertex, rounding can drop the tangent root of the native line/circle
quadratic; the exact ray cast from the line's far end then reached the
vertex, which is already recorded as an endpoint contact, and was read as a
contact the native query missed. Rounded rectangles rotated off-axis were
refused 1037 times in 1080 before this change and 0 times after.
The exactness rays now stop short of a line endpoint the other curve already
contains (half-way from each end when both are contained), so together they
still cover every other point of the line and any unrecorded contact still
refuses. The native kernel and tolerances are unchanged.
Regressions: four tangent-fillet rings and a rotated filleted part planned
through CuttingPlanBatch (red before, green after); a line ending inside a
small circle's contact band stays uncertain in both directions. Mutations that
treat either endpoint as always contained, drop the start ray, or count
recorded endpoints as native contacts all fail.
Project Memory: 41880037
Runs the four cross-platform suites as a matrix and the six synthetic
Irregular nests in their own job, with a fail-closed final "tests"
check.
Conflict in ci.yml: take the branch's Linux jobs and keep master's
windows-desktop job unchanged after them. actionlint and the aggregate
checker self-tests pass on the merged file.
Brings in the Plate menu reorganization, Database-mode drag-and-drop
open, the SavedNestsForm look fixes and the Irregular block-fill work
budget fix.
Conflicts:
- MainForm.Designer.cs: keep the branch's Plate menu order (Arrange
submenu, View in CAD last) and add master's Plan Cutting item ahead
of the lead-in commands.
- SavedNestsForm.cs: keep master's browser rewrite (details, preview,
columns) and apply the branch's intent to it: open maximized with
MaximizedBounds clear of the taskbar, the lighter grid palette on all
three grids, and B/KB/MB/GB file sizes.
The second delta review compared the filtered checks with the previous
implementation on 5,400 generated cases. Rapid checks matched in every
case, but 114 lead checks differed: a long lead passing a small circle
or arc well over 0.001 away was reported clear, while the native
line/circle query, through rounding in its squared terms, reports a
contact there. A coordinate limit and a fixed margin cannot bound that
cancellation.
Lead checks therefore examine every other part's material again,
exactly as before the filter; LeadMaterialSnapshot no longer keeps an
extent. Rapid checks, including the pre-post review's, keep skipping
completed contours more than 0.001 clear of the rapid. The 900000-long
lead beside a radius-0.0001 circle is a regression test.
Planning a dense 144-part grid now takes about 19 s again (lead checks
dominate); a new part order is still found where the old search gave
up.
The delta review found two more ways the extent filter could skip a
check a native query would have flagged:
- Native line intersections accept points 0.00001 outside each line's
bounding box, so a lead 0.000003 from another part touched it while
their extents were 0.000003 apart, beyond the 1e-6 margin.
- Around a circle of radius 5e11, rounding let the native query count
a rapid at x = -0.00001 as touching although the circle's extent
started at x = 0; the margin scaled only with the rapid's own size.
Rather than chase each tolerance, the filter now has one narrow rule:
Extent.IsClearOf skips only when both extents are finite, lie within
1e6 and are more than 0.001 apart on some axis. 0.001 is ten times the
widest absolute band of any native contact query (the 0.00001 box
allowance and the 0.0001 contact reach of tiny arcs), and within 1e6
rounding stays far below it. Larger geometry is always checked in
full, as before the filter. Planning speed is unchanged (144-part grid
about 0.5-0.8 s).
All three reproductions are regression tests, with a test of the rule
itself.
Follows c1c8d5f and c21f687: an arc's extent is its supporting circle widened to the native contact reach, and an extent with a nonfinite bound is never skipped.
Review found a plate the previous search planned that the tour now
refused. Left leads in on its left and right on its right, so right
straight after left crosses left and left straight after right crosses
right. Learning "right before left" then contradicted "left before
right" and the search returned ConstraintConflict, although cutting a
third part above them in between is safe.
A blocked approach only proves that one part cannot follow the parts
cut so far from that position, not a global order, so learned rules
stay a heuristic. Once nothing new can be learned, the remaining budget
now goes to a full search over every ready part, nearest first (the
search used before the tour). A rule contradicting an order already
required is still skipped rather than ending learning early.
IsSeparatedFrom promised that NaN bounds are never separated, but a
finite axis could still compare as separated when the other axis held
NaN. Program reading already refuses nonfinite coordinates, so no
public path was affected; the helper now checks every bound and the
margin are finite before comparing, which also covers the empty
extent of an incomplete material snapshot.
Review of the extent filter (bfe2e51) found it could skip checks the
native queries would have flagged. ContactAfterStart counts a point as
touching an arc while its squared distance from the centre is within
1e-8 x max(1, 2r) of r^2, which for small arcs reaches well past the
radius (up to 1e-4 as r -> 0), more than the filter's margin. A rapid
0.000002 outside a radius-0.001 circle lost its crossing, the pre-post
review lost the same finding, and a lead grazing a radius-0.003 circle
turned from an uncertain check into a clear one.
An arc's extent now uses that contact reach, computed from the same
slack expression as ContactAfterStart, so every point a native query
can count as contact lies inside it. The three cases above are
regression tests.
The desktop batch searched for a new part order with the flat 20000
default and kept the current order with 400 expansions per part. A new
order now also plans contour order and entries for every part (about
260 expansions per part on a dense grid), so a 100-part plate ran out
of budget and fell back to the current order. Both attempts now get
PlateBudget: 400 per part, at least 20000. The constants are renamed
MinimumExpansionBudget and ExpansionsPerPart to match.
Free-order planning was one depth-first search over parts, contours and
entries. A dead end at one part backtracked through every entry
combination of the part before it (about 1,450 for a square with two
holes) before trying another part order, so a 4 x 4 grid of such parts
ran out of its 20000 expansions (and 200000) although cutting it row by
row is safe.
The whole-part order is now an open travelling-salesman path over part
centres from the start point: nearest neighbour, then 2-opt reversals
and Or-opt moves of one to three parts, never placing a part before a
cutoff or nested-part prerequisite. The existing search then plans
contour order and entries along that order. If a part cannot be
reached without crossing parts already cut, the search learns "cut it
before those", backs up to just before the earliest of them, keeps the
parts cut before that point and re-plans the rest from the tool
position there. An attempt stops backtracking after 8 x entries x
contours expansions without getting further, so it learns instead of
retrying the entries of every earlier part. When nothing new can be
learned the result is a refusal, as before. A preserved order is
planned exactly as before.
16- and 36-part grids, in row order and shuffled, are now ready within
the default budget (they were NoSolutionWithinBudget); a 144-part grid
plans in about half a second.
Every lead was checked against every placed part's material and every
rapid against every completed contour, so each check cost O(parts) and
planning a plate cost O(parts^2). Lead checks were 88% of planning time
on a 144-part grid.
LeadMaterialSnapshot and each completed contour now keep a conservative
extent (an arc counts as its whole supporting circle). A lead or rapid
skips material or a contour only when the extents are farther apart
than 1e-6 x (1 + coordinate size), far above the contact tolerance, so
results are unchanged: anything touching or closer still gets the full
native check. The rapid filter also applies to pre-post verification,
which shares ReleasedContourState.
New tests cover the cases just inside the skip: an arc lead and a
completed arc whose bulge reaches past their endpoints, and a lead and
a rapid that only touch another part's extent.
1b422ef read absolute-mode hole subprograms by converting an
incremental-mode copy of every clean program. Rebuilding absolute
endpoints from incremental deltas is not exact: after a rapid at
1e12 a 1x10 rectangle moved by about 2.4e-5 and a real 2e-5 overlap
was reported clear, in the overlap overlay and pre-post verification
as well as Plan Cutting.
Convert programs directly again, which reads absolute coordinates
exactly, and refuse an absolute-mode subprogram as an incomplete
check instead: the converter adds a call's frame offset to
incremental moves only, so it would read such a hole at its frame
origin. OpenNest writes hole subprograms in incremental mode. The
null-list and unknown-instruction refusals from 1b422ef stay, and
CopyForGeometry is private to the planner again.
Capturing plates runs inside the dialog's message loop, where only
argument, operation and not-supported exceptions were caught, so any
other capture failure escaped to the editor. Show every capture
failure in the summary with Apply disabled instead.
PlateOverlapAnalyzer converted clean programs directly with
ConvertProgram.ToGeometry, which adds call offsets only to
incremental moves, so holes in absolute-mode subprograms were read at
their frame origin. A square with two absolute holes reported
"Native material contours cross or touch", and the Plan Cutting
overlap gate blocked it while the incremental twin passed. Convert an
owned incremental-mode copy instead (PreparedContours.CopyForGeometry,
now internal, the same normalization the cutting planner uses).
The analyzer also threw on a program whose Codes list was null and
cast or cloned instructions it does not know. It now refuses a
missing list and anything other than the exact built-in instruction
types as an incomplete check, before any copy or conversion runs.
Pre-post verification and the plate overlap overlay share this path.
A disposed PlateView stayed subscribed to its plate, so a plate that
outlived the view kept rebuilding the disposed view's layout and
raising its events; Dispose now detaches the part added, removed and
reordered handlers.
ObservableList.Reorder keeps repeated references, but the reorder
handler mapped each part to one layout, so [a, a, b] collapsed to one
shared layout for both occurrences of a. It now reuses each existing
layout once and creates one only when none is left.
Review fixes for the Plan Cutting batch and dialog:
- A plate whose clean part material overlaps, or cannot be checked
for overlap, is no longer ready, whatever its route. The batch
captures each plate's material with PlateOverlapAnalyzer on the
owner thread, analyzes it on the worker, and names both parts.
Before, two overlapping squares were Ready and Apply regenerated
them (plan section 4.2: overlap warnings are not waived).
- BuildPreview returns null for a refused plate, whose program graphs
may be unsafe to copy (an unsupported instruction's Clone ran, and a
cyclic subprogram overflowed the stack), and for a plate that
changed after planning, which drew the replayed program at the live
pose. The dialog then shows no preview and says why.
- The dialog plans with its own copy of the caller's settings, shows
a failure message if a plan cannot be presented, and has a worker
seam so the close-while-planning test holds the worker instead of
racing a slow search. Form tests now observe the planning task.
The dialog relied on Progress<T> and an await continuation capturing
SynchronizationContext.Current when planning started. WinForms
uninstalls its ambient context when the outermost DoEvents loop ends,
so a plan started outside a message loop reported progress from a
thread-pool thread and crashed the test host with "Error creating
window handle" (windows-desktop job of run 37398775280,
CuttingPlanFormTests.ApplyAfterALiveEdit_ChangesNothingAndReplanRecovers).
Capture the dialog's context once when it is built and post progress
and the finished plan to it explicitly. The busy-editor test now
starts planning after a DoEvents loop to cover that path.
Plate > Plan Cutting... and Nest > Plan Cutting (All Plates)... open
one dialog over CuttingPlanBatch. It starts from the plate's (or the
last-used) cutting settings and plans at once; Cutting Settings and
Keep the current part order replan. The summary lists every plate
with its status and findings, and a read-only preview shows the
active plate in the proposed order with its proposed programs.
Apply is enabled only when every plate is ready and installs all of
them or none; a stale plan keeps the dialog open and asks for a
replan. Closing while planning cancels the worker and waits for it.
The menu commands share the busy guard of the other plate tools, and
after a successful Apply the confirmed settings become the saved
defaults. The older automatic sequencing and lead-in assignment
commands stay until they are migrated and retired.
PlateView now follows Plate.PartsReordered, which an applied cutting
plan raises once per changed plate: it puts its layout parts in the
plate's order (the drawn part numbers are the cutting order), marks
them dirty so new programs are redrawn, and raises its own
PartsReordered. The overlap overlay treats a reorder like any other
part change and marks its report out of date.
CuttingPlanBatch captures every scoped plate on the owner thread with
an owned copy of the confirmed cutting parameters, plans them on a
worker with per-plate progress and cancellation, and returns a
proposal that applies all or nothing through CuttingPlanService.Apply.
A free-order search that ends NoSolutionWithinBudget is retried once
with the current part order, captured up front so the worker never
reads live plates; the proposal reports that the order was kept. A
kept order gets 400 expansions per part (at least 20000), since it
still searches contour order and entries.
Only after Apply succeeds does each plate keep its own copy of the
settings. The proposal builds detached preview plates (quantity zero)
and describes each plate's status and findings with part numbers.
The first windows-desktop run of BomImportFormTests (PR #4, run
37379965924) failed four cases:
- InvalidQty_IsRefusedAndTheOldValueKept("0" / "-1" / ""): the tests
committed with DataGridView.EndEdit(), which never raises
CellValidating, so the grid's default parser stored 0, -1 or null.
- BlankingMaterialOrThickness...: a null value from CellParsing is
ignored by the grid, so a blank material was stored as " ".
Operator commits (Enter, leaving the cell or the grid) do validate, but
a programmatic EndEdit() must not store text validation refuses either.
CellParsing now keeps the row's current quantity or thickness for text
validation would refuse, trims the material (a blank one is ""), and a
cleared thickness or quantity cell stores null through the columns'
DataSourceNullValue. A thickness of only spaces is refused like any
other invalid text.
The tests now commit by pressing Enter (ProcessEnterKey), as the
operator does, and a new ProgrammaticCommitOfInvalidText test covers
the EndEdit() path.
The BOM import dialog's Parts table now binds to the BomPartRow objects
themselves (a BindingList with Designer-defined columns) instead of a
DataTable of strings:
- Columns: Item #, File Name, Description, Material, Thickness, Qty,
Status. Only Material, Thickness and Qty are editable, and only on
rows that have a drawing; those rows are grey and locked. Rows that
still need input are tinted amber.
- Qty is editable. Anything but a whole number of 1 or more is refused
in the cell with an error icon; Esc restores the old value. A
blank-in-BOM quantity shows a tooltip saying 1 is used.
- A thickness that is not a number above 0 is refused the same way;
blanking it marks the row "Needs thickness".
- Every edit goes through the row's bound part, so the Groups tab
(part count and Total Qty) and the summary follow at once, and an
edit can never land on a different part. Rows stay in BOM order:
the headers no longer sort, which removes the sort-then-edit defect
where index mapping applied an edit to another part.
- Create Nests first finishes the cell being edited.
BomImportFormTests (OpenNest.WinForms.Tests) cover the columns, the
quantity edit and its group total, refused quantities, edits after
the rows are reloaded in another order, locked rows and blanked
material/thickness. They first run on the windows-desktop CI job; the
sort defect's red evidence is source review, not an executed test.
Project Memory: 5b0808d1-7353-4a34-87df-d1fbfc38f2b4
Prepare BOM rows for quantity editing:
- BomQuantity.TryParse accepts only a whole number of at least 1
(surrounding spaces allowed; no sign, decimal, exponent, separator or
overflow).
- BomPartRow keeps the BOM's own quantity (BomQty), refuses invalid
typed quantities in TrySetQuantity, and raises PropertyChanged for
Material, Thickness and Qty plus the status they affect, so a bound
grid can refresh.
- A blank BOM quantity becomes 1, as it was imported before, but is now
visible in the row and counted in the summary ("had no BOM quantity
(1 used)"). A BOM quantity below 1 is no longer imported as 0: the
row reads "Needs quantity" until the operator enters one.
BomImportFlowTests covers BOM items to rows to an edited quantity to the
group total and the created nest's required quantity. Mutation reds:
a zero floor, signed or decimal input, ignoring the quantity in the
status, not assuming 1 for blanks, setting unparsed text and dropping
the status notification each fail the Bom tests.
When two BOM rows named the same drawing file and shared a material and
thickness (the same part in two subassemblies), Create Nests imported
the file twice. Nest.Drawings is a set keyed by drawing name, so the
second drawing was dropped with its quantity: PT01 x2 plus PT01 x3
gave a nest needing 2, while the Groups tab showed 5.
Each drawing file is now imported once and needs the total of its
rows. Build_CombinesRowsThatUseTheSameDrawing required [2, 1] where
[5, 1] was expected against the previous commit.
Project Memory: 44293f97-a027-4c29-bc2d-bb789c796746
A BOM row with a file name and a matching drawing but a blank thickness
was reported "No DXF" and locked, so its thickness could not be entered
and the part was dropped: BomAnalyzer skips such items before looking
for a drawing, and the form only knew drawings the analyzer matched.
A row with a blank material read "Matched" but was silently left out
of every group.
Rows now resolve their drawing through a DrawingFileIndex shared with
BomAnalyzer (whose behavior and tests are unchanged), and a row's status
is computed from its values:
Ready | Needs material | Needs thickness | No drawing found | No file name
Rows with a drawing stay editable, and an edit updates the status cell.
Groups take only Ready rows (a zero, negative or non-finite thickness is
not Ready). The summary line counts ready rows and each problem.
Build_RowWithoutThickness_StillFindsItsDrawing failed at its DxfPath
assertion against the previous commit. Mutation reds: removing the
material check, accepting zero or non-finite thickness, skipping the
drawing for rows without thickness, and grouping without the status
each fail the Bom tests.
Project Memory: b8e3a978-2cfe-4fe7-b2a5-eb01c6c54679
A BOM row whose File Name carried its extension ("PT01.dxf") showed
"No DXF", was locked and was never imported, although BomAnalyzer had
found the file: the matched paths were stored under the raw BOM name
and looked up by the name without the extension. Both sides now use
the extension-less name.
The new BomImportRowsTests row failed at its IsEditable assertion
against the previous commit.
Project Memory: f3be7a4b-e679-470c-bb52-7057899228e4
Move the BOM import dialog's row building, grouping and per-group nest
construction out of BomImportForm into OpenNest.IO/Bom so they can be
tested on Linux:
- BomPartRow (now public) and BomImportRows.Build, a verbatim move of
the form's BuildPartRows (still via BomAnalyzer).
- BomImportGroups.Build: one grouping used by both the Groups tab and
Create Nests, which each carried their own copy. Create Nests now
creates nests in the Groups tab's order (material, then thickness).
- BomNestBuilder.Build: the nest for one group (saved defaults first,
then the group's plate size, spacing, material and thickness; one
drawing per row with the row's quantity, 1 when blank).
The form keeps the file dialogs, grids, EditNestForm windows and the
completion message. Behavior is otherwise unchanged; the two row-status
defects found while planning are fixed in the following commits.
Nest Info edits only the material name, but SaveNestInfo replaced the
nest's material with new Material(name), so pressing OK without touching
the material silently dropped its grade and density.
SaveNestInfo now goes through NestMaterialSelection.Apply in OpenNest.Data:
a name with the same SharedListNames.Key as the current material (trimmed,
whitespace collapsed, invariant upper case) keeps its grade and density
and takes the typed name; any other name gets a name-only material, as
before. It always returns a new Material. SharedListNames.Key is the
name normalizer the shared customer/material lists will use.
Behavioral reds (each restored byte-identically): restoring the old
name-only path fails both keep-grade tests; exact-string matching,
dropping the upper-casing or the whitespace collapse fails the
normalized-name test; returning the current instance fails the copy test.
EditNestInfoFormTests cover Load -> Save through the real form; they are
cross-compiled here and execute in the windows-desktop CI job.
The release workflow is the only hosted job that runs the Core, Engine,
IO and Server suites on Windows, and it had no hang guard: a silent test
would hold the runner until the 30-minute cap with no clue which test
hung. Apply the guard the windows-desktop CI job already uses:
--blame-hang-timeout 5m with a mini dump, and upload all of TestResults
rather than only the TRX files.
The suites have roughly doubled since the last release run (7.5 min),
so raise the job cap to 45 minutes. Validated with actionlint.
Open browses server records in Database mode, with no way to load a local
file. Dragging .nest files onto the window (or an open plate) now opens
them from disk in either storage mode; PlateView forwards file drops via a
new FilesDropped event instead of swallowing them, gated on having a
listener so other PlateView hosts are unaffected. A dropped document is
unbound, so Save in Database mode creates a new record on first save via
the existing storage-mode logic.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Open maximized, but with explicit MaximizedBounds (WorkingArea minus
20px) instead of trusting default maximize behavior, since the
bottom button row was rendering behind the taskbar.
- Lighten the grid's borders, header and row-header colors so the
default dark gridlines don't dominate the view.
- Format File Size as B/KB/MB/GB instead of a raw byte count.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Align Selected, Pattern Tile and Expand Spacing lived under Tools even
though they only operate on the current plate's parts, while Center
Parts and Sequence Parts (the same category) lived under Plate. Folded
them into a new Plate > Arrange submenu and left Tools with just
generic tools and app/nest configuration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The plate preview read DataGridView.CurrentRow in SelectionChanged, but
that event runs before CurrentCell moves, so CurrentRow still named the
previous row. Choosing a row showed the plate that had been selected
before it, and the next-plate button snapped the preview back to the
old plate. Read the selected row instead.
The Windows job of run 37341610182 caught it:
PlatePreview_StepsThroughThePlates_AndFollowsThePlatesTable expected
"Plate 2 of 2" after the next-plate button and got "Plate 1 of 2". The
test now also checks that the selected row follows the button.
The Details area of the Database-mode Open dialog now has a plate
preview beside the Plates/Drawings tabs, with a draggable divider. It
draws one plate of the highlighted nest; the arrow buttons below it step
through the plates ("Plate 2 of 5"), and choosing a row on the Plates tab
shows that plate, so the table and the preview stay on the same plate.
The preview is read-only (no selection or drop) and refits when resized.
NestDetails keeps the downloaded nest's plates (PlateLayouts) beside its
rows, so the preview draws the copy already read for the tables with no
second download.
Tests: NestDetails keeps every plate in nest order (fails with the
assignment removed). The Windows form test steps forward with the
button, checks the row follows, and selects a row back.
File > Open in Database mode now shows a resizable browser instead of
the small list dialog. The upper Nests grid keeps the server-side
search, sort and paging (row numbers count through the filtered list;
the title shows the server and the range). Below it, Details tabs list
the highlighted nest's plates and drawings, read from its archive after
the highlight rests for 250 ms; moving back to a nest already shown
reuses its result, and a missing archive is reported in the details
line.
Enter opens the highlighted nest (in the Find box it runs the search at
once instead) and Esc closes. Double-click opens a nest, and a context
menu offers Open, Delete (after confirmation) and Refresh. It only
browses saved nests; new nests are still created from the main window.
Windows tests drive the real form: page load, details for two nests,
Enter opening the highlighted one, the missing-archive message and Esc.