mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-03 17:01:38 -05:00
The Drawing Library Grid check found seven drawings with 1,000-1,700 holes (90-6138-26/27, Reeves bakery vent pans, 4938 A15) that took 0.5-3.6 s to preview, all of it in the part label point. - PolyLabel skips a hole's containment test outside its bounds and its edge distances when its bounds are farther than the nearest edge so far. Both skips leave the result exactly as before; label points on all seven drawings are unchanged and the search is 4-35x faster. - ShapeBuilder.GetShapes rescanned every remaining entity at each chaining step. An endpoint grid now finds the candidates, tested in list order with the same checks, so chosen entities, reversals and shapes are identical (compared against the old loop on the library drawings and 300 random entity soups). 90-6138: 700 ms -> 40 ms. - The label builds its shapes once: ShapeProfile.FromShapes reuses the ones it already chained instead of chaining them again. Previews of all seven now take 57-166 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
3.5 KiB
C#
113 lines
3.5 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 ShapeProfile()
|
|
{
|
|
}
|
|
|
|
/// <summary>A profile of shapes already built, for callers that need them too; at least one.</summary>
|
|
public static ShapeProfile FromShapes(List<Shape> shapes)
|
|
{
|
|
var profile = new ShapeProfile();
|
|
profile.Update(shapes);
|
|
return profile;
|
|
}
|
|
|
|
private void Update(List<Entity> entities) => Update(ShapeBuilder.GetShapes(entities));
|
|
|
|
private void Update(List<Shape> shapes)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|