mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-11 18:01:09 -05:00
feat(fill): tile herringbone pairs at the part's hull angles
Fill paired a 9" angle bracket at 90/270 degrees (75 on a 48 x 96 plate) while a hand-laid herringbone at 315/135 holds 84. Its pair touches only at the legs' ends, so it ranked ~888th by pair box area, and pairs are tiled at the pair's own hull angles, never the part's 45-degree edge. The pairs phase now also builds herringbone pairs: the part turned so a hull edge lies along a sheet axis, its 180-degree copy slid onto the row the part makes along that axis, tiled and remnant-filled like best-fit pairs. Fill now finds the 84. The chevron in the learned-angle fixture became a herringbone whatever angles were learned, so a cross replaces it; the learned-angle and Strip mixed nests are re-pinned. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f62f73533e
commit
735c076afb
5 files changed
+363
-19
No files matched your search
@@ -0,0 +1,67 @@
|
|||||||
|
using OpenNest.CNC;
|
||||||
|
using OpenNest.Engine.Fill;
|
||||||
|
using OpenNest.Engine.Jobs;
|
||||||
|
using OpenNest.Engine.Jobs.Placement;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
using OpenNest.Math;
|
||||||
|
|
||||||
|
namespace OpenNest.Engine.Tests.Fill;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A reported nest: a 9" angle bracket with 2.5" legs on a 48 x 96 plate. Fill paired brackets
|
||||||
|
/// at 90°/270° (75); laid by hand at 315°/135°, each row nests its brackets corner into corner
|
||||||
|
/// along the sheet and the reversed row interlocks with it, a herringbone of 84.
|
||||||
|
/// </summary>
|
||||||
|
public class HerringboneFillTests
|
||||||
|
{
|
||||||
|
/// <summary>The reported drawing's program, in absolute coordinates.</summary>
|
||||||
|
private static Drawing AngleBracket()
|
||||||
|
{
|
||||||
|
var program = new Program();
|
||||||
|
foreach (var (x, y) in new[] { (0.0, 0.0), (-6.25, 3.75), (-6.25, 6.25), (-2.5, 0.0) })
|
||||||
|
{
|
||||||
|
program.MoveTo(x, y);
|
||||||
|
program.ArcTo(new Vector(x, y), new Vector(x - 0.28125, y), RotationType.CCW);
|
||||||
|
}
|
||||||
|
|
||||||
|
program.MoveTo(-7.78125, -1.25);
|
||||||
|
program.LineTo(-7.78125, 7.75);
|
||||||
|
program.LineTo(-5.28125, 7.75);
|
||||||
|
program.LineTo(-5.28125, 2.25);
|
||||||
|
program.ArcTo(new Vector(-4.28125, 1.25), new Vector(-4.28125, 2.25), RotationType.CCW);
|
||||||
|
program.LineTo(1.21875, 1.25);
|
||||||
|
program.LineTo(1.21875, -1.25);
|
||||||
|
program.LineTo(-7.78125, -1.25);
|
||||||
|
return new Drawing("Angle Bracket", program);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void FillFindsTheHerringboneOfAngleBrackets()
|
||||||
|
{
|
||||||
|
var plate = new Plate(new Size(48, 96))
|
||||||
|
{
|
||||||
|
PartSpacing = 0.25,
|
||||||
|
EdgeSpacing = new Spacing(0.5, 0.25, 0.5, 0.25),
|
||||||
|
};
|
||||||
|
var drawing = AngleBracket();
|
||||||
|
|
||||||
|
var parts = PlateFillService.FillItem("Fill", plate,
|
||||||
|
new NestItem { Drawing = drawing, Quantity = 0 }, plate.WorkArea(), null,
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
Assert.Equal(84, parts.Count);
|
||||||
|
Assert.Empty(NestLayoutCheck.Validate(new() { (plate, parts) },
|
||||||
|
new Dictionary<Drawing, (string, int)> { [drawing] = ("Angle Bracket", 84) }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RotationsLayEachHullEdgeAlongTheSheetInEveryQuarterTurn()
|
||||||
|
{
|
||||||
|
var degrees = HerringbonePatterns.Rotations(AngleBracket())
|
||||||
|
.Select(r => System.Math.Round(Angle.ToDegrees(r)))
|
||||||
|
.OrderBy(d => d)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
Assert.Equal(new[] { 0.0, 45, 90, 135, 180, 225, 270, 315 }, degrees);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,9 @@ namespace OpenNest.Engine.Tests.Jobs;
|
|||||||
/// strip with pair grids (and strips narrower than the part's bounding box) and trying skipped
|
/// strip with pair grids (and strips narrower than the part's bounding box) and trying skipped
|
||||||
/// candidates whose remnant could win: Fill now places 34 instead of 33, and Strip keeps 35 with
|
/// candidates whose remnant could win: Fill now places 34 instead of 33, and Strip keeps 35 with
|
||||||
/// its L-shapes laid out differently (both layouts pass NestLayoutCheck).
|
/// its L-shapes laid out differently (both layouts pass NestLayoutCheck).
|
||||||
|
/// Re-captured when the pairs phase began tiling herringbone pairs (<c>HerringbonePatterns</c>):
|
||||||
|
/// the mixed Strip nest keeps 35 laid out differently, and herringbone pairs won the chevron of
|
||||||
|
/// the learned-angle nests whatever angles were learned, so a cross replaced it.
|
||||||
/// Not pinned: richer mixes that pair parallelogram, chevron or tee parts at oblique angles
|
/// Not pinned: richer mixes that pair parallelogram, chevron or tee parts at oblique angles
|
||||||
/// with other irregular items. Their Fill and Vertical Remnant nests vary between runs, most
|
/// with other irregular items. Their Fill and Vertical Remnant nests vary between runs, most
|
||||||
/// likely because best-fit pair candidates are gathered in a <c>ConcurrentBag</c> and equal
|
/// likely because best-fit pair candidates are gathered in a <c>ConcurrentBag</c> and equal
|
||||||
@@ -78,19 +81,20 @@ public class FillStackCharacterizationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 6 x 4 chevron. Filled after <see cref="LongBar"/>, its layout differs between the learned
|
/// 6 x 6 cross with 2" arms. Filled after <see cref="LongBar"/>, its layout differs between
|
||||||
/// (pruned) angle list and a full sweep.
|
/// the learned (pruned) angle list and a full sweep. (A chevron did until herringbone pairs,
|
||||||
|
/// which do not depend on learned angles, began to win it.)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Program Chevron()
|
private static Program Cross()
|
||||||
{
|
{
|
||||||
var program = new Program();
|
var program = new Program();
|
||||||
program.MoveTo(0, 0);
|
program.MoveTo(2, 0);
|
||||||
program.LineTo(3, 2);
|
foreach (var (x, y) in new[]
|
||||||
program.LineTo(6, 0);
|
{
|
||||||
program.LineTo(6, 2);
|
(4.0, 0.0), (4.0, 2.0), (6.0, 2.0), (6.0, 4.0), (4.0, 4.0), (4.0, 6.0),
|
||||||
program.LineTo(3, 4);
|
(2.0, 6.0), (2.0, 4.0), (0.0, 4.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0),
|
||||||
program.LineTo(0, 2);
|
})
|
||||||
program.LineTo(0, 0);
|
program.LineTo(x, y);
|
||||||
return program;
|
return program;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +105,7 @@ public class FillStackCharacterizationTests
|
|||||||
"notched" => Notched(),
|
"notched" => Notched(),
|
||||||
"arc" => TestDrawingFactory.ArcPart(),
|
"arc" => TestDrawingFactory.ArcPart(),
|
||||||
"bar" => LongBar(),
|
"bar" => LongBar(),
|
||||||
"chevron" => Chevron(),
|
"cross" => Cross(),
|
||||||
_ => TestDrawingFactory.Rectangle(4, 3),
|
_ => TestDrawingFactory.Rectangle(4, 3),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -258,13 +262,13 @@ public class FillStackCharacterizationTests
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The bar fills first and is productive only near 0 and 180 degrees, so the Fill filler
|
/// The bar fills first and is productive only near 0 and 180 degrees, so the Fill filler
|
||||||
/// prunes the chevron and notched fills to the angles it learned (see
|
/// prunes the cross and notched fills to the angles it learned (see
|
||||||
/// <see cref="Nest_LearnedAngleItems_DependsOnLearnedAngles"/>).
|
/// <see cref="Nest_LearnedAngleItems_DependsOnLearnedAngles"/>).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static List<NestItem> LearnedAngleItems() =>
|
private static List<NestItem> LearnedAngleItems() =>
|
||||||
[
|
[
|
||||||
new() { Drawing = new Drawing("bar", LongBar()), Quantity = 3 },
|
new() { Drawing = new Drawing("bar", LongBar()), Quantity = 3 },
|
||||||
new() { Drawing = new Drawing("chevron", Chevron()), Quantity = 20 },
|
new() { Drawing = new Drawing("cross", Cross()), Quantity = 20 },
|
||||||
new() { Drawing = new Drawing("notched", Notched()), Quantity = 4 },
|
new() { Drawing = new Drawing("notched", Notched()), Quantity = 4 },
|
||||||
new() { Drawing = new Drawing("rect", TestDrawingFactory.Rectangle(4, 3)), Quantity = 1 },
|
new() { Drawing = new Drawing("rect", TestDrawingFactory.Rectangle(4, 3)), Quantity = 1 },
|
||||||
];
|
];
|
||||||
@@ -281,7 +285,7 @@ public class FillStackCharacterizationTests
|
|||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("Fill", 34, "D3D54E9116D96ACF")]
|
[InlineData("Fill", 34, "D3D54E9116D96ACF")]
|
||||||
[InlineData("Strip", 35, "D5A35DDDE6A5B36A")]
|
[InlineData("Strip", 35, "55A0EE3EDF28E68E")]
|
||||||
[InlineData("Vertical Remnant", 35, "8CC17C7708C8049F")]
|
[InlineData("Vertical Remnant", 35, "8CC17C7708C8049F")]
|
||||||
[InlineData("Horizontal Remnant", 30, "A0700052AE771E06")]
|
[InlineData("Horizontal Remnant", 30, "A0700052AE771E06")]
|
||||||
public void Nest_MixedIrregularItems_PinnedLayout(
|
public void Nest_MixedIrregularItems_PinnedLayout(
|
||||||
@@ -296,10 +300,10 @@ public class FillStackCharacterizationTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("Fill", 28, "2D424BF0DBA7CD32")]
|
[InlineData("Fill", 28, "BE363E3A47E9B036")]
|
||||||
[InlineData("Strip", 28, "E13C9185F4206BA8")]
|
[InlineData("Strip", 23, "E4AA82327508D538")]
|
||||||
[InlineData("Vertical Remnant", 28, "2D424BF0DBA7CD32")]
|
[InlineData("Vertical Remnant", 28, "DB2409DB39642666")]
|
||||||
[InlineData("Horizontal Remnant", 28, "5E349DF948B78808")]
|
[InlineData("Horizontal Remnant", 24, "883BD209AE87E2C5")]
|
||||||
public void Nest_LearnedAngleItems_PinnedLayout(
|
public void Nest_LearnedAngleItems_PinnedLayout(
|
||||||
string strategy,
|
string strategy,
|
||||||
int expectedCount,
|
int expectedCount,
|
||||||
|
|||||||
@@ -0,0 +1,198 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
using OpenNest.Math;
|
||||||
|
|
||||||
|
namespace OpenNest.Engine.Fill
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Builds herringbone pairs: a part turned so one of its convex-hull edges lies along a sheet
|
||||||
|
/// axis, and a copy turned 180° more, nested into the row the first part makes when repeated
|
||||||
|
/// along that axis. Tiled, these give rows of same-facing parts nested into each other with
|
||||||
|
/// every other row reversed. Best-fit pairs rank by pair bounding box, so a pair whose parts
|
||||||
|
/// touch only at their ends (as an L bracket's legs do in a herringbone) ranks far too low to
|
||||||
|
/// be tried, and the pair's own hull rarely holds the angle its rows need.
|
||||||
|
/// </summary>
|
||||||
|
internal static class HerringbonePatterns
|
||||||
|
{
|
||||||
|
/// <summary>Hull angles tried, longest edges first.</summary>
|
||||||
|
private const int MaxHullAngles = 8;
|
||||||
|
|
||||||
|
/// <summary>Offsets along the row, across one row pitch, at which the reversed part is tried.</summary>
|
||||||
|
private const int ShiftSamples = 32;
|
||||||
|
|
||||||
|
/// <summary>Finer offsets tried either side of each kept sample.</summary>
|
||||||
|
private const int RefineSamples = 8;
|
||||||
|
|
||||||
|
/// <summary>Pairs kept per rotation and row axis: those making the shallowest double row.</summary>
|
||||||
|
private const int KeptShifts = 2;
|
||||||
|
|
||||||
|
private static readonly double AngleTolerance = Angle.ToRadians(1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The patterns to tile, grouped by part rotation, each group with its row axis. Every
|
||||||
|
/// pattern holds two parts, the second turned 180° from the first and clear of it by
|
||||||
|
/// <paramref name="partSpacing"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static List<List<(Pattern Pattern, NestDirection Dir)>> Build(
|
||||||
|
Drawing drawing,
|
||||||
|
double partSpacing,
|
||||||
|
CancellationToken token
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var groups = new List<List<(Pattern, NestDirection)>>();
|
||||||
|
foreach (var rotation in Rotations(drawing))
|
||||||
|
{
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
var group = new List<(Pattern, NestDirection)>();
|
||||||
|
foreach (var axis in new[] { NestDirection.Horizontal, NestDirection.Vertical })
|
||||||
|
{
|
||||||
|
foreach (var pattern in BuildPairs(drawing, rotation, axis, partSpacing))
|
||||||
|
group.Add((pattern, axis));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group.Count > 0)
|
||||||
|
groups.Add(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotations that lay one of the part's longest hull edges along a sheet axis, in each of
|
||||||
|
/// the four quarter turns.
|
||||||
|
/// </summary>
|
||||||
|
internal static List<double> Rotations(Drawing drawing)
|
||||||
|
{
|
||||||
|
var hullAngles = RotationAnalysis.FindHullEdgeAngles(
|
||||||
|
new List<Part> { Part.CreateAtOrigin(drawing) }
|
||||||
|
);
|
||||||
|
|
||||||
|
var bases = new List<double>();
|
||||||
|
foreach (var angle in hullAngles)
|
||||||
|
{
|
||||||
|
var quarter = Angle.NormalizeRad(angle) % Angle.HalfPI;
|
||||||
|
if (bases.Any(b => SameQuarterAngle(b, quarter)))
|
||||||
|
continue;
|
||||||
|
bases.Add(quarter);
|
||||||
|
if (bases.Count == MaxHullAngles)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rotations = new List<double>();
|
||||||
|
foreach (var quarter in bases)
|
||||||
|
{
|
||||||
|
for (var k = 0; k < 4; k++)
|
||||||
|
rotations.Add(Angle.NormalizeRad(quarter + k * Angle.HalfPI));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rotations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool SameQuarterAngle(double a, double b)
|
||||||
|
{
|
||||||
|
var difference = System.Math.Abs(a - b);
|
||||||
|
return difference < AngleTolerance || Angle.HalfPI - difference < AngleTolerance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Pattern> BuildPairs(
|
||||||
|
Drawing drawing,
|
||||||
|
double rotation,
|
||||||
|
NestDirection axis,
|
||||||
|
double partSpacing
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var result = new List<Pattern>();
|
||||||
|
var first = Part.CreateAtOrigin(drawing, rotation);
|
||||||
|
var reversed = Part.CreateAtOrigin(drawing, Angle.NormalizeRad(rotation + System.Math.PI));
|
||||||
|
if (first.BoundingBox.Area() <= 0)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var single = new Pattern();
|
||||||
|
single.Parts.Add(first);
|
||||||
|
single.UpdateBounds();
|
||||||
|
|
||||||
|
var huge = new Box(0, 0, 1e6, 1e6);
|
||||||
|
var pitch = new FillLinear(huge, partSpacing).CopyDistance(single, axis);
|
||||||
|
if (!(pitch > Tolerance.Epsilon) || double.IsInfinity(pitch))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var horizontal = axis == NestDirection.Horizontal;
|
||||||
|
var halfSpacing = partSpacing / 2;
|
||||||
|
var firstBox = first.BoundingBox;
|
||||||
|
var reversedBox = reversed.BoundingBox;
|
||||||
|
|
||||||
|
// Enough copies of the first part to meet the reversed one anywhere along a pitch.
|
||||||
|
var reach = (horizontal ? reversedBox.Width : reversedBox.Length)
|
||||||
|
/ pitch;
|
||||||
|
var copies = (int)System.Math.Ceiling(reach) + 1;
|
||||||
|
var row = new List<Entity>();
|
||||||
|
for (var k = -copies; k <= copies; k++)
|
||||||
|
{
|
||||||
|
var along = k * pitch;
|
||||||
|
var copy = first.CloneAtOffset(horizontal ? new Vector(along, 0) : new Vector(0, along));
|
||||||
|
row.AddRange(PartGeometry.GetOffsetPerimeterEntities(copy, halfSpacing));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The reversed part starts clear of the row on its far side and slides back onto it.
|
||||||
|
var push = horizontal ? PushDirection.Down : PushDirection.Left;
|
||||||
|
var across = horizontal
|
||||||
|
? firstBox.Top - reversedBox.Bottom + partSpacing + 1
|
||||||
|
: firstBox.Right - reversedBox.Left + partSpacing + 1;
|
||||||
|
|
||||||
|
Pattern Place(double shift)
|
||||||
|
{
|
||||||
|
var offset = horizontal
|
||||||
|
? new Vector(firstBox.Left - reversedBox.Left + shift, across)
|
||||||
|
: new Vector(across, firstBox.Bottom - reversedBox.Bottom + shift);
|
||||||
|
var moved = reversed.CloneAtOffset(offset);
|
||||||
|
var distance = SpatialQuery.DirectionalDistance(
|
||||||
|
PartGeometry.GetOffsetPerimeterEntities(moved, halfSpacing),
|
||||||
|
row,
|
||||||
|
push
|
||||||
|
);
|
||||||
|
if (distance >= double.MaxValue || distance < 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
moved = moved.CloneAtOffset(horizontal ? new Vector(0, -distance) : new Vector(-distance, 0));
|
||||||
|
var pattern = new Pattern();
|
||||||
|
pattern.Parts.Add((Part)first.Clone());
|
||||||
|
pattern.Parts.Add(moved);
|
||||||
|
foreach (var part in pattern.Parts)
|
||||||
|
part.UpdateBounds();
|
||||||
|
pattern.UpdateBounds();
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
double Depth(Pattern pattern) =>
|
||||||
|
horizontal ? pattern.BoundingBox.Width : pattern.BoundingBox.Length;
|
||||||
|
|
||||||
|
var step = pitch / ShiftSamples;
|
||||||
|
var sampled = new List<(double Shift, Pattern Pattern)>();
|
||||||
|
for (var i = 0; i < ShiftSamples; i++)
|
||||||
|
{
|
||||||
|
var pattern = Place(i * step);
|
||||||
|
if (pattern != null)
|
||||||
|
sampled.Add((i * step, pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (shift, coarse) in sampled.OrderBy(s => Depth(s.Pattern)).Take(KeptShifts))
|
||||||
|
{
|
||||||
|
var best = coarse;
|
||||||
|
for (var j = -RefineSamples; j <= RefineSamples; j++)
|
||||||
|
{
|
||||||
|
if (j == 0)
|
||||||
|
continue;
|
||||||
|
var pattern = Place(shift + j * step / RefineSamples);
|
||||||
|
if (pattern != null && Depth(pattern) < Depth(best) - Tolerance.Epsilon)
|
||||||
|
best = pattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add(best);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -157,9 +157,84 @@ namespace OpenNest.Engine.Fill
|
|||||||
reportProgress
|
reportProgress
|
||||||
);
|
);
|
||||||
|
|
||||||
|
parts = EvaluateHerringbones(
|
||||||
|
item.Drawing,
|
||||||
|
workArea,
|
||||||
|
parts,
|
||||||
|
candidates,
|
||||||
|
bestFits,
|
||||||
|
token,
|
||||||
|
reportProgress
|
||||||
|
);
|
||||||
|
|
||||||
return new PairFillResult { Parts = parts, BestFits = bestFits };
|
return new PairFillResult { Parts = parts, BestFits = bestFits };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tiles the drawing's herringbone pairs (<see cref="HerringbonePatterns"/>), one group
|
||||||
|
/// per part rotation, with the same grid and remnant fill as the best-fit pairs, and
|
||||||
|
/// returns the better of them and <paramref name="best"/> by the comparer; a tie keeps
|
||||||
|
/// <paramref name="best"/>.
|
||||||
|
/// </summary>
|
||||||
|
private List<Part> EvaluateHerringbones(
|
||||||
|
Drawing drawing,
|
||||||
|
Box workArea,
|
||||||
|
List<Part> best,
|
||||||
|
List<BestFitResult> candidates,
|
||||||
|
List<BestFitResult> bestFits,
|
||||||
|
CancellationToken token,
|
||||||
|
Action<List<Part>, string> reportProgress
|
||||||
|
)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var groups = HerringbonePatterns.Build(drawing, partSpacing, token);
|
||||||
|
if (groups.Count == 0)
|
||||||
|
return best;
|
||||||
|
|
||||||
|
var maxUtilization = candidates.Count > 0 ? candidates.Max(c => c.Utilization) : 1.0;
|
||||||
|
var partBox = drawing.Program.BoundingBox();
|
||||||
|
var partArea = System.Math.Max(partBox.Width * partBox.Length, 1);
|
||||||
|
var minCountToBeat = best?.Count ?? 0;
|
||||||
|
|
||||||
|
var results = new List<Part>[groups.Count];
|
||||||
|
Parallel.For(
|
||||||
|
0,
|
||||||
|
groups.Count,
|
||||||
|
new ParallelOptions { CancellationToken = token },
|
||||||
|
i =>
|
||||||
|
{
|
||||||
|
results[i] = EvaluateCandidate(
|
||||||
|
groups[i],
|
||||||
|
drawing,
|
||||||
|
workArea,
|
||||||
|
minCountToBeat,
|
||||||
|
maxUtilization,
|
||||||
|
partArea,
|
||||||
|
bestFits,
|
||||||
|
token
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// In rotation order, so the same layout wins however the workers finish.
|
||||||
|
foreach (var result in results)
|
||||||
|
{
|
||||||
|
if (comparer.IsBetter(result, best, workArea))
|
||||||
|
{
|
||||||
|
best = result;
|
||||||
|
reportProgress?.Invoke(best, $"Pairs: herringbone, best = {best.Count} parts");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Debug.WriteLine("[PairFiller] Canceled during herringbone pairs, using results so far");
|
||||||
|
}
|
||||||
|
|
||||||
|
return best ?? new List<Part>();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The EvaluateCandidates
|
/// The EvaluateCandidates
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ in `OpenNest.Engine/NestingEngines/<Name>/`, its tests in `OpenNest.Engine.Tests
|
|||||||
| Circles | Round parts: blanks, washers, flanges, gears by their tip circle | Each part packed as its minimum enclosing circle at centers tangent to two of walls, placed discs and clamp keep-outs, with hexagonal and square lattice seeds; bids only when every part is round; stock chosen as Rectangles does, plus whole-job runs preferring each stock and a tail re-plan |
|
| Circles | Round parts: blanks, washers, flanges, gears by their tip circle | Each part packed as its minimum enclosing circle at centers tangent to two of walls, placed discs and clamp keep-outs, with hexagonal and square lattice seeds; bids only when every part is round; stock chosen as Rectangles does, plus whole-job runs preferring each stock and a tail re-plan |
|
||||||
| Irregular | Irregular profiles | No-fit-polygon frontier packing with gap filling and best-fit pairs, six whole-job strategy variants and a tail re-plan, then grouped variants that pack like parts together, then a sheet-reduction pass that packs edge parts first and turns a key part by its contacts; under a time limit it keeps trying seeded variants until the limit |
|
| Irregular | Irregular profiles | No-fit-polygon frontier packing with gap filling and best-fit pairs, six whole-job strategy variants and a tail re-plan, then grouped variants that pack like parts together, then a sheet-reduction pass that packs edge parts first and turns a key part by its contacts; under a time limit it keeps trying seeded variants until the limit |
|
||||||
| FlatBar | Flat bar: parts about as wide as the bar | Each stock read as a bar and cut in one row: the CutList cut-list packer and a best-fill search on part lengths less their self-nesting gain, then a slide-left pass that nests shaped ends and refills freed length; stock chosen bar by bar by whole-plan cost |
|
| FlatBar | Flat bar: parts about as wide as the bar | Each stock read as a bar and cut in one row: the CutList cut-list packer and a best-fill search on part lengths less their self-nesting gain, then a slide-left pass that nests shaped ends and refills freed length; stock chosen bar by bar by whole-plan cost |
|
||||||
| Lattice, Strip, Vertical Remnant, Horizontal Remnant | Many copies of a few parts | The fixed placement strategies behind interactive fill; Lattice is the multi-phase lattice fill (linear, pairs, rectangle best-fit, remainder; the pairs phase fills the strip its grid leaves with rows of pairs when they hold more than single parts), and the remnant strategies run it ranked to keep the largest drop. In whole jobs, all four place a plate with any fixed or bounded part rotation by the rotation-aware ordered placer instead |
|
| Lattice, Strip, Vertical Remnant, Horizontal Remnant | Many copies of a few parts | The fixed placement strategies behind interactive fill; Lattice is the multi-phase lattice fill (linear, pairs, rectangle best-fit, remainder; the pairs phase fills the strip its grid leaves with rows of pairs when they hold more than single parts, and also tiles herringbone pairs: the part turned so a hull edge lies along a sheet axis, nested into its own row, with every other row reversed), and the remnant strategies run it ranked to keep the largest drop. In whole jobs, all four place a plate with any fixed or bounded part rotation by the rotation-aware ordered placer instead |
|
||||||
|
|
||||||
Each engine's plain description (`NestingEngineInfo.Description`) is shown beside its name in the
|
Each engine's plain description (`NestingEngineInfo.Description`) is shown beside its name in the
|
||||||
desktop engine lists. The main-window selection also picks Fill Area's strategy; a selection that
|
desktop engine lists. The main-window selection also picks Fill Area's strategy; a selection that
|
||||||
|
|||||||
Reference in new issue
Block a user