4.4 KiB
Coarse-then-Refine Sweep in RotationSlideStrategy
Problem
RotationSlideStrategy.GenerateCandidatesForAxis sweeps the full perpendicular range at stepSize (default 0.25"), calling Helper.DirectionalDistance at every step. Profiling shows DirectionalDistance accounts for 62% of CPU during best-fit computation. For parts with large bounding boxes, this produces hundreds of steps per direction, making the Pairs phase take 2.5+ minutes.
Solution
Replace the single fine sweep with a two-phase coarse-then-refine sweep inside GenerateCandidatesForAxis. The coarse pass identifies promising offsets using slide distance as a cheap quality signal, then the fine pass generates candidates only in narrow windows around those promising regions.
Design
Modified method: GenerateCandidatesForAxis
Located in OpenNest.Engine/BestFit/RotationSlideStrategy.cs. The public GenerateCandidates method and all other code remain unchanged.
Current flow:
- Sweep
alignedStarttoperpMaxatstepSize - At each offset: clone part2, position, compute offset lines, call
DirectionalDistance, buildPairCandidate
New flow:
- Compute
coarseStep = stepSize * 4 - Coarse sweep from
alignedStart(aligned tocoarseStep) toperpMaxatcoarseStep:- Clone part2, position at offset, compute offset lines
- Call
DirectionalDistanceto getslideDist - If
slideDist >= double.MaxValue || slideDist < 0, skip - Store
(offset, slideDist)in a list
- Select refinement regions:
- Sort coarse hits by
slideDistascending (tightest fit first) - Take up to 5 hits, skipping any whose offset is within
coarseStepof an already-selected hit (deduplicates overlapping windows)
- Sort coarse hits by
- Fine sweep each selected region from
offset - coarseSteptooffset + coarseStepatstepSize:- Clamp to
[alignedStart, perpMax]so we don't sweep outside the original range - Align the region start to a multiple of
stepSize(ensures offset=0 is always tested when present in the region) - At each fine offset: clone part2, position, compute offset lines, call
DirectionalDistance, buildPairCandidate(same logic as current code) - This produces ~8 fine steps per region (2 × coarseStep / stepSize = 2 × 1.0 / 0.25 = 8)
- Clamp to
Coarse alignment: alignedStart for the coarse pass is Math.Ceiling(perpMin / coarseStep) * coarseStep, ensuring offset=0 is always included (since coarseStep is a multiple of stepSize).
Constants: The coarse multiplier (4) and max refinement regions (5) are local constants in the method, not configurable. These values balance coverage vs speed for the typical part sizes in this application.
Performance
- Current: ~160
DirectionalDistancecalls per direction (20" range / 0.25 step) - Coarse pass: ~40 calls (20" / 1.0 step) — 75% reduction
- Refinement: ~40 calls total (5 regions × 8 steps)
- Net: ~80 calls vs ~160 = ~50% reduction per direction, with the coarse pass being much cheaper since it only stores tuples rather than building full candidates
- Total across 4 directions × N angles: proportional reduction throughout
Files Modified
| File | Change |
|---|---|
OpenNest.Engine/BestFit/RotationSlideStrategy.cs |
Replace single sweep in GenerateCandidatesForAxis with coarse-then-refine two-phase sweep |
What Doesn't Change
RotationSlideStrategy.GenerateCandidates— unchanged, callsGenerateCandidatesForAxisas beforeBestFitFinder— unchanged, callsstrategy.GenerateCandidatesas beforeBestFitCache— unchangedPairEvaluator/IPairEvaluator— unchangedPairCandidate,BestFitResult,BestFitFilter— unchangedHelper.DirectionalDistance,Helper.GetOffsetPartLines— reused as-isNestEngine.FillWithPairs— unchanged caller
Edge Cases
- Part smaller than coarseStep: The coarse sweep still works — it just produces fewer hits, and refinement still covers the full range around those hits
- Refinement regions overlap: Deduplication in region selection prevents redundant fine sweeps
- No coarse hits (all offsets produce invalid slides): No refinement regions selected, method returns empty list — same as current behavior when no valid candidates exist
- Fine sweep region extends past bounds: Clamped to
[alignedStart, perpMax] - stepSize not evenly divisible into coarseStep:
coarseStepis alwaysstepSize * 4, so it's always a clean multiple