Commit Graph
2112 Commits
Author SHA1 Message Date
ajandClaude Opus 5.5 24169a5f8f refactor(desktop): share the separator line controls' pens and painting
HorizontalLine and VerticalLine had identical constructors, Dispose and
resize handling. They now derive from SeparatorLine and only say which
way the line runs. No behavior change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:26:53 -05:00
ajandClaude Opus 5.5 e231e44f72 refactor(geometry): share box containment, union, rotation and cutout range checks
Private copies of tolerant box containment, box union, vector rotation and
the cutout proposals' coordinate-range check now use one helper each:
BoundingBox.IsWithin and BoundingBox.Union, Vector.Rotate, and
CutoutCoordinates.WithinRange. Tests pin the helpers bit-for-bit against
frozen copies of the removed bodies. No behavior change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 09:25:27 -05:00
ajandClaude Opus 5.5 082a50bbcd refactor: remove duplicated client, geometry and desktop code
Share the nest server client setup and page validation, entity endpoints,
post marker checks and the repeated desktop list, cut-time and busy-warning
code. Persistence tests use NestArchiveJson. No behavior change intended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 22:13:26 -04:00
ajandClaude Opus 5.5 31525ba342 feat(cad): list keyed holes in the hole table
A closed contour around its center with at least 270 degrees of arc on
one center and radius now counts as a keyed hole, in its own row after
round holes of that diameter. Curved slots stay out: their long arcs wrap
around solid material, so the center is outside the contour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 21:44:59 -04:00
ajandClaude Opus 5.5 970d2442d5 fix(expand): prune clearance checks and let wide gaps close to the target
Expand Spacing measured every ring pair of every part pair for each trial
move, so a packed layout of parts with holes took minutes per spacing
probe. Skip ring pairs, far part pairs and off-path parts whose bounds
already settle the answer.

Every pair was also held at its full starting gap, so parts could not
move toward anything and most layouts stayed at their entry spacing.
Part pairs may now close to the spacing being tried, never below a
smaller starting gap. Cut-offs and clamps keep their full entry gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:59:33 -04:00
ajandClaude Opus 5.5 01e2238258 feat(library): fill a blank nest from its first library drawing
A nest with no material, grade or thickness now takes them from the
library record instead of warning that the drawing differs and dropping
its cut pins. The machine's spacing and cut-off settings follow. Later
drawings still get the usual difference warning, and drawings from files
carry no thickness, so they leave the nest blank.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 20:45:39 -04:00
ajandClaude Opus 5.5 59d4c8d727 feat(machines): set spacing per thickness and save defaults from a nest
Plate > Set as Default and Nest > Edit's Defaults tab only changed the
open nest, so new nests never picked up the values. Set as Default now
also saves the new-nest defaults, and the Defaults tab gains an opt-in
"Use these defaults for new nests" check.

Machine thicknesses can now set part and edge spacing. A nest takes them
when its material, grade, thickness or machine changes and when a release
creates it; BOM import seeds its groups from them. Spacing edited in the
same dialog stays, and only an untouched trailing sheet follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 19:39:48 -04:00
aj 817c4482eb test(engine): scale the Default time-limit test's limit from the unlimited solve
DefaultUnderATimeLimitReturnsAValidPlanNoWorseThanUnlimited failed on CI
(runs 41219924107 and 41224809174, Engine.Tests job): the fixed 5 s limit
assumed the unlimited Default solve takes well under a second, but it takes
about 4 s locally and longer on a loaded runner. A limit shorter than the
normal Irregular search correctly returns an earlier, worse plan, so the
assertion only holds when the limit covers that search. Use the larger of
5 s and three times the measured unlimited solve.

Red: the old test fails on one CPU shared with three busy loops; the new
one passes there. Full Engine.Tests: 2780 passed, 2 skipped.
2026-10-31 17:31:46 -04:00
aj 561f5be06c feat(engine): choose which edge strips count as offcut
Equal-cost plans break ties by the largest edge offcut, from any of the
four edges. A caller that wants the leftover off one way, such as a
shop that keeps only end drops, or a benchmark scoring the used sheet
height, had no way to say so. On the converted Nestbolt jobs the
extended Irregular search then found plans whose larger offcut is a
strip down the side, which Nestbolt scores as worse: b-blaz1-strip
3.601 -> 3.867, b-shirts-strip 2.674 -> 2.995.

