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>
94 lines
2.6 KiB
C#
94 lines
2.6 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace OpenNest.Geometry
|
|
{
|
|
public class ShapeProfile
|
|
{
|
|
public ShapeProfile(Shape shape)
|
|
{
|
|
Update(shape.Entities);
|
|
}
|
|
|
|
public ShapeProfile(List<Entity> entities)
|
|
{
|
|
Update(entities);
|
|
}
|
|
|
|
private void Update(List<Entity> entities)
|
|
{
|
|
var shapes = ShapeBuilder.GetShapes(entities);
|
|
|
|
Perimeter = shapes[0];
|
|
Cutouts = new List<Shape>();
|
|
|
|
for (var i = 1; i < shapes.Count; i++)
|
|
{
|
|
var bb = shapes[i].BoundingBox;
|
|
var perimBB = Perimeter.BoundingBox;
|
|
|
|
if (bb.Width * bb.Length > perimBB.Width * perimBB.Length)
|
|
{
|
|
Cutouts.Add(Perimeter);
|
|
Perimeter = shapes[i];
|
|
}
|
|
else
|
|
{
|
|
Cutouts.Add(shapes[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|