mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-04 06:51:35 -05:00
A clamp rail can now carry its own Clearance, the gap parts keep from every clamp footprint, independent of part spacing. Unset, it keeps the sheet's bottom edge spacing as before. Clamp blocks drawn under the sheet can use close to zero so the laser only stays off them. Plate.ClampClearance and NestPlateStock.ClampClearance resolve it once; the engines, interactive fill/pack/push, the layout check, Expander walls and automatic cut-off placement all read it. The interactive push now holds clamps at their square clearance keep-out (the layout check's shape) instead of the part spacing, so parts no longer tuck into its corners; the clamped fill characterization digests are re-pinned after checking the new layouts validate. Persisted as an optional `clearance` on the .nest clamp rail and the machine clamp settings; edited in Nest > Clamps and the machine's Clamps tab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
4.6 KiB
C#
113 lines
4.6 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
|
|
namespace OpenNest.Engine.Jobs;
|
|
|
|
/// <summary>Alternate representation of checked physical sheets; never changes raw output.
|
|
/// Equality includes ordered requirement IDs and scalar bits, not instance indices.</summary>
|
|
internal static class RepeatedPlateGrouping
|
|
{
|
|
internal sealed record Group(NestJobPlateResult Sheet, int Quantity);
|
|
|
|
internal static IReadOnlyList<Group> Create(IReadOnlyList<NestJobPlateResult> sheets,
|
|
bool consolidate, CancellationToken token = default)
|
|
{
|
|
var groups = new List<Group>();
|
|
var firstByLayout = new Dictionary<NestJobPlateResult, int>(LayoutComparer.Instance);
|
|
foreach (var sheet in sheets)
|
|
{
|
|
token.ThrowIfCancellationRequested();
|
|
if (consolidate && sheet.Placements.Count > 0
|
|
&& firstByLayout.TryGetValue(sheet, out var first))
|
|
groups[first] = groups[first] with { Quantity = checked(groups[first].Quantity + 1) };
|
|
else
|
|
{
|
|
if (consolidate && sheet.Placements.Count > 0)
|
|
firstByLayout.Add(sheet, groups.Count);
|
|
groups.Add(new Group(sheet, 1));
|
|
}
|
|
}
|
|
return groups.AsReadOnly();
|
|
}
|
|
|
|
internal static bool SameStock(NestPlateStock a, NestPlateStock b) =>
|
|
string.Equals(a.Id, b.Id, StringComparison.Ordinal)
|
|
&& Bits(a.Size.Width) == Bits(b.Size.Width) && Bits(a.Size.Length) == Bits(b.Size.Length)
|
|
&& a.Quadrant == b.Quadrant && Bits(a.PartSpacing) == Bits(b.PartSpacing)
|
|
&& Bits(a.EdgeSpacing.Left) == Bits(b.EdgeSpacing.Left)
|
|
&& Bits(a.EdgeSpacing.Right) == Bits(b.EdgeSpacing.Right)
|
|
&& Bits(a.EdgeSpacing.Top) == Bits(b.EdgeSpacing.Top)
|
|
&& Bits(a.EdgeSpacing.Bottom) == Bits(b.EdgeSpacing.Bottom)
|
|
&& Bits(a.ClampClearance) == Bits(b.ClampClearance)
|
|
&& a.Quantity == b.Quantity
|
|
&& Equals(a.Outline, b.Outline)
|
|
&& a.Cost.HasValue == b.Cost.HasValue
|
|
&& (!a.Cost.HasValue || Bits(a.Cost.Value) == Bits(b.Cost.Value))
|
|
&& SameZones(a.ClampZones, b.ClampZones);
|
|
|
|
// C8: a layout solved with clamps is never reused for a plate without them - and a
|
|
// different clamp set is different stock. Ordered; ids and exact bits participate.
|
|
private static bool SameZones(
|
|
System.Collections.Generic.IReadOnlyList<ClampZone> x,
|
|
System.Collections.Generic.IReadOnlyList<ClampZone> y
|
|
)
|
|
{
|
|
if (x.Count != y.Count)
|
|
return false;
|
|
for (var i = 0; i < x.Count; i++)
|
|
{
|
|
if (
|
|
!string.Equals(x[i].Id, y[i].Id, StringComparison.Ordinal)
|
|
|| Bits(x[i].Bounds.X) != Bits(y[i].Bounds.X)
|
|
|| Bits(x[i].Bounds.Y) != Bits(y[i].Bounds.Y)
|
|
|| Bits(x[i].Bounds.Length) != Bits(y[i].Bounds.Length)
|
|
|| Bits(x[i].Bounds.Width) != Bits(y[i].Bounds.Width)
|
|
)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static long Bits(double value) => BitConverter.DoubleToInt64Bits(value);
|
|
|
|
private sealed class LayoutComparer : IEqualityComparer<NestJobPlateResult>
|
|
{
|
|
internal static readonly LayoutComparer Instance = new();
|
|
|
|
public bool Equals(NestJobPlateResult a, NestJobPlateResult b)
|
|
{
|
|
if (ReferenceEquals(a, b))
|
|
return true;
|
|
if (a == null || b == null || !SameStock(a.Stock, b.Stock)
|
|
|| a.Placements.Count != b.Placements.Count)
|
|
return false;
|
|
for (var i = 0; i < a.Placements.Count; i++)
|
|
{
|
|
var x = a.Placements[i];
|
|
var y = b.Placements[i];
|
|
if (!string.Equals(x.PartId, y.PartId, StringComparison.Ordinal)
|
|
|| Bits(x.X) != Bits(y.X) || Bits(x.Y) != Bits(y.Y)
|
|
|| Bits(x.Rotation) != Bits(y.Rotation))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public int GetHashCode(NestJobPlateResult sheet)
|
|
{
|
|
// Hash only accelerates lookup; Equals checks every stock field and ordered pose.
|
|
var hash = new HashCode();
|
|
hash.Add(sheet.StockId, StringComparer.Ordinal);
|
|
foreach (var pose in sheet.Placements)
|
|
{
|
|
hash.Add(pose.PartId, StringComparer.Ordinal);
|
|
hash.Add(Bits(pose.X));
|
|
hash.Add(Bits(pose.Y));
|
|
hash.Add(Bits(pose.Rotation));
|
|
}
|
|
return hash.ToHashCode();
|
|
}
|
|
}
|
|
}
|