using System; using System.Collections.Generic; using OpenNest.Geometry; namespace OpenNest.Support { /// /// A laser bed whose parts rest on a regular grid of contact points: one point every /// along X and every along Y, the first at /// (, ) measured from the plate origin into the /// sheet. A sheet in another quadrant sees the same grid mirrored, since it lies on the /// other side of the origin. Lengths are in the nest's units. /// public sealed class SupportGrid : SupportBed { public SupportGrid(double pitchX, double pitchY, double offsetX = 0, double offsetY = 0, double registrationTolerance = 0, double stabilityMargin = 0) : base(registrationTolerance, stabilityMargin) { PitchX = Positive(pitchX, nameof(pitchX)); PitchY = Positive(pitchY, nameof(pitchY)); OffsetX = Finite(offsetX, nameof(offsetX)); OffsetY = Finite(offsetY, nameof(offsetY)); } public double PitchX { get; } public double PitchY { get; } public double OffsetX { get; } public double OffsetY { get; } public override IReadOnlyList Contacts(SupportMaterial material, int quadrant, Vector shift, bool extremesOnly = false) { ArgumentNullException.ThrowIfNull(material); var contacts = new List(); if (!material.IsComplete) return contacts; // The grid's world lattice: OffsetX into the sheet from the origin, then every PitchX. var baseX = SignX(quadrant) * OffsetX + shift.X; var baseY = SignY(quadrant) * OffsetY + shift.Y; var bounds = material.Bounds; var firstRow = (long)System.Math.Ceiling((bounds.Bottom - baseY) / PitchY); var lastRow = (long)System.Math.Floor((bounds.Top - baseY) / PitchY); for (var row = firstRow; row <= lastRow; row++) { var y = baseY + row * PitchY; foreach (var (start, end) in material.SpansAtY(y)) { var first = (long)System.Math.Ceiling((start - baseX) / PitchX); var last = (long)System.Math.Floor((end - baseX) / PitchX); if (first > last) continue; // Within one span only its end points can be corners of the contacts' hull. if (extremesOnly) { contacts.Add(new Vector(baseX + first * PitchX, y)); if (last != first) contacts.Add(new Vector(baseX + last * PitchX, y)); continue; } for (var column = first; column <= last; column++) contacts.Add(new Vector(baseX + column * PitchX, y)); } } return contacts; } /// /// The grid points within (world coordinates) on a sheet in /// , for display; empty when there would be more than /// . /// public IReadOnlyList PointsIn(Box region, int quadrant, int limit = 20000) { ArgumentNullException.ThrowIfNull(region); var baseX = SignX(quadrant) * OffsetX; var baseY = SignY(quadrant) * OffsetY; var firstColumn = System.Math.Ceiling((region.Left - baseX) / PitchX); var lastColumn = System.Math.Floor((region.Right - baseX) / PitchX); var firstRow = System.Math.Ceiling((region.Bottom - baseY) / PitchY); var lastRow = System.Math.Floor((region.Top - baseY) / PitchY); var count = (lastColumn - firstColumn + 1) * (lastRow - firstRow + 1); if (!double.IsFinite(count) || count <= 0 || count > limit) return Array.Empty(); var points = new List((int)count); for (var row = firstRow; row <= lastRow; row++) for (var column = firstColumn; column <= lastColumn; column++) points.Add(new Vector(baseX + column * PitchX, baseY + row * PitchY)); return points; } public override SupportBed Scaled(double factor) => new SupportGrid(PitchX * factor, PitchY * factor, OffsetX * factor, OffsetY * factor, RegistrationTolerance * factor, StabilityMargin * factor); public override bool Equals(SupportBed other) => other is SupportGrid grid && grid.PitchX == PitchX && grid.PitchY == PitchY && grid.OffsetX == OffsetX && grid.OffsetY == OffsetY && grid.RegistrationTolerance == RegistrationTolerance && grid.StabilityMargin == StabilityMargin; public override int GetHashCode() => HashCode.Combine(PitchX, PitchY, OffsetX, OffsetY, RegistrationTolerance, StabilityMargin); private static double Positive(double value, string name) => double.IsFinite(value) && value > 0 ? value : throw new ArgumentOutOfRangeException(name, "Grid pitch must be finite and positive."); private static double Finite(double value, string name) => double.IsFinite(value) ? value : throw new ArgumentOutOfRangeException(name, "Grid offset must be finite."); } }