Files
OpenNest/OpenNest.Core/Converters/ConvertGeometry.cs
T
ajandClaude Opus 5.5 1b5e1b14a6 fix: leave etch/scribe marks out of nesting geometry
Every nesting-geometry consumer filtered only rapids, so scribe/etch
moves counted as part material. An etch tick that ends a hair outside
the outline (PEP bend ticks start on the notch edge) made the part
"open geometry leaving the material region": the job validator threw
and every built-in engine plus Gpt6Astra crashed on real PEP jobs
(PT75, drawing 4980 A01 PT77). Marks are only on the surface, so they
should never affect placement, collision, area, or validation.

- SpecialLayers.IsMaterial excludes Rapid and Scribe; used by drawing
  area, canonical angle, part collision, PartGeometry, plate perimeter,
  best-fit/pair evaluation, rotation analysis, GPU evaluators, and both
  validators. Timing, display, splitting and posts still see marks.
- ConvertGeometry also maps the saved SCRIBE layer name to Scribe, so
  programs rebuilt from stored entities keep their marks.
- NestReader repairs older files (e.g. PepNestExport output) whose
  programs saved etch as cut moves while source entities kept SCRIBE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 06:44:29 -04:00

160 lines
4.8 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)
{
var shapes = ShapeBuilder.GetShapes(geometry);
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 shape in shapes)
{
var subpgm = ToProgram(shape);
pgm.Merge(subpgm);
}
pgm.Merge(ToProgram(perimeter));
pgm.Mode = Mode.Incremental;
return pgm;
}
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;
return LayerType.Cut;
}
}
}