NestJobOptions.OffcutCut (a CutOffAxis, default null) counts only the
strips one cut along that axis frees, for salvage credit and the
compactness tie-break alike: Horizontal the full-width bottom and top
strips, Vertical the full-length sides. PlateRemnant.LargestOffcut takes
the axis; NestJobCost, the time limit and clamp-removal recovery pass
it on. Unset, nothing changes.

Benchmark manifests accept "offcutCut": "horizontal" or "vertical".
With horizontal, one CPU, 20 s limit, Default:
  b-blaz1-strip  3.934 -> 3.333
  b-shirts-strip 2.995 -> 2.610
  m-box-deep     1.918 -> 1.276 (Nestbolt 1.251)

Manifests accept only the words horizontal or vertical (any case); a
list such as "horizontal,vertical" is rejected. PlateRemnant rejects an
undefined axis. Fixed strategies keep their own no-salvage tie-break
and ignore the option; desktop, Console and MCP do not offer it yet.

Mutation reds: 14/14 (offcut-mutations.py).
2026-10-31 13:29:50 -04:00
aj 63bcd89d35 feat(engine): Irregular searches until a time limit
Under a time limit, Irregular often finished in about a second and left
the rest unused: on the converted Nestbolt b-jakobs1 job it used five
sheets where four fit.

NestJobOptions.SearchUntilCancelled asks an engine to keep searching
after its normal search until the token is cancelled.
DeadlineNestingEngine sets it. Irregular then re-runs its six variants
with seeded per-type weights on its placement rule (seeds 1, 2, 3, ...,
each weight within e^+-0.4), reports each plan that beats its best,
then re-plans that plan's tail and reports any gain. It skips this when
the token cannot be cancelled, nobody listens for progress, nothing is
placeable, or the job has one part type, so unlimited solves are
unchanged.

Default now runs Irregular last (Rectangles, Circles, Irregular) so
Circles still runs, and chooses only after every candidate has run, in
tie-break order, so the run order never changes the choice. Unlimited
choices match the old run order {Rectangles, Irregular, Circles} except
when three candidates are near-tied within the 1e-9 comparison
tolerance without being transitively ordered (only on all-round jobs,
where Circles bids).

b-jakobs1 on one CPU, 10 s limit: 5 sheets (4.500) -> 4 (3.751).
2026-10-31 12:29:19 -04:00
aj 8df4c73093 fix(benchmark): make manifest quarter turns exact
"rotations": [0, 90, 180, 270] mapped to automatic rotation, which
allows any angle: Rectangles placed parts at minimum-area angles such
as 279.8 degrees in jobs that allow only quarter turns. Quarter turns
are now a 90 degree sweep from 0 to 270. Without "rotations", a part
still rotates automatically (any angle), as before 2dab9232; the doc and
comment that called that the quarter-turn default are corrected.
2026-10-31 10:54:52 -04:00
aj 7dc8cbb22d fix(engine): fire time limits from a dedicated thread
DeadlineNestingEngine used CancelAfter, a thread-pool timer. A CPU-bound
Parallel.For keeps pool threads on their own local work, so the timer
callback can run seconds late; pinned to one CPU it waited for the
whole best-fit search (m-sign-3 under a 10 s limit returned at 64.5 s).
A dedicated background thread now waits for the limit, or for the
solve to finish, and cancels; the solve joins it before returning.

A test runs a CPU-bound Parallel.For from a pool thread under a 200 ms
limit and requires the solve to end within 3 s; with CancelAfter it
took about 7 s.
2026-10-31 10:38:55 -04:00
aj 9431006edd fix(irregular): stop waiting for the best-fit search on cancellation
PairCatalog waited for BestFitCache's search without the solve's token,
so a cancelled or time-limited Irregular solve kept running until the
search finished: minutes on one CPU for the many-segment gear, sign and
garment parts of the Nestbolt benchmark (m-public-gear-1 ran 312 s under
a 60 s limit, m-garment-teagan-1 45 min). It now uses the cancellable
overload, which stops the wait and lets the search finish in the
background and fill the cache. The drawing is private to the call or to
the caller, as that overload requires.

