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:
@@ -598,6 +598,41 @@ namespace OpenNest.Geometry
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offsets the shape inward by the given distance.
|
||||
/// Normalizes to CCW winding before offsetting Left (which is inward for CCW),
|
||||
/// making the method independent of the original contour winding direction.
|
||||
/// </summary>
|
||||
public Shape OffsetInward(double distance)
|
||||
{
|
||||
var poly = ToPolygon();
|
||||
|
||||
if (poly == null || poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CCW)
|
||||
return OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
|
||||
// Create a reversed copy to avoid mutating shared entity objects.
|
||||
var copy = new Shape();
|
||||
|
||||
for (var i = Entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
switch (Entities[i])
|
||||
{
|
||||
case Line l:
|
||||
copy.Entities.Add(new Line(l.EndPoint, l.StartPoint) { Layer = l.Layer });
|
||||
break;
|
||||
case Arc a:
|
||||
copy.Entities.Add(new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed) { Layer = a.Layer });
|
||||
break;
|
||||
case Circle c:
|
||||
copy.Entities.Add(new Circle(c.Center, c.Radius) { Layer = c.Layer });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return copy.OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest point on the shape to the given point.
|
||||
/// </summary>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user