diff --git a/OpenNest.Benchmark/NestValidator.cs b/OpenNest.Benchmark/NestValidator.cs index 934a835..4b0b276 100644 --- a/OpenNest.Benchmark/NestValidator.cs +++ b/OpenNest.Benchmark/NestValidator.cs @@ -31,11 +31,34 @@ namespace OpenNest.Benchmark ValidateQuantities(parts, job, result); ValidateBounds(parts, plate, result); + ValidateAreaBudget(parts, plate, result); ValidateSpacing(parts, plate.PartSpacing, result); return result; } + /// + /// Hard mathematical backstop: non-overlapping parts confined to the work + /// area can never have a combined area greater than the work area itself. + /// This catches overlap that the polygon-based ValidateSpacing check can + /// miss - Collision.HasOverlap (and Part.Intersects, which uses the same + /// algorithm) has been observed to return false negatives on real, + /// complex production geometry, so this check does not depend on it. + /// + private static void ValidateAreaBudget(List parts, Plate plate, ValidationResult result) + { + var workArea = plate.WorkArea(); + var budget = workArea.Width * workArea.Length; + var placedArea = parts.Sum(p => p.BaseDrawing.Area); + + if (placedArea > budget + Tolerance.Epsilon) + { + result.Violations.Add( + $"Combined placed area ({placedArea:F2}) exceeds the work area ({budget:F2}) - " + + "parts must overlap even though the polygon overlap check did not flag a pair"); + } + } + private static void ValidateQuantities(List parts, BenchmarkJob job, ValidationResult result) { var allowed = job.Requests.ToDictionary(r => r.Drawing.Id, r => r.Quantity); @@ -132,7 +155,10 @@ namespace OpenNest.Benchmark if (inflateBy > Tolerance.Epsilon) perimeter = perimeter.OffsetOutward(inflateBy) ?? perimeter; - var polygon = perimeter.ToPolygon(); + // Adaptive tolerance instead of Shape.ToPolygon()'s default (up to 1000 + // segments per arc) - arc-heavy real parts otherwise produce thousands + // of vertices, which is needlessly slow for a spacing check. + var polygon = perimeter.ToPolygonWithTolerance(0.01, circumscribe: true); if (polygon == null) return null;