A test blocks the pair evaluator and checks Build throws within 5 s of
a 200 ms cancellation; with the old call it waited the full 30 s.
2026-10-31 10:38:55 -04:00
aj 2dab923259 feat(benchmark): accept rotation sets in manifests
A manifest part may list "rotations" in degrees: [0] locks it, [0, 180]
locks it at 0 with the half turn allowed (Allow180Equivalent), and
[0, 90, 180, 270] is the automatic quarter-turn default. Any other set,
or "rotations" together with "allowRotation": false, is an error naming
the part. Without it, "allowRotation" behaves as before.

Lets converted external benchmark jobs (Nestbolt's 0/180 garment and
box jobs) keep their own rotation rules instead of being widened to
quarter turns.
2026-10-31 08:52:17 -04:00
aj dd473b6d38 feat(benchmark): add --time-limit
Wraps each solve in DeadlineNestingEngine so a solve stopped at the
limit scores the best valid plan its engine reported; with none it is
reported as timed out. The harness's own five-minute abandon timeout
grows to the limit plus a minute so the engine's limit always fires
first. Values outside 0.001 s to the timer's range, or a missing value,
fail with exit 1.

On the converted Nestbolt m-sign-1 job, Default with --time-limit 60
returns 96/96 parts on one valid sheet in 60.6 s wall, where an
unlimited run took over 300 s.
2026-10-31 08:49:27 -04:00
aj 23d5362aa1 feat(engine): return the best valid plan at a time limit
DeadlineNestingEngine wraps any engine with a wall-clock limit (any positive time up to
the cancellation timer's ~49.7 days). An engine that finishes in time
keeps its result, as does a result it returns after the limit. When the
limit cancels it, the best whole-job plan it reported (PlanImproved,
ranked like Default ranks candidates, a tie going to the later plan) is
returned if it passes the layout check; with none the solve fails with
TimeoutException. The caller's own cancellation is never turned into a
result. Engine plate commits are dropped and the returned result's
commits reported once it is known; plans are forwarded only when they
beat every plan shown so far.

Default now runs Rectangles, then Irregular, then Circles, so a search
stopped early already holds a valid plan. Ties still break by candidate
order (Irregular, Rectangles, Circles), so for these three the choice is
unchanged, and when every candidate throws, the first in that order is
still the one rethrown. A test seam takes a run order. Desktop, Console
and MCP do not apply a limit yet; Console can solve a job twice (cutout
trial and plain), which needs one shared deadline.

Mutations killed (10/10): no fallback at the limit, invalid plan
accepted, caller cancellation becomes a result, engine commits
forwarded, a worse later plan replaces the best, a tie keeps the earlier
plan, no upper limit bound, failure rethrown in run order, built-in run
order unchanged, run order ignored.
2026-10-31 08:49:27 -04:00
aj 8fb95e57b5 docs(geometry): correct slide band index comments
Reach is 2*sqrt(eps)*(1 + separation), the curve bound is an upper bound
on what the tests accept, and a curve extent is its circle +-|r| with the
slack carried by the reach. Comment-only; from the 55910d1f review.
2026-10-31 08:27:52 -04:00
aj 55910d1f9d perf(geometry): bucket slide entities by axis band
The band test still visited every entity pair. For boundaries of 24 or
more entities, EntitySlideEvents now builds bucket indexes over each
entity's perpendicular extent per enumeration (both sides for vertex
rays, the line-only side for arc-to-line, the stationary curves for
tangency) and visits only the bucket members, ascending, before applying
the same exact band tests. Range queries merge the ascending bucket
lists without repeats. Each extent is the band test's own acceptance
bound widened by a reach of 2*sqrt(QuadraticSlack)*(1 + largest
along-axis separation) for the quadratic tests plus a rounding term;
bucket numbers are monotone in the coordinate, so a query returns a
superset of the accepted entities in their original order and the
emitted events are unchanged. Geometry with any coordinate that is
nonfinite or beyond the band's coordinate limit is not indexed.

SlideBandTests adds large random boundaries (24-160 entities) to the
differential comparison against the frozen pre-filter enumeration,
reruns the band-edge sweeps with decoy entities so they go through the
index (including a far circle reached through the moving offset), and
checks index queries directly. Mutations killed: zero reach, line extent
without span slack, repeats kept by the merge or the sorted path, last
bucket skipped, points just above the minimum treated as empty, curves
in the line index, arc and tangency queries ignoring the radius, moving
index or measurement without the moving offset. Not pinned by a test:
the reach's rounding term (headroom).

