Display current plate utilization percentage in the status bar,
updating live when parts are added or removed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire IDistanceComputer into RotationSlideStrategy, replacing inline
CPU/GPU branching. BestFitFinder constructs the appropriate implementation.
Replace PushDirection enum with direction vectors in BuildOffsets.
Rename IBestFitStrategy.Type and PairCandidate.StrategyType to StrategyIndex
for clarity (JSON field name unchanged for backward compatibility).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract distance computation from RotationSlideStrategy into a pluggable
IDistanceComputer interface. CpuDistanceComputer adds leading-face vertex
culling (~50% fewer rays per direction) with early exit on overlap.
GpuDistanceComputer wraps ISlideComputer with Line-to-flat-array conversion.
SlideOffset struct uses direction vectors (DirX/DirY) instead of PushDirection.
SpatialQuery.RayEdgeDistance(dirX,dirY) made public for CPU path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs fixed in NfpSlideStrategy pipeline:
1. NoFitPolygon.Reflect() incorrectly reversed vertex order. Point
reflection (negating both axes) is a 180° rotation that preserves
winding — the Reverse() call was converting CCW to CW, producing
self-intersecting bowtie NFPs.
2. PolygonHelper inflation used OffsetSide.Left which is inward for
CCW perimeters. Changed to OffsetSide.Right for outward inflation
so NFP boundary positions give properly-spaced part placements.
3. Removed incorrect correction vector — same-drawing pairs have
identical polygon-to-part offsets that cancel out in the NFP
displacement.
Also refactored NfpSlideStrategy to be immutable (removed mutable
cache fields, single constructor with required data, added Create
factory method). BestFitFinder remains on RotationSlideStrategy
as default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NFP strategy has coordinate correction issues causing overlaps.
The slide-based approach is fast and accurate — keeping it as default.
NfpSlideStrategy and PolygonHelper remain in the codebase for future use.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Calling ConvexMinkowskiSum directly with manual reflection produced
wrong winding/reference-point handling, causing all pairs to overlap.
Route through Compute which handles reflection correctly. Hull inputs
keep it fast — few triangles means trivial Clipper union.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConvexMinkowskiSum is O(n+m) with no boolean geometry ops.
The concave Minkowski path was doing triangulation + pairwise
sums + Clipper2 Union, which hung at 100% CPU for complex parts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace RotationSlideStrategy with NfpSlideStrategy in BuildStrategies,
and add integration tests covering the end-to-end FindBestFits pipeline.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Creates PolygonHelper.cs in OpenNest.Engine.BestFit with ExtractPerimeterPolygon
(returning PolygonExtractionResult with polygon + correction vector) and RotatePolygon.
AutoNester.ExtractPerimeterPolygon and RotatePolygon become thin delegates.
Adds MakeSquareDrawing/MakeLShapeDrawing to TestHelpers and 6 PolygonHelperTests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Size(width, length) maps Width to vertical and Length to horizontal in
PlateView, but BoundingWidth (the longer dimension) was being passed as
Width (vertical) instead of Length (horizontal), causing the bounding
box to appear portrait instead of landscape.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevent InvalidOperationException when the timer fires before or
after the control handle is available.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In strip mode, build candidate list entirely from pairs whose
ShortestSide fits the narrow work area dimension, sorted by
estimated tile count. Previously, the top-50 utilization cut
ran first, excluding good strip candidates like #183.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove NeedsSweep that triggered a 5-degree sweep (36 angles) when
the work area was narrower than the part. Position matters more than
angle for narrow areas, and the base angles (bestRotation + 90deg)
cover the useful cases. ForceFullSweep still works for training.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Normalize pair bounding box to landscape (width >= height) in
PairEvaluator for consistent display and filtering. Fix
BestFitViewerForm where BoundingWidth/BoundingHeight were passed
in the wrong order to the plate Size constructor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Compactor.Settle and AutoNester.Optimize post-passes to
NestEngineBase.Nest, StripNestEngine, and PlateView.FillWithProgress
so all fill paths benefit from geometry-aware compaction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Optimize method that re-places parts using NFP-based BLF, keeping
the result only if it improves density without losing parts. Fix
perimeter inflation to use correct offset side. Add NfpNestEngine
that wraps AutoNester for the registry. Register NFP engine.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add Settle method that repeatedly pushes parts left then down until
total movement falls below a threshold. Replaces manual single-pass
push calls for more consistent gap closure.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactor BLF to compute NFP paths as Clipper PathsD with offsets
instead of translating full polygons. Add spatial pruning to skip
NFPs that don't intersect the IFP bounds. Clamp placement points
to IFP bounds to correct Clipper2 floating-point drift. Add
progress reporting to simulated annealing. Add debug logging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move PlacedPart to its own file. Replace tuple-based sequences with
SequenceEntry struct for clarity. Add IProgress parameter to
INestOptimizer. Add IFP caching to NfpCache to avoid recomputing
inner fit polygons for the same drawing/rotation/workArea.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add immutable Translate methods to Box. Make NoFitPolygon
ToClipperPath/FromClipperPath public with optional offset parameter.
Refactor InnerFitPolygon.ComputeFeasibleRegion to accept PathsD
directly, letting Clipper2 handle implicit union. Add UpdateBounds
calls after polygon construction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add SelectionChanged event to PlateView and display the selected part's
location and size in a new status bar label. Shows combined bounding box
when multiple parts are selected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three tasks: add TrimToCount with tests, replace shrink loop, replace
Take(N) in DefaultNestEngine.Fill.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Clarify sort direction (ascending, keep nearest to origin), document
parameter changes, MeasureDimension behavior, and behavioral trade-off.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace expensive ShrinkFiller re-fill loop with axis-aware edge-sorted
trim. Also replaces blind Take(N) in DefaultNestEngine.Fill.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add drawing dropdown to switch between drawings without reopening the
form. Change color scheme to light backgrounds with blue/red part fills
and auto-detect text color. Fix swapped bounding box width/length. Run
best-fit computation on a background thread so the UI stays responsive
during long calculations, with cancellation on drawing switch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add third row (5x3 grid, 15 items/page), remove 50-result cap so all
candidates are pageable, start maximized, replace page label with
editable textbox for direct page entry, center nav controls, and
eliminate flicker on page change via DoubleBuffered + WM_SETREDRAW.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CloneAtOffset shares the Program instance for tiling performance,
but rotating a part on the plate mutated the shared Program, causing
all parts from the same tile template to rotate together.
Added ownsProgram flag with EnsureOwnedProgram() that clones the
Program before first mutation, preserving tiling performance while
making user rotations independent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The documented entries (info.json, drawing-info.json, plate-info.json,
plate-NNN) were from the old v1 format. Updated to reflect the actual v2
structure: nest.json, programs/program-N, and bestfits/bestfit-N.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PairFiller now returns PairFillResult (Parts + BestFits) instead of
using a mutable BestFits property. Extracted EvaluateCandidates,
TryReduceWorkArea, and BuildTilingAngles for clarity. Simplified the
candidate loop by leveraging FillScore comparison semantics.
Removed FillRemainingStrip and all its helpers (FindPlacedEdge,
BuildRemainingStrip, BuildRotationSet, FindBestFill, TryFewerRows,
RemainderPatterns) from FillLinear — these were a major bottleneck in
strip nesting, running expensive fills on undersized remnant strips.
ShrinkFiller + RemnantFiller already handle space optimization, making
the remainder strip fill redundant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
StripNestEngine was passing progress directly to DefaultNestEngine.Fill
inside the ShrinkFiller loop, causing every per-angle/per-strategy report
to update the UI with overlapping layouts in the same work area.
Now inner fills are silent (null progress) and ShrinkFiller reports its
own progress when the best layout improves. IterativeShrinkFiller tracks
placed parts across items and includes them in reports. The trial box is
reported before the fill starts so the work area border updates immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Draw only one set of preview parts at a time — active (current strategy)
takes precedence over stationary (overall best). Also clears active
parts when setting new stationary parts to prevent stale previews.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the first pair candidate places more parts than needed (e.g., 17
when target is 10), sort by BoundingBox.Top, trim from the top until
exactly targetCount remain, and use that Top as the new work area
height. All subsequent candidates fill this smaller area, dramatically
reducing fill time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a target count is known, ShrinkFiller now uses FillBestFit (fast
rectangle packing) to estimate how many parts fit on the full area,
then scales the shrink axis proportionally to avoid an expensive
full-area fill. Falls back to full box if estimate is too aggressive.
Also shrinks to targetCount (not full count) to produce tighter boxes
when fewer parts are needed than the area can hold.
IterativeShrinkFiller passes NestItem.Quantity as the target count.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PairEvaluator already computes OptimalRotation via RotatingCalipers on
the pair's convex hull, but PairFiller.EvaluateCandidate only passed
hull edge angles to FillPattern. Now includes the optimal rotation
angle (and +90°) so tiling can use the mathematically tightest fit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both types were only used internally by the old StripNestEngine.Nest
strip-orientation logic, which has been replaced by IterativeShrinkFiller.
No references remain outside of these files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the old orientation-based strip nesting (TryOrientation,
SelectStripItemIndex, EstimateStripDimension, ShrinkFill helpers) with
a call to IterativeShrinkFiller.Fill for multi-quantity items, plus a
RemnantFinder-based PackArea pass for singles and leftovers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>