using System; using System.Collections.Generic; using System.Linq; namespace OpenNest; /// Existing cutting work on a set of plates: applied lead-ins and cut-offs. /// Real parts carrying applied lead-ins (locked ones included). /// Of those, parts whose lead-ins are locked. /// Cut-off definitions. /// Display numbers of the plates holding any of it, ascending. public sealed record AppliedCuttingWorkSummary(int LeadInParts, int LockedLeadInParts, int CutOffs, IReadOnlyList PlateNumbers) { public bool HasExistingWork => LeadInParts > 0 || CutOffs > 0; /// Operator text for the cutting planner's warning banner; empty when nothing exists. public string Describe() { if (!HasExistingWork) return string.Empty; var found = new List(); if (LeadInParts > 0) found.Add($"lead-ins on {Count(LeadInParts, "part")}" + (LockedLeadInParts > 0 ? $" ({LockedLeadInParts} locked)" : string.Empty)); if (CutOffs > 0) found.Add(Count(CutOffs, "cut-off")); var plates = PlateNumbers.Count == 1 ? "plate " : "plates "; var kept = new List(); if (LockedLeadInParts > 0) kept.Add("locked lead-ins"); if (CutOffs > 0) kept.Add("existing cut-offs"); return $"ALREADY APPLIED: {string.Join(" and ", found)} on {plates}{string.Join(", ", PlateNumbers)}." + (LeadInParts > LockedLeadInParts ? " Planning recalculates unlocked lead-ins." : string.Empty) + (kept.Count > 0 ? $" Planning keeps {string.Join(" and ", kept)} unchanged." : string.Empty) + " Clear them to recalculate everything from scratch."; } private static string Count(int value, string noun) => $"{value} {noun}{(value == 1 ? string.Empty : "s")}"; } /// Finds and clears applied lead-ins and cut-offs so a plan can recalculate them. public static class AppliedCuttingWork { /// /// Counts applied lead-ins on real parts and cut-off definitions. Definitions in /// (for example ones staged by the caller) are not counted. /// public static AppliedCuttingWorkSummary Summarize(IReadOnlyList plates, IReadOnlyList plateNumbers = null, IEnumerable ignoredCutOffs = null) { ArgumentNullException.ThrowIfNull(plates); if (plateNumbers != null && plateNumbers.Count != plates.Count) throw new ArgumentException("Give one display number per plate.", nameof(plateNumbers)); var ignored = new HashSet(ignoredCutOffs ?? [], ReferenceEqualityComparer.Instance); int leads = 0, locked = 0, cutOffs = 0; var numbers = new SortedSet(); for (var index = 0; index < plates.Count; index++) { var plate = plates[index] ?? throw new ArgumentException("Plates must not be null.", nameof(plates)); var plateLeads = plate.Parts.Where(p => !p.BaseDrawing.IsCutOff && p.HasManualLeadIns).ToArray(); var plateCutOffs = plate.CutOffs.Count(c => !ignored.Contains(c)); leads += plateLeads.Length; locked += plateLeads.Count(p => p.LeadInsLocked); cutOffs += plateCutOffs; if (plateLeads.Length > 0 || plateCutOffs > 0) numbers.Add(plateNumbers?[index] ?? index + 1); } return new(leads, locked, cutOffs, numbers.ToArray()); } /// /// Removes every applied lead-in (locked ones too) from real parts and every cut-off with /// its cut-off parts, and forgets each plate's recorded cutting parameters. Part poses, /// order and quantities are unchanged. Returns what was cleared. /// public static AppliedCuttingWorkSummary Clear(IReadOnlyList plates, IReadOnlyList plateNumbers = null) { var cleared = Summarize(plates, plateNumbers); foreach (var plate in plates) { foreach (var part in plate.Parts.Where(p => !p.BaseDrawing.IsCutOff && p.HasManualLeadIns).ToArray()) part.RemoveLeadIns(); plate.CutOffs.Clear(); for (var index = plate.Parts.Count - 1; index >= 0; index--) if (plate.Parts[index].BaseDrawing.IsCutOff) plate.Parts.RemoveAt(index); plate.CuttingParameters = null; } return cleared; } }