Measured on converted Nestbolt jobs (Default engine, hermes, quiet
machine, one run each), before -> after: m-box-tool 47.1 -> 19.2 s,
m-mechanical-mounting-kit 45.9 -> 27.3 s, m-mechanical-cog-washers
74.7 -> 44.2 s, m-mechanical-small-brackets 23.0 -> 16.6 s. The nine
completed jobs keep identical placements.
2026-10-31 08:16:03 -04:00
aj a28b543634 perf(geometry): skip slide pairs outside the axis band
An axis-aligned slide can only meet an entity whose extent across the push
direction holds the ray. EntitySlideEvents.Enumerate now tests that band
before each vertex ray, arc-to-line pair and curve-tangency pair, and skips
only pairs whose kernels cannot emit. Margins cover RayLine's segment
tolerance (twice Eps of the span) and the rounding of SolveRayCircle's
discriminant. A pair with any coordinate or radius that is NaN, infinite or
beyond 1e150 is always kept, because the kernels' products can then turn
NaN and still emit. Surviving pairs run in their original order, so the
emitted events, and every slide distance, are unchanged.

SlideBandTests compares the full event sequence and FirstBlocking result
bit for bit against a frozen copy of the pre-change enumeration: random
line/arc/circle boundaries at four coordinate scales with offsets landing
on or beside the tolerance band, an arc swept across a nearly parallel
line's band edge, and nonfinite or overflowing coordinates along the push.
Mutations killed: zero span slack, zero quadratic slack, circle-line test
ignoring the radius, with zero margin or a shrunk radius, tangency using
the difference of radii, no coordinate guard, no vertex guard, moving
offset ignored, axes swapped. Not pinned by a test: the span slack's
factor of two and the quadratic slack's absolute floor (rounding headroom).

Measured on converted Nestbolt benchmark jobs (Default engine, hermes,
one job at a time): m-box-tool 70.7 -> 40.7 s, m-mechanical-cog-washers
115.1 -> 70.0 s, m-mechanical-mounting-kit 72.7 -> 44.9 s. The nine jobs
that completed (five M jobs and four B jobs; m-sign-1 still exceeds
300 s) produced identical placements before and after.
2026-10-31 08:06:03 -04:00
aj ad5f2a9041 feat(cad-converter): add a Hole Table window that counts and colors holes by size 2026-10-31 05:50:22 -04:00
aj 45ab837c8c feat(cad-converter): drag a dimension by its value
Pressing a dimension's value text now grabs it like its line does. The
label keeps its offset under the pointer, so a radius or diameter value,
which sits past its leader's end, no longer jumps when grabbed. Hit boxes
are measured the way painting draws them, at the control's DPI.
2026-10-31 05:40:26 -04:00
aj a392cc0863 fix(cad-converter): skip the Dimensions fill during FilterPanel construction
AutoScroll runs a layout inside the constructor before dimensionsPanel
exists, so every CAD Converter form threw NullReferenceException.
2026-10-31 05:28:58 -04:00
aj e628345947 fix(cad-converter): stretch the Dimensions list to the bottom of the side panel 2026-10-31 05:23:22 -04:00
aj 08d646413a build(help): ship help v0.6.3 with dimensions, SVG, sheet outlines and cut-off gaps 2026-10-30 23:09:33 -04:00
ajandClaude Opus 5.5 ca693285bb feat(cad): add radius and diameter dimensions
Holes and radii need sizes on the drawing as well as the overall extents. Select
circles or arcs, right-click and choose Add Radius Dimension or Add Diameter
Dimension. The value is measured live from the curve, found by its entity ID, so
it follows edits; only the leader angle and label offset are saved. A radius on
an arc stays on its sweep, and labels are kept clear of the curve and center.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:47:56 -04:00
ajandClaude Opus 5.5 05d5c56ac0 fix(cad): run extension lines to the offset part profile
Extension lines started at the extents' corners, which float in empty space on
angled or irregular parts. They now run from the dimension line toward the part
until they first meet the profile offset by a fixed screen gap, so they reach
the feature they measure without touching it at any angle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:34:31 -04:00
ajandClaude Opus 5.5 abced0a7ec feat(cad): add overall dimensions to the CAD Converter
Shops need a part's overall size on the drawing while converting it. Width and
height are measured live from the geometry that will be cut, so they follow
filters, exclusions, simplifying, breakoffs and tabbed cutouts instead of going
stale. Only the placement is saved; dimensions never touch the program.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:34:31 -04:00
ajandClaude Opus 5.5 e947c3ad86 feat(cutoffs): keep meeting lines on a dragged cut-off
Limits are plain coordinates, so moving a cut-off left the lines trimmed
to it stopping where it used to be. A drag now takes along perpendicular
lines with a limit or gap edge on its center, extending or trimming them
to keep meeting it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 22:09:01 -04:00
ajandClaude Opus 5.5 3a8994c8fe feat(io): import and export SVG drawings
Customers and design tools hand over SVGs, and getting them in meant a
trip through another CAD tool. CadImporter now reads them like any other
drawing, and plates, programs and converter entities can be written out.

