mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-09 05:35:43 -05:00
Six-decimal incremental PEP programs round arc centers, so an arc's end tangent misses its neighbor's by about gap / radius (1.6e-5 rad on the offset 5/32" fillet in the reported nest). Two parts resting one spacing apart then gave the slide contact a Minkowski cone a hair wider than a half-plane, which the classifier read as the whole plane: every direction blocked, so the top part could not be pushed sideways along the part below. SlideContact now reads a cone up to TangentSlack (1e-4 rad) past a half-plane as a noisy half-plane and widens its boundary and concave-ray comparisons by the excess, so near-tangential slides read as tangential. A direction admitted that way can enter material by at most travel x excess at that contact, and every other contact is still classified on its own; wider cones remain real overlap and block. Raising JointTolerance was not needed for this case and is left at 1e-6. Whole-job benchmarks (synthetic nests plus the PEP job at 3 and 60 parts, --parallel 1) are identical before and after for every engine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
125 lines
4.2 KiB
C#
125 lines
4.2 KiB
C#
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using OpenNest.Engine.Fill;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.IO;
|
|
using Xunit;
|
|
|
|
namespace OpenNest.Tests.Fill;
|
|
|
|
/// <summary>
|
|
/// A part placed exactly one part spacing from its neighbor can always be pushed away
|
|
/// from it, whatever rounding the drawing's joints carry.
|
|
/// </summary>
|
|
public class CompactorPushAwayTests
|
|
{
|
|
// A PEP drawing (incremental, six decimals) whose arc ends miss their lines by a few
|
|
// millionths, so its offset contour has hairline kinks where the parts touch.
|
|
private const string PepProgram = """
|
|
G91
|
|
G00X0Y0
|
|
G03X0Y0I-2J0
|
|
G00X-10.173792Y2.75
|
|
G01X16.347584Y0
|
|
G02X0.03125Y-0.03125I0J-0.03125
|
|
G01X0Y-5.742929
|
|
G02X-0.03125Y-0.03125I-0.03125J0
|
|
G01X-5.1294Y0
|
|
G02X-0.058719Y0.041096I0.0000011307J0.0624992087
|
|
G03X-0.15384Y-0.15384I-0.112744J-0.041096
|
|
G02X0.041096Y-0.05872I-0.0214041482J-0.0587207882
|
|
G01X0Y-5.1294
|
|
G02X-0.03125Y-0.031249I-0.0312495J0.0000005
|
|
G01X-5.683358Y0
|
|
G02X-0.03125Y0.03125I0J0.03125
|
|
G01X0Y5.1294
|
|
G02X0.041096Y0.058719I0.0624992087J-0.0000011307
|
|
G03X-0.15384Y0.15384I-0.041096J0.112744
|
|
G02X-0.05872Y-0.041096I-0.0587207882J0.0214041482
|
|
G01X-5.1294Y0
|
|
G02X-0.031249Y0.03125I0.0000005J0.0312495
|
|
G01X0Y5.742929
|
|
G02X0.03125Y0.03125I0.03125J0
|
|
""";
|
|
|
|
private static (Plate Plate, Part Bottom, Part Top) StackedPair()
|
|
{
|
|
var program = new ProgramReader(new MemoryStream(Encoding.UTF8.GetBytes(PepProgram))).Read();
|
|
var drawing = new Drawing("PT48", program);
|
|
var plate = new Plate(new Size(48, 96)) { PartSpacing = 0.25 };
|
|
plate.EdgeSpacing = new Spacing(0.25, 0.25);
|
|
|
|
// The positions the user's nest saved: the top part one spacing above the bottom.
|
|
var bottom = new Part(drawing, new Vector(3, 10.455042000004001));
|
|
bottom.Rotate(System.Math.PI / 2, bottom.Location);
|
|
var top = new Part(drawing, new Vector(3, 27.115126000007997));
|
|
top.Rotate(System.Math.PI / 2, top.Location);
|
|
plate.Parts.Add(bottom);
|
|
plate.Parts.Add(top);
|
|
return (plate, bottom, top);
|
|
}
|
|
|
|
[Fact]
|
|
public void TopPartPushesUpAwayFromTheTouchingPartBelow()
|
|
{
|
|
var (plate, bottom, top) = StackedPair();
|
|
Assert.Equal(0.25, top.BoundingBox.Bottom - bottom.BoundingBox.Top, 6);
|
|
var expected = plate.WorkArea().Top - top.BoundingBox.Top;
|
|
Assert.True(expected > 1);
|
|
|
|
var moved = Compactor.Push(new List<Part> { top }, plate, PushDirection.Up);
|
|
|
|
Assert.Equal(expected, moved, 6);
|
|
}
|
|
|
|
[Fact]
|
|
public void BottomPartPushesDownAwayFromTheTouchingPartAbove()
|
|
{
|
|
var (plate, bottom, top) = StackedPair();
|
|
bottom.Offset(0, 5);
|
|
top.Offset(0, 5);
|
|
var expected = bottom.BoundingBox.Bottom - plate.WorkArea().Bottom;
|
|
|
|
var moved = Compactor.Push(new List<Part> { bottom }, plate, PushDirection.Down);
|
|
|
|
Assert.Equal(expected, moved, 6);
|
|
}
|
|
|
|
[Fact]
|
|
public void BottomPartStillStopsAgainstThePartAbove()
|
|
{
|
|
var (plate, bottom, top) = StackedPair();
|
|
|
|
Assert.Equal(0, Compactor.Push(new List<Part> { bottom }, plate, PushDirection.Up));
|
|
Assert.Equal(0, Compactor.PushBoundingBox(new List<Part> { bottom }, plate, PushDirection.Up));
|
|
}
|
|
|
|
[Fact]
|
|
public void TopPartStillStopsAgainstThePartBelow()
|
|
{
|
|
var (plate, bottom, top) = StackedPair();
|
|
|
|
Assert.Equal(0, Compactor.Push(new List<Part> { top }, plate, PushDirection.Down));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(PushDirection.Right)]
|
|
[InlineData(PushDirection.Left)]
|
|
public void TopPartSlidesAlongTheTouchingPartBelow(PushDirection direction)
|
|
{
|
|
var (plate, bottom, top) = StackedPair();
|
|
bottom.Offset(5, 0);
|
|
top.Offset(5, 0);
|
|
var work = plate.WorkArea();
|
|
var expected = direction == PushDirection.Right
|
|
? work.Right - top.BoundingBox.Right
|
|
: top.BoundingBox.Left - work.Left;
|
|
Assert.True(expected > 0.1);
|
|
|
|
var moved = Compactor.Push(new List<Part> { top }, plate, direction);
|
|
|
|
Assert.Equal(expected, moved, 6);
|
|
}
|
|
}
|