using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using OpenNest.Engine; using OpenNest.Engine.BestFit; using OpenNest.Engine.Fill; using OpenNest.Engine.Jobs.Placement.Fillers; using OpenNest.Engine.Strategies; using OpenNest.Geometry; using OpenNest.Math; namespace OpenNest.Engine { public abstract class NestEngineBase { protected NestEngineBase(Plate plate) { Plate = plate; } public Plate Plate { get; set; } public int PlateNumber { get; set; } public NestDirection NestDirection { get; set; } private readonly List phaseResults = new(); private readonly List angleResults = new(); public virtual NestPhase WinnerPhase { get; protected set; } public virtual List PhaseResults => phaseResults; public virtual List AngleResults => angleResults; public abstract string Name { get; } public abstract string Description { get; } // --- Engine policy --- private IFillComparer _comparer; protected IFillComparer Comparer => _comparer ??= CreateComparer(); protected virtual IFillComparer CreateComparer() => new DefaultFillComparer(); public virtual NestDirection? PreferredDirection => null; public virtual ShrinkAxis TrimAxis => ShrinkAxis.Width; public virtual List BuildAngles( NestItem item, ClassificationResult classification, Box workArea ) { return new List { classification.PrimaryAngle, classification.PrimaryAngle + OpenNest.Math.Angle.HalfPI, }; } protected virtual void RecordProductiveAngles(List angleResults) { } protected FillPolicy BuildPolicy() => new FillPolicy(Comparer, PreferredDirection); // --- Virtual methods (side-effect-free, return parts) --- public virtual List Fill( NestItem item, Box workArea, IProgress progress, CancellationToken token ) { return new List(); } public virtual List Fill( List groupParts, Box workArea, IProgress progress, CancellationToken token ) { return new List(); } public virtual List PackArea( Box box, List items, IProgress progress, CancellationToken token ) { return new List(); } // --- Nest: compatibility façade over shared single-plate orchestration --- public virtual List Nest( List items, IProgress progress, CancellationToken token ) { return PlateFillOrchestrator.Nest( Plate, items, Comparer, (item, workArea, sink, cancellation) => FillExact(item, workArea, sink, cancellation), (workArea, packItems, sink, cancellation) => PackArea(workArea, packItems, sink, cancellation), progress, token ); } // --- FillExact (non-virtual, delegates to virtual Fill) --- public List FillExact( NestItem item, Box workArea, IProgress progress, CancellationToken token ) { return Fill(item, workArea, progress, token); } // --- Convenience overloads (mutate plate, return bool) --- public bool Fill(NestItem item) { return Fill(item, Plate.WorkArea()); } public bool Fill(NestItem item, Box workArea) { var parts = Fill(item, workArea, null, CancellationToken.None); if (parts == null || parts.Count == 0) return false; Plate.Parts.AddRange(parts); return true; } public bool Fill(List groupParts) { return Fill(groupParts, Plate.WorkArea()); } public bool Fill(List groupParts, Box workArea) { var parts = Fill(groupParts, workArea, null, CancellationToken.None); if (parts == null || parts.Count == 0) return false; Plate.Parts.AddRange(parts); return true; } public bool Pack(List items) { var workArea = Plate.WorkArea(); var parts = PackArea(workArea, items, null, CancellationToken.None); if (parts == null || parts.Count == 0) return false; Plate.Parts.AddRange(parts); return true; } // --- Protected utilities --- internal static void ReportProgress(IProgress progress, ProgressReport report) { NestProgressReporter.Report(progress, report); } protected string BuildProgressSummary() => PlateFillerBase.BuildProgressSummary(PhaseResults); protected bool IsBetterFill(List candidate, List current, Box workArea) => PlateFillerBase.IsBetterFill(Comparer, candidate, current, workArea); protected bool IsBetterValidFill(List candidate, List current, Box workArea) { if ( candidate != null && candidate.Count > 0 && HasOverlaps(candidate, Plate.PartSpacing) ) { Debug.WriteLine( $"[IsBetterValidFill] REJECTED {candidate.Count} parts due to overlaps (current best: {current?.Count ?? 0})" ); return false; } return IsBetterFill(candidate, current, workArea); } protected static bool HasOverlaps(List parts, double spacing) => PlateFillerBase.HasOverlaps(parts, spacing); } }