An SVG's numbers are not drawing units, so import converts to the nest's
units and never guesses: only an absolute width or height counts as a
stated size, since px means 1/96 in to browsers but 1/72 in to
Illustrator. Without one the desktop asks, showing the size each choice
gives, and headless callers must pass the scale or the import fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 17:50:41 -04:00
ajandClaude Opus 5.5 4e3be68c11 chore: remove stray Python bytecode and ignore it
A bytecode cache from running the script tests was committed by accident
in the spelling cleanup; nothing ignored it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:12:55 -04:00
ajandClaude Opus 5.5 4d55e11318 style: use one spelling across the codebase
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>
2026-10-30 16:11:30 -04:00
ajandClaude Opus 5.5 2a128c5a11 fix(planning): stop cutout leads at the feature centre line
Small features got leads that ignored their shape: full 0.15 leads at a
shallow angle along letter strokes, stubs at the pierce zone's edge in
round holes that could be pierced in the middle, and a lead aimed at a
letter's far pole that ran longer than configured.

Plan Cutting now limits a lead to the feature's depth at its entry, where
a second wall becomes as near. The pierce-zone fallback goes there first
and otherwise aims beside the entry rather than at the whole cutout's
pole. Pinned contours keep their leads, and wide cutouts are unchanged.

Centred pierces exposed the line/circle tangent test: its absolute
discriminant tolerance called a radial line through a hole under about
0.056 radius tangent, reporting a false contact at the centre. Tangent
now also requires the line to stay at the rim, which keeps true grazes
as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:31:27 -04:00
ajandClaude Opus 5.5 667beb4334 fix(cutting): start planning from the user's settings
Edits in the Cutting Parameters dialog only reached user settings, while a
plate's stored parameters took priority, so a change was lost on any
planned plate unless Apply succeeded. User settings now win; a plate's own
are the fallback when none are saved or readable.

WinForms tests saved to the real user-settings.json, and with user settings
winning a test's lead-ins would feed real planning, so they now use a temp
file and skip the legacy settings carry-over.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 14:06:11 -04:00
ajandClaude Opus 5.5 bb6bdc8213 test: run dotnet test on a hidden desktop
WinForms tests open real windows that take focus from the user's work.
Test-Hidden.ps1 runs them on a separate Win32 desktop in the same window
station, so forms keep real handles and message loops while nothing appears.

A job object ends the whole tree on any exit, and a timeout stops runs stuck
on a modal dialog nobody can dismiss there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 13:16:47 -04:00
ajandClaude Opus 5.5 b550640ef0 refactor: merge duplicated geometry, settings-file and form helpers
Roslyn Bridge flagged these as copy-paste clusters; each pair now shares
one implementation so a fix lands everywhere at once:

- Shape.OffsetInward/OffsetOutward differed only in the target winding.
- BestFitFinder kept its own copy of SpatialQuery's arc-extreme points.
- SplitDrawingForm rebuilt DrawingSplitter's clip-region grid for preview.
- GeometryOptimizer's collinear/coradial finders (one misspelled
  GetCoradialArs) become one generic FindRelated.
- Five settings files repeated the write-with-retry loop, and two also
  repeated the unit parser and unreadable-file filter (SettingsFile).
- ClampsForm and SupportGridForm repeated their row/box builders.
- Vertical/HorizontalRemnantComparer share a RemnantComparer base.

