Files
OpenNest/OpenNest.Core/Geometry/ShapeProfile.cs
T
ajandClaude Opus 5.5 6f555345e3 feat(cad): cut breakoff joining lines as tabbed open break cuts
Breakoffs are now picked by their joining line, default to the smaller side as scrap
(flippable), and cut that line between tabs on a new Break layer instead of slots.
Plan Cutting and Assign Lead-ins enter each cut from the scrap with the nest's internal
lead-in, or pierce on the line without one; checks treat break cuts as intentional open
cuts that are not material.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:46:35 -04:00

101 lines
3.1 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 cloned = entities.CloneAll();
// Break cuts are open and their direction says which side is scrap: never re-chain or
// re-wind them. They follow the contours, in their original order.
var breakCuts = cloned.Where(e => e.Layer == SpecialLayers.Break).ToList();
if (breakCuts.Count > 0)
cloned.RemoveAll(e => e.Layer == SpecialLayers.Break);
var profile = new ShapeProfile(cloned);
var result = profile.ToNormalizedEntities();
result.AddRange(breakCuts);
return result;
}
private static void EnsureWinding(Shape shape, RotationType desired)
{
var poly = shape.ToPolygon();
if (poly != null && poly.Vertices.Count >= 3 && poly.RotationDirection() != desired)
{
shape.Reverse();
}
}
}
}