diff --git a/OpenNest.Engine/ML/BruteForceRunner.cs b/OpenNest.Engine/ML/BruteForceRunner.cs new file mode 100644 index 0000000..c3d8c59 --- /dev/null +++ b/OpenNest.Engine/ML/BruteForceRunner.cs @@ -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 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 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 parts, double plateArea) + { + if (plateArea <= 0) return 0; + return parts.Sum(p => p.BaseDrawing.Area) / plateArea; + } + } +} diff --git a/OpenNest.Engine/ML/FeatureExtractor.cs b/OpenNest.Engine/ML/FeatureExtractor.cs new file mode 100644 index 0000000..640c723 --- /dev/null +++ b/OpenNest.Engine/ML/FeatureExtractor.cs @@ -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; + } + } +}