using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using OpenNest.Clamps; using OpenNest.Collections; using OpenNest.Geometry; namespace OpenNest { public partial class Plate { /// /// Fixed rectangular machine-clamp keep-outs in plate coordinates /// (). Never parts: excluded from quantity, utilization, /// cut sequence and CNC output; placement rules and display consume them. Mutate /// through this list; fires for every change. /// public ObservableList ClampZones { get; } /// /// Raised after content changes so views can refresh /// the zone overlay. Carries no geometry arguments. /// public event EventHandler ClampZonesChanged; private void OnClampZonesChanged() => ClampZonesChanged?.Invoke(this, EventArgs.Empty); /// /// The machine clamp rail this plate follows, or null when its /// are explicit (engine sheets, files without a rail). While /// set, the zones are derived: re-resolved whenever or /// changes, because the clamps are fixed to the machine. /// public ClampRail ClampRail { get; private set; } /// /// The gap part material keeps from every clamp zone: the rail's own /// , else the bottom edge spacing (D6), never /// negative. Part spacing never applies to clamps. /// public double ClampClearance => System.Math.Max(0, ClampRail?.Clearance ?? EdgeSpacing.Bottom); private static readonly IReadOnlySet NoRemovedClamps = ImmutableHashSet.Empty; /// /// Rail clamp ids taken off this sheet, by Auto Nest as a last resort (D8) so a part /// that could not be nested anywhere with every clamp in place still fits, or by hand /// (). Empty unless /// is set; already excludes them, so /// nothing else needs to consult this list to avoid them. Ids stay listed across a /// resize/rotate even when the matching clamp is temporarily beyond the sheet, so it /// re-appears removed if a later resize brings it back onto the sheet (C13). /// public IReadOnlySet RemovedClamps { get; private set; } = NoRemovedClamps; /// /// Minutes this sheet's cut-time estimate gains from (D9): /// each listed clamp the rail places on this sheet adds /// once; a listed clamp that is beyond /// the sheet (or unknown to the rail) is not pulled and costs nothing. Zero when there /// is no rail (removal is meaningless without one to remove clamps from). /// public double ClampRemovalMinutes => ClampRail == null || RemovedClamps.Count == 0 ? 0 : ClampRail.ResolveFor(Size, Quadrant).Count(zone => RemovedClamps.Contains(zone.Id)) * ClampRail.RemovalMinutes; /// /// Follows and replaces the zones with its resolution for /// this sheet; null detaches and removes every zone. Unchanged zones raise no events. /// public void SetClampRail(ClampRail rail) => SetClampRail(rail, null); /// /// Follows with the clamps named in /// taken off this sheet (D8): zones become the rail's resolution for this sheet minus /// those ids. Null detaches, clears every zone and forgets the /// removed set (removal only means something while following a rail). /// public void SetClampRail(ClampRail rail, IEnumerable removedClamps) { ClampRail = rail; RemovedClamps = rail == null || removedClamps == null ? NoRemovedClamps : removedClamps.ToImmutableHashSet(StringComparer.Ordinal); ReplaceClampZones(ResolveRailZones()); } /// /// The rail clamp this sheet places at (the parts' placement /// frame, see ), whether in place or removed, grown by /// on every side; null without a rail or a clamp there. /// Returned bounds are in the placement frame. /// public ClampZone RailClampAt(Vector point, double tolerance = 0) { if (ClampRail == null) return null; var left = Quadrant is 2 or 3 ? -Size.Length : 0; var bottom = Quadrant is 3 or 4 ? -Size.Width : 0; foreach (var zone in ClampRail.ResolveFor(Size, Quadrant)) { var box = zone.Bounds.Translate(left, bottom); if (box.Offset(tolerance).Contains(point)) return new ClampZone(zone.Id, box); } return null; } /// /// Takes the rail clamp off this sheet, or puts it back, as a /// manual counterpart to Auto Nest's last-resort removal (D8): the zones and /// follow. False when nothing changed: no rail, a clamp /// the rail does not place on this sheet, or one already in the requested state. /// public bool SetClampRemoved(string id, bool removed) { if (ClampRail == null || id == null || RemovedClamps.Contains(id) == removed) return false; if (removed && !ClampRail.ResolveFor(Size, Quadrant).Any(zone => zone.Id == id)) return false; var ids = removed ? RemovedClamps.Append(id) : RemovedClamps.Where(x => x != id); SetClampRail(ClampRail, ids); return true; } private void ResolveClampRail() { // Called from the constructor's Size assignment before the zone list exists. if (ClampRail != null) ReplaceClampZones(ResolveRailZones()); } private IReadOnlyList ResolveRailZones() { if (ClampRail == null) return Array.Empty(); var resolved = ClampRail.ResolveFor(Size, Quadrant); if (RemovedClamps.Count == 0) return resolved; var kept = new List(resolved.Count); foreach (var zone in resolved) if (!RemovedClamps.Contains(zone.Id)) kept.Add(zone); return kept; } private void ReplaceClampZones(IReadOnlyList zones) { if (ClampRail.SameZones(ClampZones, zones)) return; ClampZones.Clear(); foreach (var zone in zones) ClampZones.Add(zone); } } }