mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-02 13:41:37 -05:00
fix(planning): route a cut-off from its start to its far end
A cut-off always runs away from its fixed edge: the origin side, the only edge guaranteed to be a real sheet edge. The part order is proposed as a traveling-salesman path over one point per placement, and a cut-off's point was the middle of its line. The route did not know the tool must arrive at the line's start and leaves at its far end, so the parts before a sheet-splitting line finished level with its middle, and the rapid down to its start crossed parts already cut. Each placement now has an entry and an exit point; they differ only for cut-offs (start and departure). Nearest neighbor and Or-opt go on from each part's exit, and 2-opt costs a reversed run along its own directed legs; with no cut-off on the plate the exits are not passed and the symmetric route is unchanged. The part cut before a cut-off faces its start. Cut-off direction is not chosen here: the order reads each cut-off's start and end from its program as given, and the joint search may still reverse a head-locked far-edge segment.
This commit is contained in:
1 parent
277719a1d6
commit
4e92d0e8f3
6 files changed
+196
-27
No files matched your search
@@ -20,9 +20,11 @@ internal static class CuttingNestedOrder
|
||||
/// <param name="Prerequisites">Ordinals that must come before each part, learned rules included.</param>
|
||||
/// <param name="Inside">Per part ordinal, the parts nested inside its cutouts at any depth.</param>
|
||||
/// <param name="HostEntry">The point on a host's contours nearest a tool position.</param>
|
||||
/// <param name="Exits">Optional point each part is left from (a cut-off's far end); null when
|
||||
/// every part is left from its center.</param>
|
||||
internal sealed record Context(IReadOnlyList<Vector> Centers,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> Prerequisites, IReadOnlyList<IReadOnlyCollection<int>> Inside,
|
||||
Func<int, Vector, Vector> HostEntry, CancellationToken Token);
|
||||
Func<int, Vector, Vector> HostEntry, CancellationToken Token, IReadOnlyList<Vector> Exits = null);
|
||||
|
||||
/// <param name="parts">The part ordinals to order; prerequisites outside this set count as done.</param>
|
||||
/// <param name="start">The modeled tool position before the first of these parts.</param>
|
||||
@@ -63,13 +65,14 @@ internal static class CuttingNestedOrder
|
||||
int[] rootOrder;
|
||||
try
|
||||
{
|
||||
rootOrder = CuttingPartOrder.Plan(roots, context.Centers, start, hostsLast, context.Token, endpoint);
|
||||
rootOrder = CuttingPartOrder.Plan(roots, context.Centers, start, hostsLast, context.Token, endpoint, context.Exits);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
try
|
||||
{
|
||||
rootOrder = CuttingPartOrder.Plan(roots, context.Centers, start, groupRules, context.Token, endpoint);
|
||||
rootOrder = CuttingPartOrder.Plan(roots, context.Centers, start, groupRules, context.Token, endpoint,
|
||||
context.Exits);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
@@ -84,7 +87,7 @@ internal static class CuttingNestedOrder
|
||||
if (inner.Length != 0)
|
||||
order.AddRange(Plan(inner, position, context, context.HostEntry(root, position)));
|
||||
order.Add(root);
|
||||
position = context.Centers[root];
|
||||
position = (context.Exits ?? context.Centers)[root];
|
||||
}
|
||||
var placed = new HashSet<int>();
|
||||
foreach (var part in order)
|
||||
@@ -95,6 +98,7 @@ internal static class CuttingNestedOrder
|
||||
}
|
||||
return order.ToArray();
|
||||
|
||||
int[] Flat() => CuttingPartOrder.Plan(parts, context.Centers, start, context.Prerequisites, context.Token, endpoint);
|
||||
int[] Flat() => CuttingPartOrder.Plan(parts, context.Centers, start, context.Prerequisites, context.Token, endpoint,
|
||||
context.Exits);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,9 @@ namespace OpenNest.Engine.CuttingPlanning;
|
||||
/// Proposes a whole-part visiting order as an open traveling-salesman path from the start point
|
||||
/// over part centers: nearest neighbor, then 2-opt and Or-opt improvement, never placing a part
|
||||
/// before one of its prerequisites. It is only a proposal; the forward search still checks every
|
||||
/// rapid and lead along it.
|
||||
/// rapid and lead along it. A part may be left from somewhere other than where it is reached (a
|
||||
/// cut-off is entered at its start and left at its far end): each leg then runs from one part's
|
||||
/// exit to the next part's center, and a reversed run is costed along its own directed legs.
|
||||
/// </summary>
|
||||
internal static class CuttingPartOrder
|
||||
{
|
||||
@@ -24,8 +26,11 @@ internal static class CuttingPartOrder
|
||||
/// <param name="start">The modeled tool position before the first of these parts.</param>
|
||||
/// <param name="prerequisites">Ordinals that must come before each part; must be acyclic.</param>
|
||||
/// <param name="endpoint">Optional fixed final position after the last of these parts.</param>
|
||||
/// <param name="exits">Optional point each part is left from, per part ordinal; null when every
|
||||
/// part is left from its center.</param>
|
||||
internal static int[] Plan(IReadOnlyList<int> parts, IReadOnlyList<Vector> centers, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, CancellationToken token, Vector? endpoint = null)
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, CancellationToken token, Vector? endpoint = null,
|
||||
IReadOnlyList<Vector> exits = null)
|
||||
{
|
||||
var local = new Dictionary<int, int>();
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
@@ -34,7 +39,8 @@ internal static class CuttingPartOrder
|
||||
var localPrerequisites = parts
|
||||
.Select(p => (IReadOnlyCollection<int>)prerequisites[p].Where(local.ContainsKey).Select(q => local[q]).ToArray())
|
||||
.ToArray();
|
||||
return Plan(localCenters, start, localPrerequisites, token, endpoint).Select(i => parts[i]).ToArray();
|
||||
var localExits = exits == null ? null : parts.Select(p => exits[p]).ToArray();
|
||||
return Plan(localCenters, start, localPrerequisites, token, endpoint, localExits).Select(i => parts[i]).ToArray();
|
||||
}
|
||||
|
||||
/// <param name="centers">One representative point per part ordinal.</param>
|
||||
@@ -45,24 +51,29 @@ internal static class CuttingPartOrder
|
||||
/// terminal edge). The terminal edge joins EVERY improvement delta, not just final
|
||||
/// scoring. Null keeps the previous whole-part open-route semantics exactly.
|
||||
/// </param>
|
||||
/// <param name="exits">
|
||||
/// Optional point each part is left from; null (every part left from its center) keeps the
|
||||
/// symmetric route exactly.
|
||||
/// </param>
|
||||
internal static int[] Plan(IReadOnlyList<Vector> centers, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, CancellationToken token, Vector? endpoint = null)
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, CancellationToken token, Vector? endpoint = null,
|
||||
IReadOnlyList<Vector> exits = null)
|
||||
{
|
||||
var count = centers.Count;
|
||||
var order = NearestNeighbor(centers, start, prerequisites, token);
|
||||
var order = NearestNeighbor(centers, exits ?? centers, start, prerequisites, token);
|
||||
var position = new int[count];
|
||||
for (var pass = 0; pass < MaxPasses; pass++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var improved = TwoOpt(order, centers, start, prerequisites, position, token, endpoint);
|
||||
improved |= OrOpt(order, centers, start, prerequisites, position, token, endpoint);
|
||||
var improved = TwoOpt(order, centers, exits, start, prerequisites, position, token, endpoint);
|
||||
improved |= OrOpt(order, centers, exits ?? centers, start, prerequisites, position, token, endpoint);
|
||||
if (!improved)
|
||||
break;
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
private static int[] NearestNeighbor(IReadOnlyList<Vector> centers, Vector start,
|
||||
private static int[] NearestNeighbor(IReadOnlyList<Vector> centers, IReadOnlyList<Vector> exits, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, CancellationToken token)
|
||||
{
|
||||
var count = centers.Count;
|
||||
@@ -89,39 +100,56 @@ internal static class CuttingPartOrder
|
||||
throw new InvalidOperationException("Part prerequisites form a cycle.");
|
||||
placed[best] = true;
|
||||
order[step] = best;
|
||||
current = centers[best];
|
||||
current = exits[best];
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
// Reverses order[i..j] when that shortens the open path and keeps every prerequisite earlier.
|
||||
private static bool TwoOpt(int[] order, IReadOnlyList<Vector> centers, Vector start,
|
||||
// With exits, a leg runs from one part's exit to the next part's center, so the reversed run's
|
||||
// own legs change length too; without them they cancel and are left out, exactly as before.
|
||||
private static bool TwoOpt(int[] order, IReadOnlyList<Vector> centers, IReadOnlyList<Vector> exits, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, int[] position, CancellationToken token,
|
||||
Vector? endpoint = null)
|
||||
{
|
||||
var improved = false;
|
||||
var count = order.Length;
|
||||
var leave = exits ?? centers;
|
||||
// The terminal edge belongs to every delta: reversing the route's tail swaps which
|
||||
// endpoint-side center faces the fixed final position.
|
||||
double Tail(Vector from) => endpoint is { } e ? from.DistanceTo(e) : 0;
|
||||
for (var i = 0; i < count - 1; i++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
// The run's internal legs, forward and reversed, grown one part at a time.
|
||||
double forward = 0, reversed = 0;
|
||||
for (var j = i + 1; j < count; j++)
|
||||
{
|
||||
var before = Point(i - 1).DistanceTo(centers[order[i]])
|
||||
+ (j + 1 < count ? centers[order[j]].DistanceTo(centers[order[j + 1]]) : Tail(centers[order[j]]));
|
||||
var after = Point(i - 1).DistanceTo(centers[order[j]])
|
||||
+ (j + 1 < count ? centers[order[i]].DistanceTo(centers[order[j + 1]]) : Tail(centers[order[i]]));
|
||||
if (exits != null)
|
||||
{
|
||||
forward += leave[order[j - 1]].DistanceTo(centers[order[j]]);
|
||||
reversed += leave[order[j]].DistanceTo(centers[order[j - 1]]);
|
||||
}
|
||||
var before = Point(i - 1).DistanceTo(centers[order[i]]) + forward
|
||||
+ (j + 1 < count ? leave[order[j]].DistanceTo(centers[order[j + 1]]) : Tail(leave[order[j]]));
|
||||
var after = Point(i - 1).DistanceTo(centers[order[j]]) + reversed
|
||||
+ (j + 1 < count ? leave[order[i]].DistanceTo(centers[order[j + 1]]) : Tail(leave[order[i]]));
|
||||
if (after >= before - Epsilon || !CanReverse(order, i, j, prerequisites, position))
|
||||
continue;
|
||||
Array.Reverse(order, i, j - i + 1);
|
||||
improved = true;
|
||||
// The run is reversed now; its legs are re-measured from scratch.
|
||||
forward = reversed = 0;
|
||||
for (var k = i + 1; k <= j && exits != null; k++)
|
||||
{
|
||||
forward += leave[order[k - 1]].DistanceTo(centers[order[k]]);
|
||||
reversed += leave[order[k]].DistanceTo(centers[order[k - 1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return improved;
|
||||
|
||||
Vector Point(int index) => index < 0 ? start : centers[order[index]];
|
||||
Vector Point(int index) => index < 0 ? start : leave[order[index]];
|
||||
}
|
||||
|
||||
// A reversal breaks a prerequisite only when both parts lie inside the reversed span.
|
||||
@@ -138,7 +166,7 @@ internal static class CuttingPartOrder
|
||||
}
|
||||
|
||||
// Moves a run of one to three parts to a later or earlier gap when that shortens the path.
|
||||
private static bool OrOpt(int[] order, IReadOnlyList<Vector> centers, Vector start,
|
||||
private static bool OrOpt(int[] order, IReadOnlyList<Vector> centers, IReadOnlyList<Vector> exits, Vector start,
|
||||
IReadOnlyList<IReadOnlyCollection<int>> prerequisites, int[] position, CancellationToken token,
|
||||
Vector? endpoint = null)
|
||||
{
|
||||
@@ -172,7 +200,7 @@ internal static class CuttingPartOrder
|
||||
// end — the fixed endpoint when one was supplied, so the terminal edge is in every delta).
|
||||
double Gap(int a, int b)
|
||||
{
|
||||
var from = a < 0 ? start : centers[order[a]];
|
||||
var from = a < 0 ? start : exits[order[a]];
|
||||
if (b >= count)
|
||||
return endpoint is { } e ? from.DistanceTo(e) : 0;
|
||||
if (b < 0)
|
||||
|
||||
@@ -199,6 +199,7 @@ internal static class JointCuttingPlanSearch
|
||||
private static CuttingNestedOrder.Context NestedOrder(CuttingPlanSnapshot snapshot, Vector[] centers,
|
||||
HashSet<int>[] prerequisites, CancellationToken token)
|
||||
{
|
||||
var exits = snapshot.Placements.Any(p => p.IsCutOff) ? snapshot.Placements.Select(Exit).ToArray() : null;
|
||||
var inside = snapshot.Placements.Select((placement, host) => placement.IsCutOff ? []
|
||||
: snapshot.Dependencies.PrerequisitesOf(host).Where(p => !snapshot.Placements[p].IsCutOff).ToArray()).ToArray();
|
||||
var contours = new Dictionary<int, Entity[]>();
|
||||
@@ -210,7 +211,7 @@ internal static class JointCuttingPlanSearch
|
||||
.Select(m => m.Curve.ToEntity()).ToArray();
|
||||
return entities.Length == 0 ? centers[host]
|
||||
: entities.Select(e => e.ClosestPointTo(position)).MinBy(point => point.DistanceTo(position));
|
||||
}, token);
|
||||
}, token, exits);
|
||||
}
|
||||
|
||||
// When the approach crossed the part cut just before the one it left from, the tool went on
|
||||
@@ -268,9 +269,17 @@ internal static class JointCuttingPlanSearch
|
||||
return false;
|
||||
}
|
||||
|
||||
// The center of the part's placed cut material, for ordering only.
|
||||
// Where the tool reaches a part, for ordering only: the center of its placed cut material,
|
||||
// or a cut-off's start. A cut-off runs from a fixed edge, so it is entered at its start and
|
||||
// left at its far end (Exit); its middle is neither.
|
||||
private static Vector Center(FixedProgramPlacement placement) =>
|
||||
CutExtent(placement)?.Center ?? placement.Execution.DeparturePoint;
|
||||
placement.IsCutOff && placement.Execution.Motions.FirstOrDefault(m => !m.Rapid) is { Start: { } start }
|
||||
? start
|
||||
: CutExtent(placement)?.Center ?? placement.Execution.DeparturePoint;
|
||||
|
||||
// Where the tool leaves a part, for ordering only: a cut-off's far end, otherwise its center.
|
||||
private static Vector Exit(FixedProgramPlacement placement) =>
|
||||
placement.IsCutOff ? placement.Execution.DeparturePoint : Center(placement);
|
||||
|
||||
// The extent of the part's placed cut material, or null when it has none.
|
||||
private static Box CutExtent(FixedProgramPlacement placement)
|
||||
@@ -444,7 +453,7 @@ internal static class JointCuttingPlanSearch
|
||||
for (var i = node.Order.Length + 1; i < sequence.Length; i++)
|
||||
if (!finished.Contains(sequence[i]) && !IsSegment(snapshot.Placements[sequence[i]]))
|
||||
return snapshot.Placements[sequence[i]];
|
||||
var from = Center(source);
|
||||
var from = Exit(source);
|
||||
FixedProgramPlacement best = null;
|
||||
var bestDistance = double.PositiveInfinity;
|
||||
foreach (var candidate in snapshot.Placements
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.CNC.CuttingPlanning;
|
||||
using OpenNest.Engine.CuttingPlanning;
|
||||
using OpenNest.Engine.Sequencing;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Tests.CuttingPlanning;
|
||||
|
||||
/// <summary>
|
||||
/// A cut-off is entered at its start, on the origin-side edge, and left at its far end. The route
|
||||
/// plans it that way: the parts before a line that splits the sheet finish beside its start, so
|
||||
/// the rapid onto it crosses nothing already cut.
|
||||
/// </summary>
|
||||
public class CutOffArrivalOrderTests
|
||||
{
|
||||
// Two columns of squares left of a full-height line, which is cut last, with the tool
|
||||
// arriving from the top right. Aimed at the line's middle, the route ends at the top of the
|
||||
// left column and the rapid down to the line's start crosses the right one. Aimed at the
|
||||
// start, it ends at the bottom of the right column, beside it.
|
||||
[Fact]
|
||||
public void ColumnsBesideASplittingLine_FinishBesideTheLinesStart()
|
||||
{
|
||||
var nest = new Nest();
|
||||
var plate = nest.CreatePlate();
|
||||
plate.Size = new Size(48, 8);
|
||||
var square = new Drawing("square", Square());
|
||||
foreach (var x in new[] { 0.5, 2.5 })
|
||||
for (var y = 2.0; y <= 42; y += 4)
|
||||
plate.Parts.Add(new Part(square, new Vector(x, y)));
|
||||
var line = new CutOff(new Vector(4, 0), CutOffAxis.Vertical);
|
||||
plate.CutOffs.Add(line);
|
||||
plate.RegenerateCutOffs(new CutOffSettings());
|
||||
PlateSequencing.PlaceCutOffs(plate, [line]);
|
||||
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate, new Vector(6, 48),
|
||||
confirmedParameters: ExplicitContourTests.Parameters()));
|
||||
|
||||
Assert.True(result.Status == CuttingPlanStatus.Ready && result.IndependentlyReplayed,
|
||||
$"{result.Status}: {string.Join("; ", result.Findings.Select(f => f.Message).Take(3))}");
|
||||
var order = result.ProposedOrder.Select(o => o.SourcePart).ToArray();
|
||||
Assert.True(order[^1].BaseDrawing.IsCutOff);
|
||||
Assert.Equal(new Vector(2.5, 2), order[^2].Location);
|
||||
}
|
||||
|
||||
// A line through open scrap from (10, 0) to (10, 30) precedes nothing, so it is routed like
|
||||
// a part: reached at its start, left at its far end. Square C sits beside the start and B
|
||||
// beside the end, so the route goes C, the line, B; one that thought the tool stays at the
|
||||
// start would go the line, C, B and travel the column twice.
|
||||
[Fact]
|
||||
public void LineThroughScrap_RouteGoesOnFromItsFarEnd()
|
||||
{
|
||||
var nest = new Nest();
|
||||
var plate = nest.CreatePlate();
|
||||
plate.Size = new Size(40, 20);
|
||||
var square = new Drawing("square", Square());
|
||||
var c = new Part(square, new Vector(12, 1));
|
||||
var b = new Part(square, new Vector(12, 31));
|
||||
plate.Parts.Add(b);
|
||||
plate.Parts.Add(c);
|
||||
var line = new CutOff(new Vector(10, 0), CutOffAxis.Vertical) { StartLimit = 0, EndLimit = 30 };
|
||||
plate.CutOffs.Add(line);
|
||||
plate.RegenerateCutOffs(new CutOffSettings());
|
||||
PlateSequencing.PlaceCutOffs(plate, [line]);
|
||||
|
||||
var result = CuttingPlanService.Plan(CuttingPlanRequest.ForPlate(plate,
|
||||
confirmedParameters: ExplicitContourTests.Parameters()));
|
||||
|
||||
Assert.True(result.Status == CuttingPlanStatus.Ready && result.IndependentlyReplayed,
|
||||
$"{result.Status}: {string.Join("; ", result.Findings.Select(f => f.Message).Take(3))}");
|
||||
var order = result.ProposedOrder.Select(o => o.SourcePart).ToArray();
|
||||
Assert.Equal(3, order.Length);
|
||||
Assert.Same(c, order[0]);
|
||||
Assert.True(order[1].BaseDrawing.IsCutOff);
|
||||
Assert.Same(b, order[2]);
|
||||
}
|
||||
|
||||
private static Program Square()
|
||||
{
|
||||
var program = new Program();
|
||||
program.MoveTo(0, 0);
|
||||
program.LineTo(1, 0);
|
||||
program.LineTo(1, 1);
|
||||
program.LineTo(0, 1);
|
||||
program.LineTo(0, 0);
|
||||
return program;
|
||||
}
|
||||
}
|
||||
@@ -86,6 +86,42 @@ public class CuttingPartOrderTests
|
||||
Assert.Throws<InvalidOperationException>(() => Plan([new(0, 0), new(1, 0)], prerequisites));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Plan_LeavesAPartFromItsExitPoint()
|
||||
{
|
||||
// Part 1 is a line entered at (2, 0) and left at (2, 100). Toured from its entry
|
||||
// alone, 1, 0, 2 looks shortest; left from its far end, 0, 1, 2 is (5 + 5.39 + 5.39).
|
||||
Vector[] entries = [new(0, 5), new(2, 0), new(0, 95)];
|
||||
Vector[] exits = [new(0, 5), new(2, 100), new(0, 95)];
|
||||
|
||||
Assert.Equal(new[] { 0, 1, 2 }, CuttingPartOrder.Plan(entries, Vector.Zero, NoPrerequisites(3),
|
||||
CancellationToken.None, exits: exits));
|
||||
}
|
||||
|
||||
// Part 3 is entered at (0, 6) and left at (0, 13). A reversed run is costed along its own
|
||||
// legs, from each part's exit: 0, 2, 4, 1, 3 (14.97), not 3, 1, 4, 2, 0 (20.17).
|
||||
[Fact]
|
||||
public void Plan_CostsAReversedRunAlongItsOwnLegsFromEachExit()
|
||||
{
|
||||
Vector[] entries = [new(6, 1), new(2, 6), new(5, 2), new(0, 6), new(4, 2)];
|
||||
Vector[] exits = [new(6, 1), new(2, 6), new(5, 2), new(0, 13), new(4, 2)];
|
||||
|
||||
Assert.Equal(new[] { 0, 2, 4, 1, 3 }, CuttingPartOrder.Plan(entries, Vector.Zero, NoPrerequisites(5),
|
||||
CancellationToken.None, exits: exits));
|
||||
}
|
||||
|
||||
// Part 4 is entered at (4, 4) and left at (4, 13). Nearest first goes on from where each
|
||||
// part is left: 4, 2, 1, 0, 3 (19.92), not 0, 3, 2, 1, 4 (20.49).
|
||||
[Fact]
|
||||
public void Plan_NearestFirstGoesOnFromEachPartsExit()
|
||||
{
|
||||
Vector[] entries = [new(7, 4), new(5, 7), new(7, 9), new(9, 6), new(4, 4)];
|
||||
Vector[] exits = [new(7, 4), new(5, 7), new(7, 9), new(9, 6), new(4, 13)];
|
||||
|
||||
Assert.Equal(new[] { 4, 2, 1, 0, 3 }, CuttingPartOrder.Plan(entries, Vector.Zero, NoPrerequisites(5),
|
||||
CancellationToken.None, exits: exits));
|
||||
}
|
||||
|
||||
private static int[] Plan(Vector[] centers, IReadOnlyCollection<int>[] prerequisites) =>
|
||||
CuttingPartOrder.Plan(centers, Vector.Zero, prerequisites, CancellationToken.None);
|
||||
|
||||
|
||||
@@ -198,7 +198,12 @@ strip lines were added, and the segments are placed into that plan afterwards (s
|
||||
[cutoff segments](#cutoff-segments) below). The order is an open
|
||||
traveling-salesman path over part centers from the start point: nearest neighbor,
|
||||
then 2-opt reversals and Or-opt moves of one to three parts, never placing a part
|
||||
before a cutoff or nested-part prerequisite. A part with other parts nested in its
|
||||
before a cutoff or nested-part prerequisite. A cutoff is not a center: the path reaches
|
||||
it where its cut starts and leaves from where it ends (read from its program as given;
|
||||
the search may still reverse a head-locked far-edge segment), and a reversed run is
|
||||
costed along its own legs from each part's exit. The parts
|
||||
before a line that splits the sheet therefore finish beside its start, and the part cut
|
||||
just before it faces that start. A part with other parts nested in its
|
||||
cutouts (a host) is cut after all of them, so the tool reaches it from whichever was
|
||||
cut last, and from the middle of a crowded cutout every straight approach crosses a
|
||||
finished neighbor. The nested parts of each host are therefore toured as one run,
|
||||
|
||||
Reference in new issue
Block a user