feat(engine): add ML feature extraction and brute-force runner
Add FeatureExtractor for computing geometric part features (convexity, aspect ratio, circularity, bitmask) and BruteForceRunner for generating training data by running the fill engine and recording results. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
|
namespace OpenNest.Engine.ML
|
||||||
|
{
|
||||||
|
public class BruteForceResult
|
||||||
|
{
|
||||||
|
public int PartCount { get; set; }
|
||||||
|
public double Utilization { get; set; }
|
||||||
|
public long TimeMs { get; set; }
|
||||||
|
public string LayoutData { get; set; }
|
||||||
|
public List<Part> PlacedParts { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class BruteForceRunner
|
||||||
|
{
|
||||||
|
public static BruteForceResult Run(Drawing drawing, Plate plate)
|
||||||
|
{
|
||||||
|
var engine = new NestEngine(plate);
|
||||||
|
var item = new NestItem { Drawing = drawing };
|
||||||
|
|
||||||
|
var sw = Stopwatch.StartNew();
|
||||||
|
var parts = engine.Fill(item, plate.WorkArea(), null, System.Threading.CancellationToken.None);
|
||||||
|
sw.Stop();
|
||||||
|
|
||||||
|
if (parts == null || parts.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new BruteForceResult
|
||||||
|
{
|
||||||
|
PartCount = parts.Count,
|
||||||
|
Utilization = CalculateUtilization(parts, plate.Area()),
|
||||||
|
TimeMs = sw.ElapsedMilliseconds,
|
||||||
|
LayoutData = SerializeLayout(parts),
|
||||||
|
PlacedParts = parts
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string SerializeLayout(List<Part> parts)
|
||||||
|
{
|
||||||
|
var data = parts.Select(p => new { X = p.Location.X, Y = p.Location.Y, R = p.Rotation }).ToList();
|
||||||
|
return System.Text.Json.JsonSerializer.Serialize(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateUtilization(List<Part> parts, double plateArea)
|
||||||
|
{
|
||||||
|
if (plateArea <= 0) return 0;
|
||||||
|
return parts.Sum(p => p.BaseDrawing.Area) / plateArea;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
|
namespace OpenNest.Engine.ML
|
||||||
|
{
|
||||||
|
public class PartFeatures
|
||||||
|
{
|
||||||
|
// --- Geometric Features ---
|
||||||
|
public double Area { get; set; }
|
||||||
|
public double Convexity { get; set; } // Area / Convex Hull Area
|
||||||
|
public double AspectRatio { get; set; } // Width / Length
|
||||||
|
public double BoundingBoxFill { get; set; } // Area / (Width * Length)
|
||||||
|
public double Circularity { get; set; } // 4 * PI * Area / Perimeter^2
|
||||||
|
public int VertexCount { get; set; }
|
||||||
|
|
||||||
|
// --- Normalized Bitmask (32x32 = 1024 features) ---
|
||||||
|
public byte[] Bitmask { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{Area:F2},{Convexity:F4},{AspectRatio:F4},{BoundingBoxFill:F4},{Circularity:F4},{VertexCount}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FeatureExtractor
|
||||||
|
{
|
||||||
|
public static PartFeatures Extract(Drawing drawing)
|
||||||
|
{
|
||||||
|
var entities = OpenNest.Converters.ConvertProgram.ToGeometry(drawing.Program)
|
||||||
|
.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var profile = new ShapeProfile(entities);
|
||||||
|
var perimeter = profile.Perimeter;
|
||||||
|
|
||||||
|
if (perimeter == null) return null;
|
||||||
|
|
||||||
|
var polygon = perimeter.ToPolygonWithTolerance(0.01);
|
||||||
|
polygon.UpdateBounds();
|
||||||
|
var bb = polygon.BoundingBox;
|
||||||
|
|
||||||
|
var hull = ConvexHull.Compute(polygon.Vertices);
|
||||||
|
var hullArea = hull.Area();
|
||||||
|
|
||||||
|
var features = new PartFeatures
|
||||||
|
{
|
||||||
|
Area = drawing.Area,
|
||||||
|
Convexity = drawing.Area / (hullArea > 0 ? hullArea : 1.0),
|
||||||
|
AspectRatio = bb.Width / (bb.Length > 0 ? bb.Length : 1.0),
|
||||||
|
BoundingBoxFill = drawing.Area / (bb.Area() > 0 ? bb.Area() : 1.0),
|
||||||
|
VertexCount = polygon.Vertices.Count,
|
||||||
|
Bitmask = GenerateBitmask(polygon, 32)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Circularity = 4 * PI * Area / Perimeter^2
|
||||||
|
var perimeterLen = polygon.Perimeter();
|
||||||
|
features.Circularity = (4 * System.Math.PI * drawing.Area) / (perimeterLen * perimeterLen);
|
||||||
|
|
||||||
|
return features;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] GenerateBitmask(Polygon polygon, int size)
|
||||||
|
{
|
||||||
|
var mask = new byte[size * size];
|
||||||
|
polygon.UpdateBounds();
|
||||||
|
var bb = polygon.BoundingBox;
|
||||||
|
|
||||||
|
for (int y = 0; y < size; y++)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < size; x++)
|
||||||
|
{
|
||||||
|
// Map grid coordinate (0..size) to bounding box coordinate
|
||||||
|
var px = bb.Left + (x + 0.5) * (bb.Width / size);
|
||||||
|
var py = bb.Bottom + (y + 0.5) * (bb.Length / size);
|
||||||
|
|
||||||
|
if (polygon.ContainsPoint(new Vector(px, py)))
|
||||||
|
{
|
||||||
|
mask[y * size + x] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user