mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-03 17:01:38 -05:00
fix(geometry): leave leads out of push and collision material
A part with lead-ins applied could not be pushed away once another part touched it. PartGeometry kept LEADIN/LEADOUT entities as material, so the leads chained onto the perimeter as open spurs and the spacing offset no longer closed. The slide-contact classifier cannot tell the material side of an unclosed boundary and blocks every contact, so parts resting against it could not move up or right off it. Leads are cut into scrap, and the spacing review, overlap analyzer and support checks already drop them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
322a66c339
commit
d133f6879b
3 files changed
+56
-28
No files matched your search
@@ -7,12 +7,21 @@ namespace OpenNest
|
||||
{
|
||||
public static class PartGeometry
|
||||
{
|
||||
/// <summary>
|
||||
/// Nesting material of a program, without lead-ins and lead-outs: a lead is a cut
|
||||
/// into scrap, not part material, and chaining it into the perimeter leaves an open
|
||||
/// spur, so the offset contour no longer closes and every slide contact against it
|
||||
/// blocks.
|
||||
/// </summary>
|
||||
private static List<Entity> MaterialEntities(CNC.Program program) =>
|
||||
ConvertProgram.ToGeometry(program)
|
||||
.Where(e => e.Layer != SpecialLayers.Leadin && e.Layer != SpecialLayers.Leadout)
|
||||
.ForNesting();
|
||||
|
||||
public static List<Line> GetPartLines(Part part, double chordTolerance = 0.001)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(
|
||||
entities.ForNesting()
|
||||
);
|
||||
var entities = MaterialEntities(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(entities);
|
||||
var lines = new List<Line>();
|
||||
|
||||
foreach (var shape in shapes)
|
||||
@@ -55,10 +64,8 @@ namespace OpenNest
|
||||
public static List<Entity> GetOffsetPerimeterEntities(CNC.Program program, double spacing)
|
||||
{
|
||||
PerfCounters.CountOffsetPerimeterEntities();
|
||||
var geoEntities = ConvertProgram.ToGeometry(program);
|
||||
var profile = new ShapeProfile(
|
||||
geoEntities.ForNesting().ToList()
|
||||
);
|
||||
var geoEntities = MaterialEntities(program);
|
||||
var profile = new ShapeProfile(geoEntities);
|
||||
|
||||
var offsetShape = profile.Perimeter.OffsetOutward(spacing);
|
||||
if (offsetShape == null)
|
||||
@@ -73,10 +80,8 @@ namespace OpenNest
|
||||
/// </summary>
|
||||
public static List<Entity> GetOffsetPartEntities(Part part, double spacing)
|
||||
{
|
||||
var geoEntities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
geoEntities.ForNesting().ToList()
|
||||
);
|
||||
var geoEntities = MaterialEntities(part.Program);
|
||||
var profile = new ShapeProfile(geoEntities);
|
||||
var entities = new List<Entity>();
|
||||
|
||||
var perimeter = profile.Perimeter.OffsetOutward(spacing);
|
||||
@@ -106,10 +111,8 @@ namespace OpenNest
|
||||
/// </summary>
|
||||
public static List<Entity> GetPerimeterEntities(Part part)
|
||||
{
|
||||
var geoEntities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
geoEntities.ForNesting().ToList()
|
||||
);
|
||||
var geoEntities = MaterialEntities(part.Program);
|
||||
var profile = new ShapeProfile(geoEntities);
|
||||
|
||||
return CopyEntitiesAtLocation(profile.Perimeter.Entities, part.Location);
|
||||
}
|
||||
@@ -120,10 +123,8 @@ namespace OpenNest
|
||||
/// </summary>
|
||||
public static List<Entity> GetPartEntities(Part part)
|
||||
{
|
||||
var geoEntities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
geoEntities.ForNesting().ToList()
|
||||
);
|
||||
var geoEntities = MaterialEntities(part.Program);
|
||||
var profile = new ShapeProfile(geoEntities);
|
||||
var entities = CopyEntitiesAtLocation(profile.Perimeter.Entities, part.Location);
|
||||
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
@@ -165,10 +166,8 @@ namespace OpenNest
|
||||
Vector vectorDirection
|
||||
)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(
|
||||
entities.ForNesting()
|
||||
);
|
||||
var entities = MaterialEntities(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(entities);
|
||||
var lines = new List<Line>();
|
||||
|
||||
foreach (var shape in shapes)
|
||||
|
||||
@@ -312,6 +312,35 @@ namespace OpenNest.Tests.Fill
|
||||
AssertClearance(moving, obstacle, 2);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(PushDirection.Right)]
|
||||
[InlineData(PushDirection.Up)]
|
||||
public void Push_WithSpacing_ContactWithLeadInPartDoesNotBlockOtherDirections(
|
||||
PushDirection next
|
||||
)
|
||||
{
|
||||
// Leads are cut into scrap, not material: chained into the obstacle's perimeter
|
||||
// they left its offset contour open, so every contact with it blocked.
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var obstacle = MakeRectPart(20, 20, 40, 10);
|
||||
obstacle.ApplyLeadIns(new OpenNest.CNC.CuttingStrategy.CuttingParameters
|
||||
{
|
||||
ExternalLeadIn = new OpenNest.CNC.CuttingStrategy.LineLeadIn { Length = 0.25 },
|
||||
ExternalLeadOut = new OpenNest.CNC.CuttingStrategy.LineLeadOut { Length = 0.25 },
|
||||
}, new Vector(0, 0));
|
||||
var moving = MakeRectPart(30, 60, 5, 5);
|
||||
var parts = new List<Part> { moving };
|
||||
var obstacles = new List<Part> { obstacle };
|
||||
|
||||
Assert.True(Compactor.Push(parts, obstacles, workArea, 2, PushDirection.Down) > 0);
|
||||
Assert.Equal(32, moving.BoundingBox.Bottom, 7);
|
||||
|
||||
var distance = Compactor.Push(parts, obstacles, workArea, 2, next);
|
||||
|
||||
Assert.True(distance > 1, $"Push {next} after contact moved only {distance:R}");
|
||||
AssertClearance(moving, obstacle, 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Push_WithSpacing_ContactStillBlocksTheSameDirection()
|
||||
{
|
||||
|
||||
@@ -45,13 +45,13 @@ public class PartGeometryDirectionalPreparationTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaterialIncludesDisplayAndLeads_ButExcludesRapidAndScribe()
|
||||
public void MaterialIncludesDisplay_ButExcludesLeadsRapidAndScribe()
|
||||
{
|
||||
var material = RectangleProgram(false);
|
||||
AddRectangle(material, 15, 2, 3, 2, LayerType.Display);
|
||||
var clean = MakePart(material);
|
||||
AddRectangle(material, 21, 3, 2, 4, LayerType.Leadin);
|
||||
AddRectangle(material, 26, 1, 4, 3, LayerType.Leadout);
|
||||
var clean = MakePart(material);
|
||||
AddRectangle(material, 100, 120, 7, 9, LayerType.Scribe);
|
||||
material.MoveTo(-200, -300);
|
||||
material.MoveTo(400, 500);
|
||||
@@ -59,9 +59,9 @@ public class PartGeometryDirectionalPreparationTests
|
||||
foreach (var direction in new[] { PushDirection.Left, PushDirection.Right, PushDirection.Up, PushDirection.Down })
|
||||
{
|
||||
var expected = FrozenGetPartLines(clean, direction);
|
||||
Assert.Equal(4, expected.Count);
|
||||
Assert.Equal(2, expected.Count);
|
||||
AssertLines(expected, PartGeometry.GetPartLines(marked, direction));
|
||||
AssertMatches(marked, direction, new Vector(3, -2), 0.001);
|
||||
AssertMatches(clean, direction, new Vector(3, -2), 0.001);
|
||||
}
|
||||
AssertLines(PartGeometry.GetPartLines(clean), PartGeometry.GetPartLines(marked));
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user