mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-03 15:51:42 -05:00
fix(planning): swap the pair a blocked approach doubled back across
When a part could not be reached without crossing parts already cut, the free search always learned "cut the blocked part before them". When the crossed part was the one cut just before the part the tool left from, the tool had gone on past it and come back: the cheap repair is to cut those two the other way round. Without it, N26-23S learned a chain of wrong rules (8 before 4 and 5, 3 before 1, ...) that excluded every valid order and ran the full search out of budget, although swapping parts 5 and 6 gives a valid plan. It now plans ready in 786 expansions. The problem-nest corpus keeps every outcome; three P260417-17 plates stay Ready with more expansions (5341 -> 8937 the largest). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cd51f7cfda
commit
16bd92567c
3 files changed
+43
-4
No files matched your search
@@ -195,7 +195,10 @@ internal static class JointCuttingPlanSearch
|
||||
}, token);
|
||||
}
|
||||
|
||||
// "Cut the blocked part before every part whose cut contour its approach crossed", unless that
|
||||
// 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: "cut those two the other way round", so the tool
|
||||
// leaves from the nearer one (two stacked strips, cut far one first). Otherwise "cut the
|
||||
// blocked part before every part whose cut contour its approach crossed", unless that
|
||||
// would contradict an order already required. When every such rule would (a sheet separator
|
||||
// is cut after everything), the blocked part cannot come earlier, so the tool must arrive
|
||||
// from beyond those parts instead: "cut the part it left from before them". False when
|
||||
@@ -217,6 +220,10 @@ internal static class JointCuttingPlanSearch
|
||||
{
|
||||
if (attempt.Blocked is not int blocked)
|
||||
return [];
|
||||
if (attempt.LastCut?.SourceOrdinal is int last && attempt.CutBeforeLast?.SourceOrdinal is int previous
|
||||
&& attempt.Crossed.Contains(previous) && !prerequisites[previous].Contains(last)
|
||||
&& !Precedes(previous, last, prerequisites))
|
||||
return [(last, previous)];
|
||||
var before = New(blocked);
|
||||
return before.Length != 0 || attempt.LastCut?.SourceOrdinal is not int departed ? before : New(departed);
|
||||
|
||||
@@ -1581,6 +1588,9 @@ internal static class JointCuttingPlanSearch
|
||||
/// <summary>The part cut last before the deepest boundary, or null at the start point.</summary>
|
||||
internal FixedProgramPlacement LastCut => Deepest?.Order is { Length: > 0 } order ? order[^1] : null;
|
||||
|
||||
/// <summary>The part cut just before <see cref="LastCut"/>, or null.</summary>
|
||||
internal FixedProgramPlacement CutBeforeLast => Deepest?.Order is { Length: > 1 } order ? order[^2] : null;
|
||||
|
||||
/// <summary>The ordinal of the part the attempt could not get past, or null.</summary>
|
||||
internal int? Blocked => sequence == null || Deepest == null || Deepest.Order.Length >= sequence.Length ? null
|
||||
: sequence[Deepest.Order.Length];
|
||||
|
||||
@@ -118,6 +118,29 @@ public class ReorderSearchTests
|
||||
Assert.True(result.IndependentlyReplayed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FreeOrder_ApproachCrossesThePartCutJustBefore_SwapsThatPair()
|
||||
{
|
||||
// Two long bars, one above the other, and a small part off to the right below them. The
|
||||
// shortest tour cuts the lower bar, the upper one, then the small part, whose approach
|
||||
// from the upper bar's left side crosses the lower bar. Cutting the upper bar first lets
|
||||
// the tool leave the lower bar below its corner instead; jumping the small part ahead of
|
||||
// the lower bar would detour.
|
||||
var lower = LeftLeadRectangle("lower", 0, 0, 19, 6, departure: new Vector(-0.25, -0.25));
|
||||
var upper = LeftLeadRectangle("upper", 0, 7, 19, 6);
|
||||
var small = LeftLeadRectangle("small", 28, -19, 4, 4);
|
||||
var start = new Vector(9.5, -10);
|
||||
Assert.Contains(Analyze(start, lower, upper, small).Findings, f => f.Kind == PostVerificationKind.RapidCrossing);
|
||||
|
||||
var result = CuttingPlanService.Plan(new CuttingPlanRequest([lower, upper, small], start,
|
||||
confirmedParameters: ExplicitContourTests.Parameters()));
|
||||
|
||||
Assert.True(result.Status == CuttingPlanStatus.Ready, Describe(result));
|
||||
Assert.True(result.IndependentlyReplayed);
|
||||
Assert.Equal(new[] { upper, lower, small }, result.ProposedOrder.Select(p => p.SourcePart));
|
||||
Assert.Empty(Analyze(start, upper, lower, small).Findings);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FreeOrder_LearnedOrderContradicts_FallsBackToTheFullSearch()
|
||||
{
|
||||
@@ -197,7 +220,9 @@ public class ReorderSearchTests
|
||||
return part;
|
||||
}
|
||||
|
||||
private static PostVerificationReport Analyze(params Part[] parts)
|
||||
private static PostVerificationReport Analyze(params Part[] parts) => Analyze(Vector.Zero, parts);
|
||||
|
||||
private static PostVerificationReport Analyze(Vector start, params Part[] parts)
|
||||
{
|
||||
var nest = new Nest();
|
||||
var plate = nest.CreatePlate();
|
||||
@@ -207,7 +232,7 @@ public class ReorderSearchTests
|
||||
Assert.True(copy.RestoreLeadInProgram((Program)source.Program.Clone(), source.LeadInsLocked));
|
||||
plate.Parts.Add(copy);
|
||||
}
|
||||
return PostVerificationAnalyzer.Analyze(nest, Vector.Zero);
|
||||
return PostVerificationAnalyzer.Analyze(nest, start);
|
||||
}
|
||||
|
||||
private static string Describe(CuttingPlanResult r) =>
|
||||
|
||||
@@ -218,7 +218,11 @@ 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 part
|
||||
from the tool position there; parts cut before that point are kept. 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
|
||||
one first. When the part
|
||||
cannot come earlier (a sheet separator is cut after everything), it learns the other
|
||||
way round, "cut the part the approach left from before those", so the tool arrives
|
||||
from beyond them. The first pass
|
||||
|
||||
Reference in new issue
Block a user