mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 13:51:06 -04:00
Some jobs cut only cutouts into a supplied blank, such as instrument holes in a purchased door that is the whole sheet. Its outline has to be known to the planner (the blank is material that leads, rapids and cut-offs must respect) but must never be pierced, led or cut. A new DisplayPerimeter layer marks that outline. It is material, so nesting, containment, lead clearance and automatic cut-offs all see the blank; a door that fills its sheet leaves cut-offs nothing to cut. Cutting preparation keeps it out of the contours entirely: with no cut perimeter every contour is a hole with internal leads, and the search, best-effort and safest-route paths stop offering a perimeter. Replay refuses any run along the display perimeter and completes once every other ring is cut. A display perimeter must be one whole closed contour enclosing every other contour; a mixed ring, one inside another contour, or a second one is refused with that reason. Saving, drawing and posting it are still to do.
193 lines
6.3 KiB
C#
193 lines
6.3 KiB
C#
using System.Collections.Generic;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.Math;
|
|
|
|
namespace OpenNest.Converters
|
|
{
|
|
public static class ConvertGeometry
|
|
{
|
|
public static Program ToProgram(IList<Entity> geometry)
|
|
{
|
|
// Break cuts keep their order and direction (their scrap side): they are emitted as
|
|
// given, one program run per continuous chain, before the contours.
|
|
var breakCuts = new List<Shape>();
|
|
var contours = new List<Entity>();
|
|
foreach (var entity in geometry)
|
|
{
|
|
if (entity.Layer != SpecialLayers.Break)
|
|
{
|
|
contours.Add(entity);
|
|
continue;
|
|
}
|
|
var last = breakCuts.Count == 0 ? null : breakCuts[^1].Entities[^1];
|
|
if (last == null || EndOf(last).DistanceTo(StartOf(entity)) > Tolerance.Epsilon)
|
|
breakCuts.Add(new Shape());
|
|
breakCuts[^1].Entities.Add(entity);
|
|
}
|
|
|
|
var shapes = ShapeBuilder.GetShapes(contours);
|
|
|
|
if (shapes.Count == 0)
|
|
return null;
|
|
|
|
var perimeter = shapes[0];
|
|
var area = perimeter.BoundingBox.Area();
|
|
var index = 0;
|
|
|
|
for (int i = 1; i < shapes.Count; ++i)
|
|
{
|
|
var program = shapes[i];
|
|
var area2 = program.BoundingBox.Area();
|
|
if (area2 > area)
|
|
{
|
|
perimeter = program;
|
|
area = area2;
|
|
index = i;
|
|
}
|
|
}
|
|
|
|
shapes.RemoveAt(index);
|
|
|
|
var pgm = new Program();
|
|
|
|
foreach (var cut in breakCuts)
|
|
pgm.Merge(ToProgram(cut));
|
|
|
|
foreach (var shape in shapes)
|
|
{
|
|
var subpgm = ToProgram(shape);
|
|
pgm.Merge(subpgm);
|
|
}
|
|
|
|
pgm.Merge(ToProgram(perimeter));
|
|
pgm.Mode = Mode.Incremental;
|
|
|
|
return pgm;
|
|
}
|
|
|
|
private static Vector StartOf(Entity entity) => entity is Arc arc ? arc.StartPoint() : ((Line)entity).StartPoint;
|
|
|
|
private static Vector EndOf(Entity entity) => entity is Arc arc ? arc.EndPoint() : ((Line)entity).EndPoint;
|
|
|
|
public static Program ToProgram(Shape shape)
|
|
{
|
|
var pgm = new Program();
|
|
var lastpt = new Vector();
|
|
|
|
for (int i = 0; i < shape.Entities.Count; i++)
|
|
lastpt = AddEntity(pgm, lastpt, shape.Entities[i]);
|
|
|
|
return pgm;
|
|
}
|
|
|
|
private static Vector AddEntity(Program pgm, Vector lastpt, Entity geo)
|
|
{
|
|
switch (geo.Type)
|
|
{
|
|
case EntityType.Arc:
|
|
lastpt = AddArc(pgm, lastpt, (Arc)geo);
|
|
break;
|
|
|
|
case EntityType.Circle:
|
|
lastpt = AddCircle(pgm, lastpt, (Circle)geo);
|
|
break;
|
|
|
|
case EntityType.Line:
|
|
lastpt = AddLine(pgm, lastpt, (Line)geo);
|
|
break;
|
|
}
|
|
|
|
return lastpt;
|
|
}
|
|
|
|
private static Vector AddArc(Program pgm, Vector lastpt, Arc arc)
|
|
{
|
|
var startpt = arc.StartPoint();
|
|
var endpt = arc.EndPoint();
|
|
|
|
if (startpt.DistanceTo(lastpt) > Tolerance.ChainTolerance)
|
|
pgm.MoveTo(startpt);
|
|
|
|
lastpt = endpt;
|
|
|
|
var layer = ClassifyLayer(arc);
|
|
var sweep = System.Math.Abs(arc.SweepAngle());
|
|
if (sweep < Tolerance.Epsilon || sweep.IsEqualTo(Angle.TwoPI))
|
|
{
|
|
pgm.Codes.Add(new LinearMove(endpt) { Layer = layer });
|
|
}
|
|
else
|
|
{
|
|
pgm.Codes.Add(
|
|
new ArcMove(
|
|
endpt,
|
|
arc.Center,
|
|
arc.IsReversed ? RotationType.CW : RotationType.CCW
|
|
)
|
|
{
|
|
Layer = layer,
|
|
}
|
|
);
|
|
}
|
|
|
|
return lastpt;
|
|
}
|
|
|
|
private static Vector AddCircle(Program pgm, Vector lastpt, Circle circle)
|
|
{
|
|
var startpt = new Vector(circle.Center.X + circle.Radius, circle.Center.Y);
|
|
|
|
if (startpt.DistanceTo(lastpt) > Tolerance.ChainTolerance)
|
|
pgm.MoveTo(startpt);
|
|
|
|
pgm.Codes.Add(
|
|
new ArcMove(startpt, circle.Center, circle.Rotation)
|
|
{
|
|
Layer = ClassifyLayer(circle),
|
|
}
|
|
);
|
|
|
|
lastpt = startpt;
|
|
return lastpt;
|
|
}
|
|
|
|
private static Vector AddLine(Program pgm, Vector lastpt, Line line)
|
|
{
|
|
if (line.StartPoint.DistanceTo(lastpt) > Tolerance.ChainTolerance)
|
|
pgm.MoveTo(line.StartPoint);
|
|
|
|
pgm.Codes.Add(new LinearMove(line.EndPoint) { Layer = ClassifyLayer(line) });
|
|
|
|
lastpt = line.EndPoint;
|
|
return lastpt;
|
|
}
|
|
|
|
// Engrave/etch/scribe geometry maps to Scribe so the post processor can treat it as a
|
|
// separate tool pass; everything else keeps the move's default Cut layer. SCRIBE is the
|
|
// name marks carry once saved (SpecialLayers.Scribe), so drawings rebuilt from stored
|
|
// entities must map it too or their marks silently become cut moves.
|
|
private static LayerType ClassifyLayer(Entity geo)
|
|
{
|
|
var name = geo.Layer?.Name;
|
|
if (
|
|
string.Equals(name, "ENGRAVE", System.StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(name, "ETCH", System.StringComparison.OrdinalIgnoreCase)
|
|
|| string.Equals(name, SpecialLayers.Scribe.Name, System.StringComparison.OrdinalIgnoreCase)
|
|
)
|
|
return LayerType.Scribe;
|
|
|
|
if (string.Equals(name, SpecialLayers.Break.Name, System.StringComparison.OrdinalIgnoreCase))
|
|
return LayerType.Break;
|
|
|
|
if (string.Equals(name, SpecialLayers.Tab.Name, System.StringComparison.OrdinalIgnoreCase))
|
|
return LayerType.Tab;
|
|
|
|
if (string.Equals(name, SpecialLayers.DisplayPerimeter.Name, System.StringComparison.OrdinalIgnoreCase))
|
|
return LayerType.DisplayPerimeter;
|
|
|
|
return LayerType.Cut;
|
|
}
|
|
}
|
|
}
|