Every nesting-geometry consumer filtered only rapids, so scribe/etch moves counted as part material. An etch tick that ends a hair outside the outline (PEP bend ticks start on the notch edge) made the part "open geometry leaving the material region": the job validator threw and every built-in engine plus Gpt6Astra crashed on real PEP jobs (PT75, drawing 4980 A01 PT77). Marks are only on the surface, so they should never affect placement, collision, area, or validation. - SpecialLayers.IsMaterial excludes Rapid and Scribe; used by drawing area, canonical angle, part collision, PartGeometry, plate perimeter, best-fit/pair evaluation, rotation analysis, GPU evaluators, and both validators. Timing, display, splitting and posts still see marks. - ConvertGeometry also maps the saved SCRIBE layer name to Scribe, so programs rebuilt from stored entities keep their marks. - NestReader repairs older files (e.g. PepNestExport output) whose programs saved etch as cut moves while source entities kept SCRIBE. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
114 lines
3.6 KiB
C#
114 lines
3.6 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using OpenNest.Converters;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.Math;
|
|
|
|
namespace OpenNest.Engine
|
|
{
|
|
public enum PartType
|
|
{
|
|
Rectangle,
|
|
Circle,
|
|
Irregular,
|
|
}
|
|
|
|
public struct ClassificationResult
|
|
{
|
|
public PartType Type;
|
|
public double Rectangularity;
|
|
public double Circularity;
|
|
public double PerimeterRatio;
|
|
public double PrimaryAngle;
|
|
}
|
|
|
|
public static class PartClassifier
|
|
{
|
|
public const double RectangularityThreshold = 0.92;
|
|
public const double PerimeterRatioThreshold = 0.85;
|
|
public const double CircularityThreshold = 0.95;
|
|
|
|
public static ClassificationResult Classify(Drawing drawing)
|
|
{
|
|
var result = new ClassificationResult { Type = PartType.Irregular };
|
|
|
|
var entities = ConvertProgram
|
|
.ToGeometry(drawing.Program)
|
|
.Where(e => SpecialLayers.IsMaterial(e.Layer));
|
|
|
|
var shapes = ShapeBuilder.GetShapes(entities);
|
|
|
|
if (shapes.Count == 0)
|
|
return result;
|
|
|
|
// Find the largest shape (outer perimeter).
|
|
var perimeter = shapes[0];
|
|
var perimeterArea = perimeter.Area();
|
|
|
|
for (var i = 1; i < shapes.Count; i++)
|
|
{
|
|
var area = shapes[i].Area();
|
|
if (area > perimeterArea)
|
|
{
|
|
perimeter = shapes[i];
|
|
perimeterArea = area;
|
|
}
|
|
}
|
|
|
|
// Convert to polygon for hull/MBR computation.
|
|
var polygon = perimeter.ToPolygonWithTolerance(0.1);
|
|
|
|
if (polygon == null || polygon.Vertices.Count < 3)
|
|
return result;
|
|
|
|
// Compute convex hull.
|
|
var hull = ConvexHull.Compute(polygon.Vertices);
|
|
var hullArea = hull.Area();
|
|
|
|
// Compute MBR via rotating calipers.
|
|
var mbr = RotatingCalipers.MinimumBoundingRectangle(hull);
|
|
var mbrArea = mbr.Area;
|
|
var mbrPerimeter = 2 * (mbr.Width + mbr.Height);
|
|
|
|
// Share the single angle formula with CanonicalAngle (no duplicate MBR compute).
|
|
result.PrimaryAngle = CanonicalAngle.FromMbr(mbr);
|
|
|
|
// Drawing perimeter for circularity and perimeter ratio.
|
|
var drawingPerimeter = polygon.Perimeter();
|
|
|
|
// Circularity: 4*PI*area / perimeter^2. Circles ~ 1.0.
|
|
if (drawingPerimeter > Tolerance.Epsilon)
|
|
result.Circularity =
|
|
4 * System.Math.PI * perimeterArea / (drawingPerimeter * drawingPerimeter);
|
|
|
|
// Check circle first (rotationally invariant).
|
|
if (result.Circularity >= CircularityThreshold)
|
|
{
|
|
result.Type = PartType.Circle;
|
|
return result;
|
|
}
|
|
|
|
// Rectangularity: hull area / MBR area.
|
|
if (mbrArea > Tolerance.Epsilon)
|
|
result.Rectangularity = hullArea / mbrArea;
|
|
|
|
// Perimeter ratio: MBR perimeter / drawing perimeter.
|
|
if (drawingPerimeter > Tolerance.Epsilon)
|
|
result.PerimeterRatio = mbrPerimeter / drawingPerimeter;
|
|
|
|
// Rectangle: both metrics pass thresholds.
|
|
if (
|
|
result.Rectangularity >= RectangularityThreshold
|
|
&& result.PerimeterRatio >= PerimeterRatioThreshold
|
|
)
|
|
{
|
|
result.Type = PartType.Rectangle;
|
|
return result;
|
|
}
|
|
|
|
result.Type = PartType.Irregular;
|
|
return result;
|
|
}
|
|
}
|
|
}
|