Behavior is unchanged except that Engine/NestStorage settings loads now
also tolerate NotSupported/Security exceptions, matching the other
settings files' never-throw contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:59:47 -04:00
ajandClaude Opus 5.5 4028dafb26 feat(cutoffs): trim a middle piece into a gap in the line
Trim refused the piece between two crossing cut-offs because a cut-off had
one start and one end, so a line could not be stopped short around a pocket
of parts without deleting it and drawing two. A cut-off now carries Gaps,
stretches between its limits left uncut: segment generation excludes them
like parts, so the line splits into segments that each take their own place
in the cut sequence. Trimming a middle piece adds a gap from one crossing
centre to the other (merged with any it touches), Extend near a gap's edge
closes it, and Swap Through-Cut refuses a junction a gap touches rather
than guess which side of the gap should run through.

Every reader of the limits treats a gap as uncut, so nothing waits for,
warns about or credits a cut that never happens: segment spans stop at a
gap, the cutting dependencies skip a part inside one, the clamp check
ignores a clamp inside one, the cutting-state and overlap stamps go stale
when gaps change, and a gapped line is never full span for the expected
remnant or automatic cut-offs. Gaps persist as an optional gaps array;
older versions ignore it and would cut straight through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:59:46 -04:00
ajandClaude Opus 5.5 7d9f900633 feat(posts): add the Cincinnati lower pallet function (M43/M44)
Lasers with the Lower Pallet Special Function option need M43 on before
the program's first move. It is written before GOTO1 so restarting at a
later sheet still runs in lower pallet mode, and is off by default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:54:57 -04:00
ajandClaude Opus 5.5 27665c0ded test(planning): report each plate's budget headroom in the corpus
Fixes to the cut planner kept reopening nests fixed earlier. The budget
counts expansions, so outcomes repeat, but the corpus only ranked the
outcome and could not show a plate passing just inside its budget, the
kind a reordered search tips into NoSolutionWithinBudget. Each plate now
reports the share of its budget its strict search spent, and the run
ends with the plates at 80% or more, to compare with master's run.

part-order and entries reorder the search on every plate, so naming
either in OPENNEST_PROBLEM_NESTS_CHECKS now plans every nest instead of
only those tagged with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:54:56 -04:00
ajandClaude Opus 5.5 2c1a8eef42 refactor(reporting): split NestReportBuilder.Capture into focused helpers
Capture had grown to one 120-line method mixing plate validation, part
placement counting and drawing reconciliation, with a local function
holding the ID state. Separate those steps and move ID assignment into a
small registry so each rule can be read on its own. Validation order and
the snapshot are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:44:39 -04:00
ajandClaude Opus 5.5 47f9593a41 fix(planning): pierce arc text where a fitting lead-in cannot route
A plate of tags with arc-outline digits found no plan, although each
cutout had a clear entry. Eroded pierces were offered only when no
configured lead fit, so a letter with one fitting lead on the wrong
side stayed stuck with it, and an uncertain lead check skipped erosion
altogether. Recovery also spent its allowance on the outside entries
and accepted the first it reached, leaving a part on a side the next
rapid had to cross it from.

Count uncertain leads as no fit. Give recovery a last, separately
funded try that adds eroded pierces for the outside entries ranked
ahead of the first reached, and hold back routes that end facing away
from the next cut until then. Drop a lead that touches only in the
whole emission, where rounding moves it, rather than rebuilding the
same chain. Plans reached from their best entry keep their entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 12:38:32 -04:00
ajandClaude Opus 5.5 e76864b489 fix(engine): nest alike on every machine by fixing PairFiller's batches
FillItem_WithClampZones has flipped between two digests because each was
correct for the machine that pinned it. PairFiller sized its parallel
batches from Environment.ProcessorCount, and a batch shares the best count
and work area from before it, so core count decides which candidates are
tried. With 10+ logical processors candidate 9 runs before the area shrinks
and its 46-part layout wins for every comparer (9BF5FC7FC47364A9); with 8 or
fewer the area has shrunk, it can only reach 45, and Fill/Strip and Vertical
Remnant settle on different 45-part ties. 40 of the parts are kept either
way. So the same nest differed between shop PCs, and CI failed whenever the
digests were pinned on a 12-thread desktop.

