using OpenNest.Engine.Fill; using OpenNest.Geometry; using OpenNest.Math; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; namespace OpenNest.Engine.Strategies { public static class FillHelpers { public static Pattern BuildRotatedPattern(List groupParts, double angle) { var pattern = new Pattern(); var center = ((IEnumerable)groupParts).GetBoundingBox().Center; foreach (var part in groupParts) { var clone = (Part)part.Clone(); clone.UpdateBounds(); if (!angle.IsEqualTo(0)) clone.Rotate(angle, center); pattern.Parts.Add(clone); } pattern.UpdateBounds(); return pattern; } public static List FillPattern(FillLinear engine, List groupParts, List angles, Box workArea, IFillComparer comparer = null) { var results = new ConcurrentBag<(List Parts, FillScore Score)>(); Parallel.ForEach(angles, angle => { var pattern = BuildRotatedPattern(groupParts, angle); if (pattern.Parts.Count == 0) return; var h = engine.Fill(pattern, NestDirection.Horizontal); if (h != null && h.Count > 0) results.Add((h, FillScore.Compute(h, workArea))); var v = engine.Fill(pattern, NestDirection.Vertical); if (v != null && v.Count > 0) results.Add((v, FillScore.Compute(v, workArea))); }); List best = null; var bestScore = default(FillScore); foreach (var res in results) { if (comparer != null) { if (best == null || comparer.IsBetter(res.Parts, best, workArea)) best = res.Parts; } else { if (best == null || res.Score > bestScore) { best = res.Parts; bestScore = res.Score; } } } return best; } /// /// Runs a fill function with direction preference logic. /// If preferred is null, tries both directions and returns the better result. /// If preferred is set, tries preferred first; only tries other if preferred yields zero. /// public static List FillWithDirectionPreference( Func> fillFunc, NestDirection? preferred, IFillComparer comparer, Box workArea) { if (preferred == null) { var h = fillFunc(NestDirection.Horizontal); var v = fillFunc(NestDirection.Vertical); if ((h == null || h.Count == 0) && (v == null || v.Count == 0)) return new List(); if (h == null || h.Count == 0) return v; if (v == null || v.Count == 0) return h; return comparer.IsBetter(h, v, workArea) ? h : v; } var other = preferred == NestDirection.Horizontal ? NestDirection.Vertical : NestDirection.Horizontal; var pref = fillFunc(preferred.Value); if (pref != null && pref.Count > 0) return pref; var fallback = fillFunc(other); return fallback ?? new List(); } /// /// Sweeps a list of angles, calling fillAtAngle for each, and returns /// the best result according to the context's comparer. Handles /// cancellation and progress reporting. /// public static List BestOverAngles( FillContext context, IReadOnlyList angles, Func> fillAtAngle, NestPhase phase, string phaseLabel) { var workArea = context.WorkArea; var comparer = context.Policy?.Comparer ?? new DefaultFillComparer(); List best = null; for (var i = 0; i < angles.Count; i++) { context.Token.ThrowIfCancellationRequested(); var angle = angles[i]; var result = fillAtAngle(angle); var angleDeg = Angle.ToDegrees(angle); if (result != null && result.Count > 0) { if (best == null || comparer.IsBetter(result, best, workArea)) best = result; } NestEngineBase.ReportProgress(context.Progress, new ProgressReport { Phase = phase, PlateNumber = context.PlateNumber, Parts = best, WorkArea = workArea, Description = $"{phaseLabel}: {i + 1}/{angles.Count} angles, {angleDeg:F0}° best = {best?.Count ?? 0} parts", }); } return best ?? new List(); } /// /// Checks if any pair of parts geometrically overlap. Uses bounding box /// pre-filtering for performance, then falls back to shape intersection. /// internal static bool HasOverlappingParts(List parts) { for (var i = 0; i < parts.Count; i++) { var b1 = parts[i].BoundingBox; for (var j = i + 1; j < parts.Count; j++) { var b2 = parts[j].BoundingBox; var overlapX = System.Math.Min(b1.Right, b2.Right) - System.Math.Max(b1.Left, b2.Left); var overlapY = System.Math.Min(b1.Top, b2.Top) - System.Math.Max(b1.Bottom, b2.Bottom); if (overlapX <= Tolerance.Epsilon || overlapY <= Tolerance.Epsilon) continue; if (parts[i].Intersects(parts[j], out _)) return true; } } return false; } } }