feat: add etch mark entities from bend lines to CNC program pipeline

Etch marks for up bends are now real geometry entities on an ETCH layer
instead of being drawn dynamically. They flow through the full pipeline:
entities → FilterPanel layers → ConvertGeometry (tagged as Scribe) →
post-processor sequencing before cut geometry.

Also includes ShapeProfile normalization (CW perimeter, CCW cutouts)
applied consistently across all import paths, and inward offset support
for cutout shapes in overlap/offset polygon calculations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 00:42:49 -04:00
parent 80e8693da3
commit 2db8c49838
15 changed files with 306 additions and 147 deletions

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.Geometry
{
@@ -41,5 +42,52 @@ namespace OpenNest.Geometry
public Shape Perimeter { get; set; }
public List<Shape> Cutouts { get; set; }
/// <summary>
/// Ensures CNC-standard winding: perimeter CW (kerf left = outward),
/// cutouts CCW (kerf left = inward). Reverses contours in-place as needed.
/// </summary>
public void NormalizeWinding()
{
EnsureWinding(Perimeter, RotationType.CW);
foreach (var cutout in Cutouts)
EnsureWinding(cutout, RotationType.CCW);
}
/// <summary>
/// Returns the entities in normalized winding order (perimeter first, then cutouts).
/// </summary>
public List<Entity> ToNormalizedEntities()
{
NormalizeWinding();
var result = new List<Entity>(Perimeter.Entities);
foreach (var cutout in Cutouts)
result.AddRange(cutout.Entities);
return result;
}
/// <summary>
/// Convenience method: builds a ShapeProfile from raw entities,
/// normalizes winding, and returns the corrected entity list.
/// </summary>
public static List<Entity> NormalizeEntities(IEnumerable<Entity> entities)
{
var profile = new ShapeProfile(entities.ToList());
return profile.ToNormalizedEntities();
}
private static void EnsureWinding(Shape shape, RotationType desired)
{
var poly = shape.ToPolygon();
if (poly != null && poly.Vertices.Count >= 3
&& poly.RotationDirection() != desired)
{
shape.Reverse();
}
}
}
}