The nest report PDF gains an estimated cut time: the job total on the
summary, an Est. cut time column in the plates table, and per-plate and
all-copies times on each plate page. Times come from the cut simulation
with the settings last used in Calculate Cut Time, plus clamp removal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cut simulation treated Display-layer contour moves (what cutting
strategies emit) as head-up travel, so plates with assigned lead-ins were
timed as rapids. They now cut, matching the verifier and posts, and
suppressed moves are left out.
Cut time now follows the CI Fiber post: closed clockwise contours and
cut-offs take an outside feed, other contours a hole feed, and etch-only
contours can be skipped like its SkipScribe default. The settings are
shared by Calculate Cut Time and the simulator, and plate/nest times now
always come from the simulation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Grbl-style lookahead planner re-times the cut simulation with
trapezoidal acceleration, junction-deviation corner speeds and a
centripetal cap on arcs. Machines gain cut/rapid acceleration and corner
deviation in Machine Configuration, with placeholder values until the
controller's are entered. The simulator and Calculate Cut Time pick the
machine (or full speed), and the simulator shows the live head speed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Plate > Simulate Cutting replays the active plate in cutting order: the
torch traces cuts, rapids and pierces timed like Calculate Cut Time, with
play/pause, scrubbing, 1x-250x speed, step and part/cutout jumps. Parts
the torch has not reached are drawn muted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Interactive Fill ran the best-fit pair phase first. The first pair
computation for a drawing can take about a minute, and until it finished
the progress dialog showed no layout, Accept stayed disabled and the
status text did not change.
The fill now packs the part boxes (RectBestFit) before the pair phase and
reports that layout, so Accept is available at once. The layout is only a
fallback: strategies still run in their usual order, and a completed fill
picks the same winner as before. While the pairs are computed, the detail
line reads "Computing best-fit pairs (may take a minute)...". Accept or
Stop now stops the wait. The computation keeps running in the background
and is cached for the next fill.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Item.Create builds the circle packer's view of a part: the minimum
enclosing circle of its outward-flattened perimeter (padded by the
layout check's flattening allowance), its fill ratio, the offset back
to the part origin, and the largest disc clear of material inside its
cutouts. Gears and sprockets pack by their tip circle around their
bore; the fill ratio lets a future engine decide which jobs to bid on
instead of a yes/no round test.
Adds MinimumEnclosingCircle (Welzl, repeatable order) to Core geometry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Select entities in the CAD converter (click, or drag a window/crossing
box) and right-click to exclude/include them or set them to cut, etch or
display only, regardless of their DXF layer. Delete excludes the
selection.
Roles are stored per source entity on the drawing and persisted in the
entity set as an optional `roles` map. Display-only geometry is kept out
of the program, so posts, nesting and verification never see it; placed
parts draw it as a dashed overlay. Load-time scribe repair skips
etch-layer entities set to cut. Per-entity exclusions now survive filter
changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hole can now be declared a tabbed cutout in the CAD Converter: pick a
circle or any edge of a closed hole, set the tab count and width, and drag
the tabs around the hole. The definition persists on the drawing and in the
nest file, and stays editable from the source entities.
The builder replaces the hole with open cut pieces and uncut spans on a new
Tab layer. Tabs are material, so area, nesting and overlap checks still see
a closed hole, but they are never cut. Plan Cutting and Assign Lead-ins cut
each piece from the slug with the internal lead-in and no lead-out. A lead
that would reach back over a tab is mirrored, and an arc lead onto an arc
pierces straight in. Planning accounts for each span between tabs and runs
the open-cut proof, so its own tabbed cutouts certify; tabs followed by a
lead-out are still refused. Pre-post review flags programs that still
contain tab motions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cutout cut in several pieces around tabs, as PEP programs often are, made
pre-post review report every piece's lead-out and the part's open fragments
as incomplete. Review now proves such open cuts release nothing: within the
part they touch other cuts only at their own ends and form no loop (each
closed contour with its leads counting as one point), and leads at open ends
stay out of the gap beyond them. Anything else stays incomplete. Plan Cutting
keeps refusing to certify its own tabs followed by a lead-out.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Schema v5 stores each distinct drawing package once in drawing_packages,
keyed by SHA-256; revisions point at it, so details, settings, archive and
restore revisions add no blob. The v4 drawing tables are replaced outright
(no production library existed); nest tables are untouched.
Nesting settings (cut first, priority, rotation constraints, color) become
structured record fields (DrawingLibraryRecord.Settings), returned on every
read, filterable by cutFirst, editable through the metadata endpoint and
applied by LibraryDrawingInsert. Canonical packages now leave every
record-owned field at its default, so renaming or changing settings never
changes the package hash. Saving a nest drawing to the library carries its
settings into the record.
The desktop library browser gains a Cut first column and an Edit Settings
dialog that saves notes and settings as a new revision with the same package.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drawings flagged CutFirst (e.g. test squares) are sequenced ahead of
every other part: they get their own route with the chosen method, then
the remaining parts follow. Cutoffs crossing a cut-first part still
precede it. The flag is editable in Edit Drawing, persists in .nest as
`cutFirst` (omitted when false) and is kept by split pieces.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Center Parts centred the parts on the whole plate, ignoring edge
spacing, so a wider clamp-side margin left the parts closer to the
clamps than the opposite edge. It now centres them in Plate.WorkArea()
via the new Plate.OffsetToCenterPartsInWorkArea, so each side keeps
the same extra room beyond its edge spacing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Breakoffs are now picked by their joining line, default to the smaller side as scrap
(flippable), and cut that line between tabs on a new Break layer instead of slots.
Plan Cutting and Assign Lead-ins enter each cut from the scrap with the nest's internal
lead-in, or pierce on the line without one; checks treat break cuts as intentional open
cuts that are not material.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Forming backgauge tabs drawn as open cuts came off the sheet loose and could not be
planned. A breakoff merges the scrap into the outline and cuts the shared edge as
closed slots between tabs, so it stays attached and every pierce lands in scrap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NestPipelineResult.Unplaced lists each requirement's unplaced copies with a
typed NestUnplacedReason, counted from the returned placements: no stock fits
at the sampled rotations, reserved in a cutout of an unplaced frame, stock
exhausted, plate limit, no position found, or proposal not keepable. Desktop
Auto Nest, Console --autonest and MCP autonest_plate/autonest_job report them;
the API returns NestResponse.Unplaced, persisted from response schema 5.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
NestPipeline.Run now bundles parts that fit other parts' enclosed cutouts
into their frames before the selected engine, for desktop, Console, MCP and
API alike. A valid complete bundled trial is kept; a refused trial falls back
to the ordinary solve (reason on RefusedCutoutTrial), and an incomplete trial
is compared with it, keeping whichever valid proposal places more. An
incomplete but valid trial is now keepable like any incomplete result.
Expanded sheets list each frame's inserts before the frame, so the committed
order cuts every insert before the cutout that releases its slug.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Posting review now proves containment exactly as the cutting planner does
(shared NestedPartContainment) and warns when a nested part is cut after its
host (NestedCutOrder) or when a host's hole lead or the nested part's lead
meets the other's material (NestedLead), including subprogram hole poses.
Ambiguous containment is incomplete, never clear; fixed-lead motions are not
reported twice. A committed cutout preview warns until Plan Cutting cuts the
insert first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 5.5 pinned-entry shortcut in ContourEntryFeasibility looked up the
choice's contour before the probe's own validation, so an owned choice with
an out-of-range contour ordinal threw instead of returning Incomplete as it
did before. The lookup now runs only for an ordinal inside the preparation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assign Lead-ins (LeadInAssigner) now leaves every unlocked part with active
drawing pins unchanged and returns those parts, so it never overwrites a pin
with the plate's settings; the desktop names them ("N parts kept their
drawing lead-ins (plate P: parts ...); use Plan Cutting to apply their
drawing pins."). Parts without pins, with pins switched off, or locked are
assigned or skipped exactly as before.
The pre-post review now covers drawing pins. A part with active pins whose
placed program does not cut each pinned contour exactly as the pin's own
emission (re-emitted with the part's cutting settings and matched motion for
motion) warns "Drawing pins not applied", as do locked lead-ins overriding
pins and a pin that no longer matches the geometry; pins switched off are
not reported. The contours that are cut as pinned join locked programs in
the fixed-lead re-check against every part's clean material, and fixed
leads are now also checked against active clamps (Plan Cutting itself does
not model clamps).
AGENTS.md records the fixed-emission safeguard and the characterization
digests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A drawing-pinned contour is now a fixed emission like a locked program. A
regenerated placement maps its execution motions onto the emitted contour
choices (one group per choice after the program's scribes, opened by its
rapids) and owns as fixed the lead-in, cut, lead-out and tab of every pinned
contour; the rapids between contours, including the one into a pinned
contour, stay the planner's and strictly checked. If the motions do not
split cleanly into one group per choice, nothing is treated as fixed.
So a pinned lead that meets another part's material or its own part's no
longer rejects the candidate: the feasibility precheck accepts a pinned
entry as given, the search keeps it in place, and replay reports a FixedLead
warning naming the part, cutting contour, pin and the part hit, with the
advice to increase spacing or switch off drawing pins for the part. The
plate is UNVERIFIED and needs acceptance; the lead is never shortened and
the entry never moved. The unpinned control and the same plate with pins
off are Ready.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Capture now honours a placement's pins only while they are active: a part
with DrawingPinsOff plans from the plate settings with no warning (D1's
per-part escape hatch), and Part.HasActiveDrawingPins is the shared test.
A pinned part is regenerated like any unlocked part whatever lead-ins or
settings it carried. A locked part with active pins keeps its exact program
and warns "Locked lead-ins override drawing pins on part N; Clear Lead-ins
to apply the pins." instead of being refused; an unlocked part a caller left
ineligible warns that its pins are not applied.
Apply's freshness record now holds each drawing's pin list (reference and an
explicit StateFingerprint of every pin's point, leads and tab) and each
part's pins-off flag, so editing either after capture returns Stale. The
fingerprint writer is shared between settings and pins; a reflection test
pins ContourCutPin's public surface to what the fingerprint covers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PreparedContours.Capture(program, parameters, pins) takes CutPinResolver
results for the same contours and fixes every resolved pin's contour: its
one and only entry is the pin's point, and it is emitted through the
existing emitter with the pin's own lead-in, lead-out and tab (angle
rounding off, so the entry never moves). Every catalogue returns the pinned
entry alone and emission refuses any other entry for that contour, so no
search path can relocate it. The certified tab size is the pinned outside's
own, so a pinned tab gap is genuine and replay certifies it. Unpinned
contours keep the shared settings and automatic selection; without pins the
existing preparation is untouched (5.1 digests unchanged).
Capture now resolves a regenerated part's drawing pins. A pin that does not
resolve (not on a contour, on several, sharing one, or unplaceable) is never
guessed: its contour stays automatic and the plate gets a FixedLead warning
naming the pin, making the proposal UNVERIFIED. A tab pinned on an internal
contour cannot be cut as pinned (only the outside is tabbed) and warns the
same way. Pins-off, locked parts with pins and freshness follow in 5.4.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
D1a (user, 2026-10-14): one locked cutout must not refuse the whole layout.
With confirmed parameters a locked program is now a fixed emission, accepted
as given. The replay checker and the lead validator take a per-motion owner:
a violation inside the fixed emission (a lead meeting another part's or its
own material, a rapid inside the locked program crossing a cut contour, an
uncertifiable tab gap or a partial/reversed/retraced cut) is recorded apart
instead of failing the candidate. The search never rejects or learns an order
rule from them, so fixed emissions never steer it; the planner's rapid into
the locked program and every regenerated lead keep every strict check.
Replay turns those violations into typed FixedLead findings that name the
part, contour and the part hit. A plate whose only findings are these is
BestEffort with IndependentlyReplayed set: never Ready, applicable only with
the per-proposal acceptance, and CuttingPlanService.Apply still rejects it.
Overlap, unreadable programs, stale input and cancellation still block, and
unlocked programs a caller left ineligible and the no-parameter route keep
refusing as before.
Pre-post review now re-checks locked leads against every part's clean
material, so the same violations reappear before posting.
Existing tests that encoded the old locked refusal now assert the warning;
two batch tests that used a locked crossing as their hard block use an
overlap instead. The 5.1 digests are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Freeze what Plan Cutting produces today before pins and fixed-lead warnings
touch the planner: the six synthetic nests (placed deterministically, every
other part turned a quarter) and five focused fixtures go through the desktop
batch, and the proposals, expansions and every emitted motion are hashed.
Coordinates are rounded to six decimals so last-bit libm differences between
platforms cannot move a digest. OPENNEST_PLANNING_DIGESTS appends the computed
digests to a file for the slice record.
Two red rows record today's refusals that D1a will turn into warnings: a
locked hole lead that runs into a part nested in that hole, and a locked
tabbed perimeter under confirmed parameters.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two remaining pin rules from the drawing library plan:
- D3: a library drawing added to a nest whose material, grade or
thickness differ from the library record now really arrives without
its cut pins. The insert already warned and reported KeepPins = false;
it now clears the copy's pins, because they were authored for the
library material and thickness. The library link still records the
revision the copy came from.
- A copy of a placement (Clone, CloneAtOffset) keeps its drawing pins
off, so copying or patterning an overridden part does not silently
bring the drawing's pins back. Clearing lead-ins keeps it too: pins-off
is a placement choice, not lead-in state.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Proves the source-to-program mapping the pins rely on (plan policy: prove
it or stop): the program frame is the source frame moved by
-Source.Offset, never rotated in place; a placement rotates it about the
program origin and adds the part location. Source.Angle is canonical
metadata and plays no part.
The oracle does not use Source.Offset or part rotation bookkeeping. It
recovers the rigid source-to-plate transform from the placed geometry
itself, using two holes of different sizes, and checks every pin at 0,
90 and 37 degrees, placed directly and the way engines place parts
(CreateAtOrigin), after:
- import (BuildDrawing re-origins at the first rapid);
- a converter edit that hides the first-cut hole and so moves
Source.Offset;
- a converter edit of a drawing without source entities (the derived
entities are moved back by Source.Offset);
- save and reload;
- a drawing package round trip.
To exercise the real converter paths headlessly, the desktop's entity
load and in-place apply move into CadImporter.EditableEntities and
CadImporter.ApplyEdit, unchanged; the in-place apply keeps the library
link and cut pins as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A library drawing can say how individual contours must be cut. A
`ContourCutPin` holds the entry point in the drawing's source frame plus
the lead-in, lead-out and optional tab used there; `Drawing.CutPins`
lists them. Nothing plans with pins yet (Slice 5).
- Pins accept only the exact built-in lead and tab types, copied through
the same owned copiers the planner uses, so a pin cannot carry a type
the emitter would not certify. Properties return copies: the pin is
immutable.
- A tab requires no lead-out, because the planner refuses a lead-out
after an open contour.
- The source frame is the frame of the source entities. It does not move
when BuildDrawing re-origins the program at its first rapid, so pins
survive converter edits that change Source.Offset.
`CutPinResolver` maps pins to (contour ordinal, entity ordinal, local
point) for a placement rotation, using the same `PreparedContours`
ordinals Plan Cutting will use. It never guesses: a pin must lie within
the contour closure tolerance of exactly one contour that no other pin
claims, otherwise it is reported unresolved with a typed reason. A
drawing program rotated in place (a canonical copy) is not the source
frame and resolves nothing; canonical copies and split pieces carry no
pins.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saving a nest drawing to the drawing library now has a Linux-testable model,
DrawingLibrarySaveSession, ahead of the desktop dialog.
Prefill starts from the drawing's name and customer (the nest's customer when
the drawing has none), the nest's material, grade, thickness and units, and the
linked revision's cut type and notes when the drawing came from this server's
library, otherwise the operator's last-used cut type.
Duplicates follow D4: active drawings whose normalized customer, name, material
and grade and exact thickness, units and cut type match. The server only
narrows (a blank filter means "any" there, and LIKE does not collapse
whitespace), so every key is checked again on the client. The default is the
linked drawing when it matches, else the only match; several matches require
the operator to choose, and a linked drawing that no longer matches is offered
but never the default, so a thickness change cannot silently rewrite it.
Writes follow NestSaveSession: a new drawing's id is allocated before the
upload, and a save that fails without a definite answer stays pending. The
next save first looks for the revision it would have produced and adopts it
when the package hash and details match; a not-stored new drawing is retried
under the same id, never onto another drawing, and a revision someone else
saved meanwhile is a conflict. A definite refusal (409, old server, invalid
request) leaves nothing pending. On success the drawing's link points at the
saved revision; nothing else in the nest copy changes (D2).
RemoteDrawingLibrary now implements IDrawingLibraryStore, the read/write
surface the session needs, so tests can use an in-memory store.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add from Drawing Library needs the same race-safe paging as the Database-mode
Open dialog: a generation counter so an older response never replaces a newer
one, cancellation after the superseding request is installed, and IsLoading
tracking only the latest request. `DrawingLibraryBrowseSession` mirrors
`NestBrowseSession` for library queries.
It starts filtered to the nest's material, grade and thickness, since that is
almost always what the operator wants and a mismatch drops cut pins anyway
(D3). "Show all" lifts the filter. A nest with no thickness filters by material
only, and the empty-result text tells the operator when the nest filter, rather
than an empty library, is why nothing is listed. Archived drawings stay hidden.
`IDrawingLibrary` is the read side the session and the add flow use, so both
are testable with fakes; `RemoteDrawingLibrary` implements it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adding a library drawing to a nest has several outcomes that must be decided
before anything changes, so the desktop can show warnings first and so every
rule is tested on Linux. `LibraryDrawingInsert.Plan` returns a new exact copy
carrying its library link, an added quantity on the copy already there, or a
refusal that leaves the nest untouched.
- Material, grade or thickness that differ from the nest still add the drawing
but drop its cut pins, with one warning naming each difference (D3). Names
compare by SharedListNames key and thickness at four decimals, matching the
server's own filters.
- Different units are refused: conversion is out of scope and a silent unit
mismatch would scale the part.
- The same revision from the same server only raises the quantity; a different
revision of that drawing is refused in favour of Update Drawings (D2b).
- Another drawing with the same name is refused naming both, because the
name-keyed DrawingCollection would otherwise drop one silently.
- A package whose hash or units disagree with its record is refused.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A nest drawing copied from the drawing library has to remember exactly which
revision it is a copy of, so later slices can tell when the library moved on
(D2b) and so a save can target the right drawing. `Drawing.Library` holds the
server key, id, revision and a snapshot of that revision's cut type, thickness,
material, grade, units, notes and package hash. It is immutable, so copies can
share it.
`DrawingDto.library` is additive and omitted when null. It is read leniently:
anything that cannot be trusted as a link drops the link with a reader warning
and the drawing still loads, because a nest must never fail to open over
provenance. Enum names are checked as single names since Enum.TryParse would
accept numbers and comma lists.
Copy paths: converter edits update drawings in place and so keep the link;
canonical engine copies carry it; split pieces are new geometry and get none.
Packages strip it so linking never changes a drawing's package bytes or hash.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RemoteDrawingLibrary covers upload, query, get, package download, revisions,
revision packages, guarded package/metadata updates, archive and restore. It
checks the shared bounds before sending, refuses oversized or inconsistent
pages, and verifies that the server stored the exact package it sent.
A 404 on the library routes means the server predates them: the client raises
ServerTooOldException and never falls back to another store. Because a 404 for
one drawing is ambiguous, it probes the query route before deciding between
"unknown drawing" and "server too old". Conflicts raise
DrawingLibraryConflictException carrying the current record, which the save
flow needs to offer a reload or the next revision.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Saved pairs are in the drawing's canonical frame, but loaded candidates
held the source drawing, so BuildCanonicalParts/BuildSourceParts rebuilt
a different pair for any drawing with a non-zero canonical angle (a 30
degree T-shape came back 16.57 x 10.39 instead of its saved 16.92 x 8).
That reached the Irregular engine's pair catalog and the best-fit viewer
after opening a saved nest. Import now binds every candidate to one
canonical copy, as computed candidates are; fills already passed the
canonical drawing explicitly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading a nest used to fill the engine's static BestFitCache as a side
effect, which is the only reason OpenNest.IO referenced OpenNest.Engine.
Core now defines SavedPairSet/SavedPairResult and IBestFitStore; the
mapping to BestFitResult moved verbatim into Engine's BestFitCacheStore.
NestReader/NestWriter take the store as an optional BestFitStore property
and skip bestfits/ entries without one.
Every production reader and writer opts into BestFitCacheStore.Shared, so
behaviour is unchanged; the golden round-trip test still matches byte for
byte.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Characterize the bestfits/ entries before moving their mapping out of
OpenNest.IO: golden bytes for hand-built sets, field-by-field restore of
hand-built and computed sets (kept results and used plate sizes only), a
nest without saved best fits, and the loaded-candidate frame on a rotated
source drawing, which today rebuilds to a different pair than its saved
bounds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core only uses System.Drawing.Color, which ships in the shared framework, so
the System.Drawing.Common package (Windows-only beyond primitives) was an
unneeded dependency of the cross-platform domain library. The desktop app
keeps its own reference.
CutParameters lives in OpenNest.Core but declared namespace OpenNest.Api,
forcing Core and desktop files to import the Api namespace for a Core type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the end tail was shorter than the minimum tail to keep, the planner
dropped the separator but still stopped the strip grid at it, so the last
strip ran to the sheet end and could exceed the maximum spacing (e.g. a
39-inch strip from 105 to 144 with 35-inch spacing). The grid now spans the
whole sheet whenever the separator is skipped, keeping every strip within
the spacing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A nominal ring may close within NominalContourClosure.Tolerance, and replay
already carried that admitted gap as slack when bridging it shortens the cut.
When the gap instead lies across the following edge, the emitted path starts
that edge at the ring's end point, so the cut is longer by the gap. Two checks
still compared at the exact epsilon and refused it as "invalid input":
- the retrace guard rejected any surplus length beyond 1e-8;
- Follows matched the edge's leftover sliver against the next nominal entity
and failed its direction check.
Both now use the ring's admitted slack, the same bound the shortfall and the
nominal side of Follows already use. Exactly closed rings keep the epsilon, and
a second circuit still exceeds the slack by a whole ring.
A0227-08's sprocket outline misses its start by 1e-6 and refused planning in
both keep-order and reorder modes; it now plans Ready and applies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Operators sometimes pull a clamp for a manual layout, but only Auto Nest's
last-resort search could take clamps off a sheet. Right-clicking a clamp in
Select mode now offers to remove it from that sheet or put it back, reusing
the existing per-sheet RemovedClamps, so the zones, cut-time charge, display
and .nest persistence all behave as they do for Auto Nest removals.
The change is refused while a nest run or fill may be reading the clamps,
sharing the guard the Clamps dialog already used.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The spec review found two places where the marker and threshold
contract does not hold.
A clamp narrower than the chord tolerance is still judged
tolerance-free against finely re-read material and reported as the
whole clamp rectangle. That existing safety policy is kept, so its
marker is the clamp's centre: within half the clamp's size (under
0.0005) of the material, though not necessarily on it. Document this
on PlateOverlapPair.Marker and in the overlap docs, and lock it in the
tiny-clamp region test.
Also document that 1e-8 grid rounding can move an area within
rounding error of the 1e-5 threshold to either side of it.
Far from the origin a grid step smaller than the coordinate's
precision leaves x (or y) unchanged: at 1e16, 1e16 + 1 == 1e16. The
starting grid loop then never ended and allocated cells without limit,
even with the new maxCells budget (a hang dump reached 4.3 GB). Stop a
row or column once its step no longer advances.
Grids that advance are untouched, so the bit-identical differential
against the former sorted-list implementation still passes. Found by
the independent quality review of the Clipper2 overlap branch.
The overlap crosshair sat on the pair's area centroid. For a split,
holed or concave overlap, that point can be off the red material:
between two patches, in a hole, or inside a curve.
PlateOverlapPair.Marker is a point inside the pair's largest island.
It is the island's pole of inaccessibility (PolyLabel), searched to a
tenth of the island's area/perimeter within a 4096-cell budget. If
that bounded search does not land inside (a long thin sliver), the
marker falls back to the midpoint of the widest interior run of a
horizontal scan line placed between vertex heights.
The overlay crosshair, marker hit testing, hover coordinates
("Marker:") and the cutting-inspection ring now use the marker.
Centroid stays the true area centroid. View > Overlap Check > Display
> Centroids is now labelled Markers.
Before the marker existed, its call sites used the centroid. Two
patches, a covered frame, a half annulus and a thin crescent then put
it off the material; now all four land on it. Disabling the scan-line
fallback fails its test. On the 389-plate corpus, markers add about
0.4 ms per overlapping pair; pair and area results are unchanged.
PolyLabel kept its candidate cells in a sorted List, which costs
O(n^2) through Insert and RemoveAt(0). A 48 x 0.002 sliver searched
to 1e-4 took about 27 s.
Use a PriorityQueue keyed by (MaxDist, insertion order). Equal bounds
still pop newest first, and initial cells in sorted order, so every
existing caller gets bit-identical poles. A frozen copy of the
sorted-list implementation checks this on 1800 random shapes and on
symmetric shapes with tied poles. Swapping the tie order fails 25 of
the differential rows.
A new Find(outer, holes, precision, maxCells) overload stops after
about maxCells distance evaluations and returns the best cell so far.
When the starting grid would exceed a quarter of the budget, it is
coarsened so the whole shape is sampled first. The unbounded overload
behaves as before.
The material-overlap diagnostic clipped every triangle of one part
against every triangle of the other, then carved each fragment by
every hole triangle. Curved overlaps produced thousands of fragments,
and a part sitting inside another part's cutout was slow even when
clear.
PlateOverlapAnalyzer now intersects each candidate pair's hole-aware
material once with Clipper2, in part-local coordinates on a 1e-8 grid.
Each operand is its outline minus the union of its holes (positive
fill rule). Regions become connected islands: a counterclockwise
outline plus clockwise Holes, including an island that both parts'
holes enclose together. PolygonAreaMoments.TrySubtract gives each
island's net moments. A pair is reported when its total shared area
exceeds Tolerance.Epsilon. Out-of-range or nonfinite coordinates make
the pair incomplete rather than clear. The overlay and the cutting
inspection highlight fill outlines with their holes.
On 389 real PEP-converted plates the pair, clamp-finding and issue
sets are identical to the previous kernel. Pair areas are equal or up
to 0.0002 larger: the kernel drops fragments below its area threshold.
With material prepared, the per-plate clipping maximum fell from
2.6 s to 52 ms (total 5.0 s to 0.5 s).
Engine and validation checks still use Collision.
PlateOverlapRegion vertices repeat their closing vertex, and the
desktop overlay drops that last vertex when it builds the fill path.
The tolerance-free tiny-clamp finding returned four open corners, so
the overlay drew it as a triangle. Close the ring like every other
region.
A direct CutOff.Regenerate(plate, settings) call with no perimeter
cache argument trusted Part.BoundingBox plus a sub-epsilon roundoff
allowance. A legitimately larger arc refit (ConvertProgram fitting
an imported arc's center to its chord) can protrude past that cached
box by more than the roundoff, so an uncached manual cutoff could
emit a segment crossing the part's true converted material.
Every production call site (Plate.RegenerateCutOffs,
AutomaticCutOffPlanner, the desktop cutoff tools) already builds and
passes a Plate.BuildPerimeterCache dictionary, so this only affects a
direct/uncached caller. Extract the per-part conversion out of
BuildPerimeterCache into Plate.BuildPartPerimeter and use it as a
lazy, on-the-fly fallback in CutOff.ComputeSegments whenever no cache
dictionary is supplied at all, so the uncached path gets the same
conservative envelope as the cached one instead of silently trusting
the bare bounding box. Cached behavior (including a cache's own
explicit null entries from a failed conversion) is unchanged.
OpenNest PM 13f7a685-0564-4cbf-8f46-a7805a0288a0.
A cut-off line passing exactly through a vertex of the clearance outline
got duplicate intersection hits and fell back to the part's bounding box.
Automatic strip lines sit on a 0.05 lattice that often lands on such
vertices, and the strip search preferred them because the box fallback
measured as less cutting, so planned cut-offs looked box-trimmed while a
slightly moved manual line was trimmed correctly.
Slice the polygonal clearance outline exactly with half-open edges:
crossing vertices count once, touching vertices zero or twice, edges on
the line are excluded whole. Zero clearance and non-polygon outlines keep
the existing path and its conservative fallback.