Fix the batch at 8, which keeps the pins CI has always produced. Also claim
GridDedup keys in candidate order before the parallel loop: the claim was a
race inside a batch, seen as one candidate returning 42 parts and then 33
in the same process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:16:05 -04:00
aj 7a7b671408 fix(tests): select the library row before Add/Edit in DrawingLibraryFormTests
The drawing library tests relied on DataGridView picking a current row by
itself when the page is populated. It only does that when the first row is
already laid out as displayed, which the hosted Windows runner does not
guarantee. Without a current row SelectedRecord() is null, Add/Edit return
silently, and the test fails later:

- run 41057280460: EditSettings_SavesCutFirstAsANewRevision_AndTheAddAppliesIt
  "Timed out waiting for the reloaded page."
- run 40312036013: MismatchWarning_IsShownBeforeTheAdd_AndCancelLeavesTheNestAlone
  expected 2 messages, got 0.

Make the first row current explicitly after each page load (and after the
edit reload that precedes an Add), and make Run() rethrow a faulted task so
a failed step reports its own error rather than a 15 s timeout.
2026-10-30 09:17:13 -04:00
ajandClaude Opus 5.5 d80002e832 chore(help): ship help v0.6.2 with the set wording
Help v0.6.2 counts release quantities in sets and recaptures the release
screens, so the bundled guides match the release panel again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:58:15 -04:00
ajandClaude Opus 5.5 508b7db0df fix(engine): stop rejecting arc pairs placed at the part spacing
On a plate with clamps, MaterialsTooClose measured gaps on polygons faceted
at 0.001, which understate an arc gap by more than the 0.0005 slack. A pair
interlocked exactly at the spacing read as too close, so FillGroup discarded
every copy and the fill silently placed nothing. Confirm the gap on
NativeContours, as the layout check and pre-post review already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:41:22 -04:00
ajandClaude Opus 5.5 4ca8b0debf fix(ui): call set quantities sets in the release panel
The release panel was the only place that counted a set in kits;
everywhere else in the drawing library calls them sets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 08:25:23 -04:00
ajandClaude Opus 5.5 889e33d179 feat(ui): stop new sheet cut-offs at the first crossing
A manual cut-off ran the full sheet even when another cut-off crossed it,
leaving a Trim/Extend pass to clean up what Automatic Scrap Cutoffs does
on its own. New lines now run from their start edge, set by the cut
direction, and stop at the first crossing line. Tab switches to full
length for the lines that should run through, and the choice lasts until
the nest is closed.

Suggested-by: alexvyperus
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:20:56 -04:00
ajandClaude Opus 5.5 1c2bf16b3e feat(data): give the Generic Laser real grades, leads and stock sizes
Mild steel splits into CR, HR and HR P&O, stainless into 304 and 316,
and aluminum into 5052 and 6061, so no thickness carries a grade it is
not sold in. Gauge thicknesses use MaterialLedger's decimals, which
also corrects stainless gauges that used carbon-steel values.

Every thickness gets a 60 degree line lead-in (0.15", longer from
1/4" up) and a 0.1" lead-out, with no lead-out on inside cuts, a 0.75
cut-off overtravel and every catalog plate size up to 72x144.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:09:46 -04:00
ajandClaude Opus 5.5 6ca9a1243c fix(ui): enlarge the app icon's glyph so it matches taskbar neighbors
The sheet glyph filled only about half of a near-black tile that vanished
on the dark taskbar, so the icon read much smaller than other apps'.
Scale the glyph 1.3x, lighten the tile and give it a pale edge, then
re-render every .ico frame (16-256 px) directly from the SVG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:33:40 -04:00
aj c313d9699e test(engine): restore the clamped fill pins the code produces
0719476 re-pinned the Fill, Strip and Vertical Remnant clamp-zone
characterization digests to 9BF5FC7FC47364A9, but that layout never
reproduced: CI and local Linux runs at that commit and since give the
previous digests unchanged, so Cross-platform tests has failed on every
push since. Clamp clearance defaults to the bottom edge spacing, which
these plates already used, so their layouts were never expected to move.

Checked the produced layouts before restoring them: all four strategies
place 40 parts inside the work area with no NestLayoutCheck violations
against the clamp clearance.
2026-10-29 19:45:49 -04:00
aj 73a02cf309 feat(avalonia): run Auto Nest from the Nest menu and toolbar
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.
2026-10-29 19:14:55 -04:00