RemnantFiller: add placed parts as a single envelope obstacle instead
of individual bounding boxes to prevent the next drawing from filling
into inter-row gaps. Remove the topmost bounding-box part to create a
clean rectangular boundary.
PairsFillStrategy: guard against recursive invocation — remnant fills
within PairFiller create a new engine that runs the full pipeline,
which would invoke PairsFillStrategy again causing deep recursion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make quantity trimming direction-aware: DefaultNestEngine uses TrimAxis
(virtual property on NestEngineBase) so HorizontalRemnantEngine removes
topmost parts instead of rightmost. Rename ShrinkAxis.Height → Length
for consistency with Width/Length naming used throughout the codebase.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Height shrink now uses HorizontalRemnantEngine (minimizes Y-extent)
and width shrink uses VerticalRemnantEngine (minimizes X-extent).
IterativeShrinkFiller accepts an optional widthFillFunc so each
shrink axis can use a different fill engine.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Evaluate pair candidates in parallel batches instead of sequentially
- Add GridDedup to skip duplicate pattern/direction/workArea combos
across PairFiller and StripeFiller strategies
- Replace crude 30% remnant area estimate with L-shaped geometry
calculation using actual grid extents and max utilization
- Move FillStrategyRegistry.SetEnabled to outer evaluation loop
to avoid repeated enable/disable per remnant fill
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace stored property setters (BestPartCount, BestDensity, NestedWidth,
NestedLength, NestedArea) with computed properties that derive values from
BestParts, with a lazy cache invalidated on setter. Add internal
ProgressReport struct to replace the 7-parameter ReportProgress signature.
Update all 13 callsites and AccumulatingProgress. Delete FormatPhaseName
in favor of NestPhase.ShortName() extension.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove redundant early-return branches and unify loop body — Floor(remaining/length2) already returns 0 when remaining < length2, so both branches collapse into one. 14 tests cover all edge cases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PairFiller previously only filled the main grid with pair patterns,
leaving narrow waste strips unfilled. Row/Column strategies filled
their remnants, winning on count despite worse base grids.
Now PairFiller evaluates grid+remnant together for each angle/direction
combination, picking the best total. Uses a two-phase approach: fast
grid evaluation first, then remnant filling only for grids within
striking distance of the current best. Remnant results are cached
via FillResultCache.
Constructor now takes Plate (needed to create remnant engine).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract ConvergeFromAngle to deduplicate ~40 lines shared between
ConvergeStripeAngle and ConvergeStripeAngleShrink. Reduce BuildGrid
from 7 to 4 params and FillRemnant from 6 to 2 by reading context
fields directly. Remove unused angle parameter from FillRemnant.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PairFiller now accepts an optional IFillComparer (defaulting to
DefaultFillComparer) and uses it in EvaluateCandidates and
EvaluateCandidate/FillPattern instead of raw FillScore comparisons.
PairsFillStrategy passes context.Policy?.Comparer through.
StripeFiller derives _comparer from FillContext.Policy in its
constructor and uses it in Fill() instead of FillScore comparisons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements two IFillComparer strategies that preserve axis-aligned remnants:
VerticalRemnantComparer minimizes X-extent, HorizontalRemnantComparer minimizes
Y-extent, both using a count > extent > density tiebreak chain. Includes 12
unit tests covering all tiebreak levels and null-guard cases.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts the fill result scoring contract into IFillComparer with a DefaultFillComparer implementation that preserves the existing count-then-density lexicographic ranking via FillScore.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Convergence loop now uses FillLinear internally to measure actual
waste with geometry-aware spacing instead of bounding-box arithmetic
- Each candidate pair is tried in both Row and Column orientations to
find the shortest perpendicular dimension (more complete stripes)
- CompleteStripesOnly flag drops partial stripes; remnant strip is
filled by a full engine run (injected via CreateRemnantEngine)
- ConvergeStripeAngleShrink tries N+1 narrower pairs as alternative
- FillResultCache avoids redundant engine runs on same-sized remnants
- CLAUDE.md: note to not commit specs/plans
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The convergence loop now ensures the pair starts with its short side
parallel to the primary axis, maximizing the number of pairs that fit.
Also adds ConvergeStripeAngleShrink to try N+1 narrower pairs, and
evaluates both expand and shrink results to pick the better grid.
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>
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>
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>
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>
Introduces IterativeShrinkFiller.Fill, which composes RemnantFiller and
ShrinkFiller by wrapping the caller's fill function in a closure that tries
both ShrinkAxis.Height and ShrinkAxis.Width and picks the better FillScore.
Adds IterativeShrinkResult (Parts + Leftovers). Covers null/empty inputs and
single-item placement with three passing xUnit tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Kept using OpenNest.Api in Timing.cs and EditNestForm.cs alongside
remote's reorganized usings and namespace changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract magic numbers into named constants (MaxTopCandidates,
EarlyExitMinTried, etc.), extract candidate evaluation into
EvaluateCandidate method, and expose BestFits property so
PairsFillStrategy can reuse without redundant BestFitCache call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The pattern bounding box already computes max(upper) - min(lower), so the
manual loop was redundant. Extract the N×N pair distance loop into a static
FindMaxPairDistance helper. Drop pre-cached edge arrays since GetEdges()
returns stored references with zero allocation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Also fix missing using for FillHelpers in FillLinear and FillExtents,
and update callers (CompactorTests, PatternTileForm) for the new
Vector parameter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move known-good pruning check before sweep/ML to avoid wasted work,
extract ContainsAngle, NeedsSweep, AddSweepAngles, ApplyMlPrediction,
and BuildPrunedList so Build reads as a clear pipeline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FillExtents vertical copy distance was not clamped, allowing rows to be
placed overlapping each other when slide calculations returned large
values. Clamp to pairHeight + partSpacing minimum, matching FillLinear.
Also add FillStrategyRegistry.SetEnabled() to restrict which strategies
run — useful for isolating individual strategies during troubleshooting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move fill algorithms to OpenNest.Engine.Fill namespace:
FillLinear, FillExtents, PairFiller, ShrinkFiller, Compactor,
RemnantFiller, RemnantFinder, FillScore, Pattern, PatternTiler,
PartBoundary, RotationAnalysis, AngleCandidateBuilder, and
AccumulatingProgress.
Move strategy layer to OpenNest.Engine.Strategies namespace:
IFillStrategy, FillContext, FillStrategyRegistry, FillHelpers,
and all built-in strategy implementations.
Add using directives to all consuming files across Engine, UI,
MCP, and Tests projects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>