using System; using System.Collections.Generic; using System.Linq; using OpenNest.CNC; using OpenNest.CNC.CuttingPlanning; using OpenNest.CNC.CuttingStrategy; using OpenNest.Diagnostics; using OpenNest.Geometry; namespace OpenNest.Engine.CuttingPlanning; /// /// Caller-side direct-XY input. Regeneration requires explicit confirmed parameters. /// Keep sources/settings stable during Capture. Only plate-scoped requests () /// record the state that CuttingPlanService.Apply checks; a detached part list cannot be applied. /// public sealed class CuttingPlanRequest { public CuttingPlanRequest(IEnumerable parts, Vector startPoint = default, int expansionBudget = 20000, CuttingParameters confirmedParameters = null, IEnumerable eligibleParts = null, bool preservePartOrder = false, int maxEntries = 16) { Parts = parts == null ? null : Array.AsReadOnly(parts.ToArray()); StartPoint = startPoint; ExpansionBudget = expansionBudget; ConfirmedParameters = confirmedParameters; EligibleParts = eligibleParts == null ? null : Array.AsReadOnly(eligibleParts.ToArray()); PreservePartOrder = preservePartOrder; MaxEntries = maxEntries; } /// /// Plate scope: plans the plate's current parts and captures its exact state, so a Ready /// result can later be applied through CuttingPlanService.Apply. A factory rather than a /// constructor overload, so new CuttingPlanRequest(null) stays unambiguous. /// public static CuttingPlanRequest ForPlate(Plate plate, Vector startPoint = default, int expansionBudget = 20000, CuttingParameters confirmedParameters = null, IEnumerable eligibleParts = null, bool preservePartOrder = false, int maxEntries = 16) => new(plate?.Parts, startPoint, expansionBudget, confirmedParameters, eligibleParts, preservePartOrder, maxEntries) { Plate = plate }; /// The plate scope, or null for a detached part list that cannot be applied. public Plate Plate { get; private init; } public CuttingParameters ConfirmedParameters { get; } public IReadOnlyList EligibleParts { get; } public bool PreservePartOrder { get; } public int MaxEntries { get; } internal Action ExpansionObserver { get; init; } public IReadOnlyList Parts { get; } public Vector StartPoint { get; } public int ExpansionBudget { get; } } /// Owned worker input. SourcePart references are identity handles only, never worker data. public sealed class CuttingPlanSnapshot { internal CuttingPlanSnapshot(IEnumerable placements, Vector startPoint, int expansionBudget, CuttingPlanStatus? failure = null, IEnumerable findings = null, bool regeneration = false, bool preservePartOrder = false, int maxEntries = 16, Action expansionObserver = null, PlateCuttingState plateState = null, CuttingParameters ownedParameters = null, CuttingDependencyGraph dependencies = null) { PlateState = plateState; OwnedParameters = ownedParameters; Placements = Array.AsReadOnly(placements.ToArray()); Dependencies = dependencies ?? CuttingDependencyGraph.Empty(Placements.Count); StartPoint = startPoint; ExpansionBudget = expansionBudget; Failure = failure; Findings = Array.AsReadOnly((findings ?? []).ToArray()); Regeneration = regeneration; PreservePartOrder = preservePartOrder; MaxEntries = maxEntries; ExpansionObserver = expansionObserver; } /// Whole-part prerequisites by source ordinal (cutoffs, inner parts before hosts). internal CuttingDependencyGraph Dependencies { get; } /// Exact captured plate state for plate-scoped requests; null for detached part lists. internal PlateCuttingState PlateState { get; } /// Owned copy of the confirmed parameters taken at capture; never the caller's object. internal CuttingParameters OwnedParameters { get; } internal bool Regeneration { get; } internal bool PreservePartOrder { get; } internal int MaxEntries { get; } internal Action ExpansionObserver { get; } public IReadOnlyList Placements { get; } public Vector StartPoint { get; } public int ExpansionBudget { get; } internal CuttingPlanStatus? Failure { get; } internal IReadOnlyList Findings { get; } } public sealed class FixedProgramPlacement { internal FixedProgramPlacement(Part sourcePart, int sourceOrdinal, Vector location, double rotation, bool leadInsLocked, OwnedExecution execution, Program program = null, PreparedContours prepared = null, LeadMaterialSnapshot material = null, IReadOnlyList choices = null) { SourcePart = sourcePart; SourceOrdinal = sourceOrdinal; Location = location; Rotation = rotation; LeadInsLocked = leadInsLocked; Execution = execution; this.program = program; Prepared = prepared; Material = material; ContourChoices = Array.AsReadOnly((choices ?? []).ToArray()); } private readonly Program program; internal PreparedContours Prepared { get; } internal LeadMaterialSnapshot Material { get; } public IReadOnlyList ContourChoices { get; } public bool IsRegenerated => ContourChoices.Count != 0; /// Returns an independent deep copy; never an alias to captured/proposed code. public Program CopyProgram() => program == null ? null : OwnedProgramCopy.Copy(program); internal SelectedContourProgram SelectedProgram { get; private init; } internal FixedProgramPlacement Propose(Program proposed, OwnedExecution execution, IReadOnlyList choices, System.Threading.CancellationToken token = default) { SelectedContourProgram selected = null; try { // The expected program is constructed from owned choices/settings, NEVER // from proposed or its cached execution. Counterfeit payloads remain subject // to independent final checks, including callers of this internal test seam. selected = Prepared?.CaptureSelectedProgram(choices, Location, token); } catch (Exception ex) when (ex is ArgumentException or NotSupportedException or InvalidOperationException or ArithmeticException) { // Invalid metadata is a refused proposal, not an exception from final replay. } return new(SourcePart, SourceOrdinal, Location, Rotation, LeadInsLocked, execution, OwnedProgramCopy.Copy(proposed, token), Prepared, Material, choices) { SelectedProgram = selected, IsCutOff = IsCutOff }; } /// A cutoff: always a fixed open-cut program, never material or regenerated. public bool IsCutOff { get; internal init; } public Part SourcePart { get; } public int SourceOrdinal { get; } public Vector Location { get; } public double Rotation { get; } public bool LeadInsLocked { get; } public OwnedExecution Execution { get; } } public enum CuttingPlanStatus { Ready, ConstraintConflict, UnsupportedGeometry, InvalidInput, NoSolutionWithinBudget, Cancelled } /// Ordinals are zero-based source positions, not proposed sequence positions. public sealed record CuttingPlanFinding(int? SourceOrdinal, Part SourcePart, int? OtherSourceOrdinal, Part OtherSourcePart, PostVerificationKind? Kind, string Message); /// /// A replayed direct-XY proposal, optionally with regenerated programs, that respects the /// captured cutoff/containment prerequisites. Not physical safety or posting consent. Apply /// installs it only for a plate-scoped request whose plate is unchanged. Failures contain no proposals. /// public sealed class CuttingPlanResult { internal CuttingPlanResult(CuttingPlanStatus status, IEnumerable order = null, IEnumerable findings = null, int expansions = 0, double rapidDistance = 0, bool independentlyReplayed = false) { Status = status; ProposedOrder = Array.AsReadOnly((order ?? []).ToArray()); Findings = Array.AsReadOnly((findings ?? []).ToArray()); Expansions = expansions; RapidDistance = rapidDistance; IndependentlyReplayed = independentlyReplayed; } public CuttingPlanStatus Status { get; } public IReadOnlyList ProposedOrder { get; } public IReadOnlyList Findings { get; } public int Expansions { get; } public double RapidDistance { get; } public bool IndependentlyReplayed { get; } /// The captured input this result was planned from; binds Apply to its freshness record. internal CuttingPlanSnapshot Snapshot { get; set; } }