mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-15 22:41:36 -04:00
176 lines
8.6 KiB
C#
176 lines
8.6 KiB
C#
using OpenNest.CNC;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Tests.CutOffs;
|
|
|
|
public class AutomaticCutOffShiftClampTests
|
|
{
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
[InlineData(3)]
|
|
[InlineData(4)]
|
|
public void ClampAroundNominalLine_MovesInsteadOfSkipping(int quadrant)
|
|
{
|
|
var plate = AutomaticCutOffShiftTests.UserCase(quadrant);
|
|
plate.EdgeSpacing = new Spacing(0, 0.25, 0, 0);
|
|
var rail = new ClampRail(2, 0.625, new[] { 35.0 });
|
|
plate.SetClampRail(rail);
|
|
var zones = plate.ClampZones.Select(z => (z.Id, z.Bounds.X, z.Bounds.Y, z.Bounds.Length, z.Bounds.Width)).ToArray();
|
|
var parts = plate.Parts.ToArray();
|
|
var plan = AutomaticCutOffPlanner.Create(plate, AutomaticCutOffShiftTests.Options(), new CutOffSettings());
|
|
|
|
Assert.False(plan.HasBlockingDiagnostics);
|
|
Assert.Equal(3, plan.Definitions.Count);
|
|
Assert.True(System.Math.Abs(plan.Definitions[0].Position.X) < 33.75);
|
|
Assert.DoesNotContain(plan.Diagnostics, d => d.Code == AutomaticCutOffDiagnosticCode.ClampBlockedCut);
|
|
Assert.Contains(plan.Diagnostics, d => d.Code == AutomaticCutOffDiagnosticCode.ShiftedCut);
|
|
AutomaticCutOffShiftTests.AssertLimits(plan, 35, 17.5);
|
|
AssertClearsPhysicalFootprints(plate, rail, plan);
|
|
Assert.Equal(zones, plate.ClampZones.Select(z => (z.Id, z.Bounds.X, z.Bounds.Y, z.Bounds.Length, z.Bounds.Width)));
|
|
Assert.Equal(parts, plate.Parts.ToArray());
|
|
Assert.Empty(plate.CutOffs);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
[InlineData(3)]
|
|
[InlineData(4)]
|
|
public void ClampClearanceOnly_ExcludesOtherwiseOptimalCandidate(int quadrant)
|
|
{
|
|
var plate = AutomaticCutOffShiftTests.UserCase(quadrant);
|
|
var rail = new ClampRail(0.2, 0.625, new[] { 35.2 });
|
|
plate.SetClampRail(rail);
|
|
var settings = new CutOffSettings();
|
|
var options = AutomaticCutOffShiftTests.Options();
|
|
var withoutClearance = AutomaticCutOffPlanner.Create(plate, options, settings);
|
|
var first = System.Math.Abs(withoutClearance.Definitions[0].Position.X);
|
|
Assert.InRange(first, 34.69, 34.71);
|
|
Assert.True(first < rail.Centers[0] - rail.Width / 2);
|
|
|
|
// Bottom clearance is the physical rail clearance in every quadrant, not Top.
|
|
plate.EdgeSpacing = new Spacing(0, 0.553, 0, 0);
|
|
var plan = AutomaticCutOffPlanner.Create(plate, options, settings);
|
|
Assert.Equal(3, plan.Definitions.Count);
|
|
Assert.True(System.Math.Abs(plan.Definitions[0].Position.X) < 34.547);
|
|
Assert.DoesNotContain(plan.Diagnostics, d => d.Code == AutomaticCutOffDiagnosticCode.ClampBlockedCut);
|
|
Assert.Equal(withoutClearance.TailSeparatorX, plan.TailSeparatorX);
|
|
AutomaticCutOffShiftTests.AssertLimits(plan, 35, 17.5);
|
|
AssertClearsPhysicalFootprints(plate, rail, plan);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1, 18, 26.25)]
|
|
[InlineData(2, 18, 26.25)]
|
|
[InlineData(3, 18, 26.25)]
|
|
[InlineData(4, 18, 26.25)]
|
|
[InlineData(1, 40, 40)]
|
|
[InlineData(2, 40, 40)]
|
|
[InlineData(3, 40, 40)]
|
|
[InlineData(4, 40, 40)]
|
|
public void NoClampFreeArrangement_FallsBackToWholeGapNominalPositions(int quadrant, double width, double center)
|
|
{
|
|
var plate = AutomaticCutOffShiftTests.UserCase(quadrant);
|
|
var sign = quadrant is 2 or 3 ? -1 : 1;
|
|
var rail = new ClampRail(width, 0.625, new[] { center });
|
|
plate.SetClampRail(rail);
|
|
// Width 18 covers the entire first-line window. Width 40 leaves candidates
|
|
// in both rows, but the allowed first/second windows are over 35 apart.
|
|
var plan = AutomaticCutOffPlanner.Create(plate, AutomaticCutOffShiftTests.Options(), new CutOffSettings());
|
|
|
|
Assert.False(plan.HasBlockingDiagnostics);
|
|
Assert.True(plan.HasSeparatedTail);
|
|
Assert.Equal(new[] { sign * 70.0, plan.TailSeparatorX!.Value }, plan.Definitions.Select(c => c.Position.X));
|
|
var blocked = Assert.Single(plan.Diagnostics.Where(d => d.Code == AutomaticCutOffDiagnosticCode.ClampBlockedCut));
|
|
Assert.Equal(sign * 35, blocked.X);
|
|
Assert.DoesNotContain(plan.Diagnostics, d => d.Code == AutomaticCutOffDiagnosticCode.ShiftedCut);
|
|
Assert.Equal(120, plan.FixedGridCutLength);
|
|
Assert.Equal(60, plan.StripCutLength);
|
|
AssertClearsPhysicalFootprints(plate, rail, plan);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
[InlineData(3)]
|
|
[InlineData(4)]
|
|
public void ClampFallback_IsLocalToGap_OtherGapStillShifts(int quadrant)
|
|
{
|
|
var plate = new Plate(60, 200) { Quadrant = quadrant, PartSpacing = 0.3 };
|
|
var sign = quadrant is 2 or 3 ? -1 : 1;
|
|
foreach (var right in new[] { 34.75, 69.75, 114.75, 149.75, 180 })
|
|
plate.Parts.Add(AutomaticCutOffShiftTests.Rectangle(10, 20,
|
|
quadrant is 2 or 3 ? -right : right - 10, quadrant is 3 or 4 ? -30 : 10));
|
|
plate.CutOffs.Add(new CutOff(new Vector(sign * 80, 0), CutOffAxis.Vertical));
|
|
var rail = new ClampRail(18, 0.625, new[] { 26.25 });
|
|
plate.SetClampRail(rail);
|
|
var plan = AutomaticCutOffPlanner.Create(plate, AutomaticCutOffShiftTests.Options(), new CutOffSettings());
|
|
|
|
Assert.False(plan.HasBlockingDiagnostics);
|
|
Assert.Equal(4, plan.Definitions.Count);
|
|
Assert.Equal(sign * 70, plan.Definitions[0].Position.X);
|
|
var shifted = plan.Definitions.Skip(1).Take(2).Select(c => System.Math.Abs(c.Position.X)).ToArray();
|
|
Assert.InRange(shifted[0], 110.3, 114.75);
|
|
Assert.InRange(shifted[1] - shifted[0], 17.5 - AutomaticCutOffPlanner.GeometryTolerance,
|
|
35 + AutomaticCutOffPlanner.GeometryTolerance);
|
|
Assert.True(shifted[1] < 150);
|
|
Assert.InRange(plan.UsedSpan - shifted[1], 0, 35);
|
|
Assert.Single(plan.Diagnostics.Where(d => d.Code == AutomaticCutOffDiagnosticCode.ClampBlockedCut));
|
|
Assert.Equal(2, plan.Diagnostics.Count(d => d.Code == AutomaticCutOffDiagnosticCode.ShiftedCut));
|
|
AssertClearsPhysicalFootprints(plate, rail, plan);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
[InlineData(3)]
|
|
[InlineData(4)]
|
|
public void NearExistingSeparatorInsideClamp_RemainsUncertified(int quadrant)
|
|
{
|
|
var plate = AutomaticCutOffShiftTests.UserCase(quadrant);
|
|
var options = AutomaticCutOffShiftTests.Options();
|
|
var settings = new CutOffSettings();
|
|
var first = AutomaticCutOffPlanner.Create(plate, options, settings);
|
|
var separator = System.Math.Abs(first.TailSeparatorX!.Value);
|
|
var sign = quadrant is 2 or 3 ? -1 : 1;
|
|
var existing = new CutOff(new Vector(sign * (separator - AutomaticCutOffPlanner.GeometryTolerance / 2), 0),
|
|
CutOffAxis.Vertical);
|
|
plate.CutOffs.Add(existing);
|
|
existing.Regenerate(plate, settings);
|
|
var original = existing.Drawing.Program;
|
|
// Proposed separator is clear, but its Near-equivalent manual coordinate is not.
|
|
var rail = new ClampRail(1, 0.625,
|
|
new[] { separator - AutomaticCutOffPlanner.GeometryTolerance / 4 - 0.5 });
|
|
plate.SetClampRail(rail);
|
|
var plan = AutomaticCutOffPlanner.Create(plate, options, settings);
|
|
|
|
Assert.False(plan.HasBlockingDiagnostics);
|
|
Assert.False(plan.HasSeparatedTail);
|
|
Assert.Equal(plate.Size.Length, plan.UsedSpan);
|
|
Assert.Equal(2, plan.Definitions.Count);
|
|
Assert.Contains(plan.Diagnostics, d => d.Code == AutomaticCutOffDiagnosticCode.ClampBlockedCut);
|
|
Assert.Contains(plan.Diagnostics, d => d.Code == AutomaticCutOffDiagnosticCode.NoSafeTailSeparator);
|
|
Assert.DoesNotContain(plan.Diagnostics, d => d.Code == AutomaticCutOffDiagnosticCode.ExistingCutOff);
|
|
Assert.Same(original, existing.Drawing.Program);
|
|
AssertClearsPhysicalFootprints(plate, rail, plan);
|
|
}
|
|
|
|
private static void AssertClearsPhysicalFootprints(Plate plate, ClampRail rail, AutomaticCutOffPlan plan)
|
|
{
|
|
// Independent physical oracle: distances from the origin, not mapped zone bounds.
|
|
foreach (var cut in plan.Definitions)
|
|
{
|
|
var distance = System.Math.Abs(cut.Position.X);
|
|
foreach (var center in rail.Centers)
|
|
{
|
|
var low = center - rail.Width / 2 - plate.EdgeSpacing.Bottom;
|
|
var high = center + rail.Width / 2 + plate.EdgeSpacing.Bottom;
|
|
Assert.True(distance < low || distance > high, $"Cut {distance:R} enters [{low:R}, {high:R}].");
|
|
}
|
|
foreach (var move in cut.Drawing.Program.Codes.OfType<Motion>())
|
|
Assert.Equal(cut.Position.X, move.EndPoint.X);
|
|
}
|
|
}
|
|
}
|