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. This is not an Apply request. /// 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; } 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) { Placements = Array.AsReadOnly(placements.ToArray()); StartPoint = startPoint; ExpansionBudget = expansionBudget; Failure = failure; Findings = Array.AsReadOnly((findings ?? []).ToArray()); Regeneration = regeneration; PreservePartOrder = preservePartOrder; MaxEntries = maxEntries; ExpansionObserver = expansionObserver; } 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 }; } 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. Not physical /// safety, posting consent, dependency readiness or an atomic Apply payload. 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; } }