Delete OpenNest.Engine/Nfp (AutoNester, BottomLeftFill, NfpCache,
SimulatedAnnealing, INestOptimizer, PlacedPart, SequenceEntry), the Core
InnerFitPolygon, and the NestPhase.Nfp member. None had callers outside the
folder: console --autonest and MCP autonest_plate call engine.Nest(), not
AutoNester.
Drop the Nfp cases from NestPhaseExtensionsTests, fix the --autonest help
text, and update CLAUDE.md. NoFitPolygon stays; BestFit pair evaluation
still uses it.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
PairFiller.EvaluateCandidates wrapped its candidate loop in
FillStrategyRegistry.SetEnabled(...) and reset it with SetEnabled(null)
afterwards. Nothing inside that window re-enters the strategy pipeline
(EvaluateCandidate and FillRemnantBox only use FillLinear), so the
restriction had no effect on the running solve. It did mutate process-global
state: concurrent solves (e.g. benchmark --parallel) could observe the
narrowed strategy list, and the reset cleared any filter set elsewhere.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and
line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests
pass after reformat, full solution builds 0 errors.
Added .csharpierignore so csproj/config XML keeps its existing layout
(CSharpier's XML wrapping churns attributes with zero benefit).
Formatting is now enforceable: dotnet csharpier check . passes.
The witness-probe overlap test missed small corner intersections: its
candidate points (crossing-edge midpoints and vertex-centroid midpoints)
can all land on a part boundary or outside the intersection, so two 10x10
parts at (0,0) and (9,9) with zero spacing were accepted despite sharing
a 1x1 unit of material.
Route the overlap decision through Collision, which clips triangulated
polygons and keeps only positive-area regions, catching corner overlaps,
containment, and coincident poses while legal edge/corner contact stays
legal. Collision's hole subtraction was conservative (partially-clipped
triangles were kept whole), so a part inside another part's cutout could
false-positive depending on triangulation alignment; subtract holes
exactly instead: a piece outside a convex hole triangle is the union of
its clips against each edge's outside half-space.
SortStrips measured the gap between only the first two strips in original
placement order and replayed that single value between every strip after
reordering by height/width. Real (non-uniform) geometry produces varying
inter-strip gaps, so resequencing could expand the total footprint beyond
the plate's already-fitted work area, crashing StripPlateNester with
"Candidate placement falls outside the usable stock area." Using the
actual required spacing guarantees the resequenced span never exceeds
the original, since real gaps are always >= spacing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Implements a sealed adapter class that forces a fixed IPlateNester strategy onto
any NestJob, overriding the job's own PlacementStrategy while preserving MaxPlates.
Delegates all multi-plate allocation and stock selection to NestJobRunner.
This allows single-plate nesting strategies to compete as full whole-job
INestingEngine solvers in benchmarks, enabling comparative performance testing
of placement algorithms across various job configurations.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Task 5 of the whole-job engine API: add a geometry safety gate that
validates every candidate trial before the runner commits accounting.
- NestJobPlacementValidator: closed-contour validity, rotation-policy
compliance, work-area containment per quadrant, hole-aware material
overlap, and required part spacing. Overlap is interior-only, so
zero-clearance edge/corner contact remains a valid placement.
- NestJobValidator: route candidate validation through the geometry
gate; reject unusable/unclosed/degenerate contours up front.
- NestJobRunner: wrap candidate evaluation in a progress bridge that
tags legacy engine detail with the current candidate context.
- LegacyPlateNesterAdapter: forward IProgress to the legacy engine so
its progress surfaces under the active candidate.
- Tests: geometry (quadrants, rotations, touching, containment, holes,
empty stock, real Default/Strip smoke), validation, and cancellation
suites; repaired test fakes that emitted out-of-bounds or overlapping
placements the gate now correctly rejects.
Engine.Tests: 70 passed, 0 failed, 0 skipped in Debug and Release.
Windows-only OpenNest.Tests not run on Linux.
LegacyPlateNesterAdapter.Create now delegates to PlateNesterFactory
instead of carrying its own minimal Default-only switch, so built-in
strategy resolution has one source of truth. Behavior unchanged:
unknown keys still reject; all four built-ins now resolvable.
44 net8.0 tests pass in Debug and Release; 0 warnings.
Replace name-based quantity deduction with reference-based drawing
identity in the engine paths the whole-job runner reaches
(NestEngineBase fill/pack, StripNestEngine deduction, RemnantFiller
ledger, IterativeShrinkFiller leftovers). Add instance-scoped
PlateNesterFactory that resolves built-in strategies without touching
the global NestEngineRegistry. Add identity and engine-selection tests.
44 net8.0 tests pass in Debug and Release; no new warnings.
Change net8.0-windows to net8.0 in Core, IO, and Engine projects so
the libraries can be consumed in Docker containers running on Linux.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Parts that already overlap the moving group are now excluded from the
obstacle list so they don't block the push direction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replaces PartBoundary polygon edges with PartGeometry.GetOffsetPerimeterEntities
(inflated Line/Arc entities) so arcs are handled exactly without the polygon
sampling error that previously required a bboxDim + PartSpacing clamp. Adds
bbox DirectionalGap / PerpendicularOverlap early-outs to skip pair checks
that can't produce a valid slide, and removes the now-unused PartBoundary
cache, GetPatternLines/GetOffsetPatternLines helpers, and ComputeCopyDistance
clamp.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
NfpSlideStrategy wrote to nfp-slide-debug.log on the Desktop on every
call. The console's SetUpLog created test-harness-logs/ next to input
files but nothing in the codebase wrote to Trace, so those files were
always empty. Drop both along with the --no-log flag.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
FillContext.ReportProgress dereferences Policy.Comparer, so any caller
that forgot to set Policy hit a NullReferenceException. Default to
FillPolicy(DefaultFillComparer) so tests and ad-hoc callers work without
boilerplate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PairEvaluator was cloning the full CNC program (including all internal
cutouts) for every candidate. For parts with many holes (e.g. 952),
this caused O(n²) overlap checks and thousands of unnecessary polygon
tessellations per candidate.
Now extracts the perimeter shape once, builds a lightweight drawing
from it, and uses that for all Part.CreateAtOrigin calls. Cutouts are
irrelevant for best fit — only the outer boundary matters for pairing.
75x speedup on a 952-hole rectangle (30s → 0.4s).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
External lead-ins now sit on the line between the last internal cutout
and the next part's first pierce point, minimizing rapid travel. Cutout
sequencing starts from the bounding box corner opposite the origin and
iterates 3 times to converge the perimeter lead-in and internal sequence.
LeadInAssigner and PlateProcessor both use a two-pass approach: first
pass collects pierce points, second pass refines with next-part knowledge.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Phase 3 curve-to-curve direct distance in CpuDistanceComputer to
catch contacts that vertex sampling misses between curved entities.
Enforce minimum copy distance in FillLinear to prevent bounding box
overlap when circumscribed polygon boundaries overshoot true arcs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Iterate all remnants instead of only the first when packing and filling
- Improve ScoreZone with estimated part count and aspect ratio matching
- Cache bounding boxes in SortItems and remnants in TryPlaceOnExistingPlates
- Make TryConsolidateTailPlates loop until stable, trying all donor/target pairs
- Fix consolidation grouping to use BaseDrawing reference instead of name
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move all selection state and operations (SelectedParts, SelectedCutOffs, DeselectAll, SelectAll, AlignSelected, RotateSelectedParts, PushSelected, GetPartAt*, GetPartsFromWindow, DeleteSelected) into a new internal SelectionManager class. PlateView retains public forwarding methods and properties to preserve the existing API surface. SelectedCutOff property kept public for WinForms designer compatibility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add entity-based DirectionalDistance overload to SpatialQuery that
uses RayArcDistance/RayCircleDistance instead of tessellating arcs
and circles into line segments
- Add GetOffsetPartEntities, GetPerimeterEntities, GetPartEntities to
PartGeometry for non-tessellated entity extraction
- Update Compactor.Push to use native entities instead of tessellated
lines — 952 circles = 952 entities vs ~47,600 line segments
- Use bounding box containment check to skip cutout entities when no
obstacle is inside the moving part (perimeter-only for common case)
- Obstacles always use perimeter-only entities since cutout edges are
inside the solid and cannot block external parts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The filter only checked ShortestSide against the plate's short dimension,
allowing results where the long side far exceeded the plate length.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three fixes to TryUpgradeOrNewPlate and a new post-pass:
1. Change ShouldUpgrade from < to <= so upgrade wins when costs are
tied (e.g., all zero) — previously 0 < 0 was always false
2. Guard against "upgrades" that shrink a dimension — when options are
sorted by cost and costs are equal, the next option may have a
smaller length despite higher width (e.g., 72x96 after 60x144)
3. Revert plate size when upgrade fill fails — the plate was being
resized before confirming parts fit, leaving it at the wrong size
4. Add TryConsolidateTailPlates post-pass: after all nesting, find the
lowest-utilization new plate and try to absorb its parts into
another plate via upgrade. Eliminates wasteful tail plates (e.g.,
a 48x96 plate at 21% util for 2 parts that fit in upgraded space).
Real nest file: 6 plates → 5 plates, all 43 parts placed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Convert static class to instance with private constructor; shared
parameters (template, plateOptions, salvageRate, minRemnantSize,
progress, token) become fields, eliminating parameter threading
across all private methods (10→3 params on Nest entry point stays
unchanged; private methods drop from 7-9 params to 1-2)
- Extract FillAndPlace helper consolidating the repeated
clone→fill→add-to-plate→deduct-quantity pattern (was duplicated
in 4 call sites)
- Merge FindScrapZones/FindViableRemnants (98% duplicate) into single
FindRemnants(plate, minRemnantSize, scrapOnly) method
- Extract ScoreZone helper and collapse duplicate normal/rotated
orientation checks into single conditional
- Extract CreateNewPlateResult helper for repeated PlateResult
construction + PlateOption lookup pattern
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The consolidation pass was iterating stale remnant lists after placing
parts, causing overlapping placements. Now recalculates remnants from
the plate after each fill operation. Also added plate options to the
real nest file integration test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Small parts no longer create their own plates during the main pass.
Instead they're deferred to the consolidation pass which fills them
into remaining space on existing plates, packing multiple drawing
types together. Drops from 9 plates to 4 on the test nest file.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After the main single-pass placement, leftover items are now packed
together using the engine's multi-item Nest()/PackArea() methods
instead of creating one plate per drawing. First tries packing into
remaining space on existing plates, then creates shared plates for
anything still remaining.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, parts classified as "Large" skipped all existing plates
and always created new ones. This caused one-unique-part-per-plate
behavior since most parts exceed half the plate dimension. Now large
parts search viable remnants on existing plates before creating new
ones, matching the intended part-first behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Small parts must only go into scrap zones (both dims < minRemnantSize)
to preserve viable remnants. The implementer had inverted this, giving
small parts access to all remnants. Also fixed the test to verify
remnant preservation behavior and removed unused FindAllRemnants helper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the main Nest() method that ties together sorting,
classification, and placement across multiple plates. The method
processes items largest-first, placing medium/small parts into
remnant zones on existing plates before creating new ones. Includes
private helpers: TryPlaceOnExistingPlates, PlaceOnNewPlates,
TryUpgradeOrNewPlate, FindAllRemnants, and CloneItem.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds IsScrapRemnant(), FindScrapZones(), and FindViableRemnants() to
MultiPlateNester. A remnant is scrap only when both dimensions fall
below the minimum remnant size threshold (AND logic, not OR).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces PartClass enum and Classify() static method that categorizes
parts as Large (exceeds half work area in either dimension), Medium
(area > 1/9 work area), or Small.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements static MultiPlateNester.SortItems with BoundingBoxArea and Size sort orders, covered by two passing xUnit tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>