Files
OpenNest/OpenNest.Engine/NestingEngines/Irregular/ClampObstacles.cs
T
ajandClaude Opus 5.5 863ce4afde feat(engine): drop a sheet by packing edge parts first and turning the key part by its contacts
The Irregular engine grows each sheet from one side, so parts that fit only
when the outer ones hug opposite edges never met: ten one-off brackets
(four tapered 112-inch bars, four gussets, two small plates) stacked three bars
on one sheet and took three 60 x 120 sheets where two hold them.

After the search, a sheet-reduction pass splits a complete single-stock plan
over one sheet fewer and packs each sheet edge first: large parts to the
corners (flush, most outline on the edges, thinnest across the sheet), small
parts into the gaps last, and one key part into the pocket between. Clamped
sides move in past the keep-outs so edge parts line up on one straight edge.
When no sampled orientation fits the key, ContactFit turns it by its contacts
(each overlap a linear constraint on dx, dy and rotation) instead of sampling
more angles; the exact no-fit free region then confirms the angle, so spacing
and validation are unchanged. That job now nests on two sheets, the middle bar
at 4.4 degrees; the repository benchmark jobs are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 15:17:56 -05:00

65 lines
2.6 KiB
C#

#nullable enable
using Clipper2Lib;
using OpenNest.Engine.Jobs;
namespace OpenNest.Engine.NestingEngines.Irregular;
/// <summary>
/// Static, clearance-inflated rectangles prepared once per stock per solve. Kept out of
/// the part catalog and its spacing cache: clamp clearance is the stock's clamp clearance, not
/// part spacing, and two stocks at the same part spacing may have different clamps.
/// </summary>
internal sealed class ClampObstacles
{
private readonly Orientation[] rectangles;
private readonly NoFitCache nfps = new(0);
public ClampObstacles(NestPlateStock stock)
{
rectangles = new Orientation[stock.ClampZones.Count];
var clearance = stock.ClampClearance;
for (var i = 0; i < rectangles.Length; i++)
{
var box = stock.ClampZones[i].Bounds;
var (left, right) = EncloseOnGrid(box.Left - clearance, box.Right + clearance);
var (bottom, top) = EncloseOnGrid(box.Bottom - clearance, box.Top + clearance);
rectangles[i] = new Orientation
{
// Real part types are nonnegative; the private cache cannot alias them.
TypeIndex = -1,
Index = i,
Rotation = 0,
Tolerance = 0,
MinX = left,
MinY = bottom,
MaxX = right,
MaxY = top,
Outline = new PathD
{
new(left, bottom), new(right, bottom), new(right, top), new(left, top),
},
};
}
}
// NoFitCache quantizes before adding its safety margin. Enclose the keep-out
// on that existing lattice so a sub-grid rectangle cannot disappear first.
private static (double Lower, double Upper) EncloseOnGrid(double lower, double upper)
{
var scale = System.Math.Pow(10, NoFitCache.Precision);
var first = System.Math.Floor(lower * scale);
// Positive dimensions can be smaller than a coordinate ulp (upper == lower).
var last = System.Math.Max(System.Math.Ceiling(upper * scale), first + 1);
return (first / scale, last / scale);
}
public int Count => rectangles.Length;
/// <summary>The keep-out rectangle, clearance included: a part's outline must stay outside it.</summary>
public PathD Outline(int index) => rectangles[index].Outline;
// No additional physical spacing. NoFitCache retains the established arc/grid safety
// margin and covered-hole certification for the moving orientation and rectangle.
public Nfp Get(int index, Orientation moving) => nfps.Get(rectangles[index], moving);
}