diff --git a/OpenNest.Engine/CuttingPlanning/JointCuttingPlanSearch.cs b/OpenNest.Engine/CuttingPlanning/JointCuttingPlanSearch.cs index 5c66e8c9..336b0f04 100644 --- a/OpenNest.Engine/CuttingPlanning/JointCuttingPlanSearch.cs +++ b/OpenNest.Engine/CuttingPlanning/JointCuttingPlanSearch.cs @@ -155,9 +155,14 @@ internal static class JointCuttingPlanSearch } // Back up to just before the earliest part the blocked approach crossed, keep the // parts cut before it, and re-plan the rest from where the tool is at that point. + // The last kept part was planned facing the part that followed it in the old + // order; when another part follows it now, it is planned again, in the same + // place, facing that one. var back = attempt.Crossed.Min(part => Array.IndexOf(sequence, part)); - resume = attempt.BoundaryAt(back); - sequence = [.. sequence.Take(back), .. CuttingNestedOrder.Plan(sequence[back..], resume.Position, nesting)]; + var rest = CuttingNestedOrder.Plan(sequence[back..], attempt.BoundaryAt(back).Position, nesting); + var replan = back > 0 && rest.Length != 0 && rest[0] != sequence[back] ? back - 1 : back; + resume = attempt.BoundaryAt(replan); + sequence = [.. sequence.Take(back), .. rest]; } } catch (BudgetExceededException) diff --git a/OpenNest.Tests/CuttingPlanning/CuttingDependencyTests.cs b/OpenNest.Tests/CuttingPlanning/CuttingDependencyTests.cs index fbb67f0d..c08c4670 100644 --- a/OpenNest.Tests/CuttingPlanning/CuttingDependencyTests.cs +++ b/OpenNest.Tests/CuttingPlanning/CuttingDependencyTests.cs @@ -147,8 +147,9 @@ public class CuttingDependencyTests Assert.True(result.Status == CuttingPlanStatus.Ready, Describe(result)); var order = result.ProposedOrder.Select(o => o.SourcePart).ToList(); Assert.True(order.IndexOf(cut) < order.IndexOf(a), string.Join(",", result.ProposedOrder.Select(o => o.SourceOrdinal))); - // Learning at once took 22 expansions; retrying every entry of B first took 60. - Assert.True(result.Expansions < 40, Describe(result)); + // Learning at once takes 40 expansions (22, then B planned again to face the cutoff that + // now follows it); retrying every entry of B first took 60. + Assert.True(result.Expansions < 50, Describe(result)); } // 100 x 100 sheet: squares at X 1-11 and 60-70; a cutoff at X 5 is trimmed around the left diff --git a/OpenNest.Tests/CuttingPlanning/PlanningCharacterizationTests.cs b/OpenNest.Tests/CuttingPlanning/PlanningCharacterizationTests.cs index 4d4ed3b7..12f9a51a 100644 --- a/OpenNest.Tests/CuttingPlanning/PlanningCharacterizationTests.cs +++ b/OpenNest.Tests/CuttingPlanning/PlanningCharacterizationTests.cs @@ -45,7 +45,7 @@ public class PlanningCharacterizationTests [InlineData("holes-grid-kept-order", "07B70B43549E95C65407B24944DA8F7262B784CFC4ABF86241C10DD8272B1780")] [InlineData("rounded-circle-leads", "73677429BFC950669D238E834CAE48F54A483759BE4E0BB1437D096F0AF7F3DA")] [InlineData("tabbed-perimeters", "6858C09A02D73E4531D0D59B6CD1CFFFA2C9C8BA5F48CF4943D9C4AEC17195BA")] - [InlineData("locked-among-regenerated", "D7914712F87513E38AA93776886AAFD5025EC89A0D4D7314F900A1163F830019")] + [InlineData("locked-among-regenerated", "42AFCF0DE942F501D8542C091C9D4407EAE41DE9442F75300348DCDB4FF0C733")] public void FocusedFixture_PlansExactlyAsBefore(string name, string expected) { var nest = new Nest(); diff --git a/docs/cutting-planner.md b/docs/cutting-planner.md index c2455250..3ba2b5fb 100644 --- a/docs/cutting-planner.md +++ b/docs/cutting-planner.md @@ -218,7 +218,9 @@ from the start point. Its 2-opt and Or-opt passes would grow with the square of part count. Replans after a learned rule use the plain tour. If a part on that order cannot be reached without crossing parts already cut, the search learns "cut this part before those", backs up to just before the earliest of them and re-plans the rest -from the tool position there; parts cut before that point are kept. When the approach +from the tool position there; parts cut before that point are kept. The last of them was +planned facing the part that followed it; when another part follows it now, it is +planned again, in the same place, facing that one. When the approach crossed the part cut just before the one it left from, the tool went on past that part and came back across it, so the search learns "cut those two the other way round" instead: the tool then leaves from the nearer one, as with two stacked strips cut far