Files
OpenNest/OpenNest.Core/ClampZoneFrame.cs
T
aj df2f1b796d fix(engine): keep every plate's clamp zones in the sheet frame
Plates the engine built from stock (DrawingJobMapper.CreatePlate, used
by the public result materializer, the layout check and the placement
fillers) stored placement-frame zones, while live plates store them
relative to the sheet's lower-left corner. The same Plate.ClampZones
therefore meant two frames: the new clamp cut checks double-translated
materialized plates in quadrants 2-4, and interactive fill and push on
live plates read sheet-frame zones as placement coordinates, so they
could put parts on clamps there.

Plates built from stock now keep the stock's input zones (sheet frame,
no lossy translate-and-back), and the layout check and interactive
clamp obstacles map through ClampZoneFrame.ToPlacement, which
reproduces the stock's placement zones bit for bit, so engine
placements and validation are unchanged.

Project Memory: opennest 14f93b26-2e41-41f1-b09f-87dc895bb4da
2026-10-13 18:15:49 -04:00

55 lines
2.6 KiB
C#

using System;
using System.Collections.Generic;
using OpenNest.Geometry;
namespace OpenNest;
/// <summary>
/// Maps a plate's <see cref="Plate.ClampZones"/>, stored in the sheet's lower-left frame
/// (<see cref="ClampRail.ResolveFor"/>), into the signed placement frame of its parts,
/// cut-offs and motions. The translation matches the sheet itself and the engine stock:
/// -Length on X in quadrants 2 and 3, -Width on Y in quadrants 3 and 4. Whole footprints are
/// kept, never clipped. Every plate uses the sheet frame, including plates the engine builds
/// from a <c>NestPlateStock</c>, so this is the only translation any consumer applies.
/// </summary>
public static class ClampZoneFrame
{
/// <summary>
/// Detached placement-frame copies in the plate's zone order; empty when there are none.
/// Throws <see cref="ArgumentException"/> for a missing or malformed zone: zone bounds stay
/// publicly mutable, so construction-time validation is not proof of current geometry.
/// </summary>
public static IReadOnlyList<ClampZone> ToPlacement(Plate plate)
{
ArgumentNullException.ThrowIfNull(plate);
var zones = plate.ClampZones;
if (zones == null || zones.Count == 0)
return Array.Empty<ClampZone>();
if (plate.Quadrant is < 1 or > 4)
throw new ArgumentException("Quadrant must be 1 through 4.", nameof(plate));
var left = plate.Quadrant is 2 or 3 ? -plate.Size.Length : 0;
var bottom = plate.Quadrant is 3 or 4 ? -plate.Size.Width : 0;
if (!double.IsFinite(left) || !double.IsFinite(bottom))
throw new ArgumentException("Sheet size must be finite.", nameof(plate));
var mapped = new ClampZone[zones.Count];
for (var i = 0; i < mapped.Length; i++)
{
var zone = zones[i];
if (zone?.Bounds == null)
throw new ArgumentException($"Clamp zone {i + 1} is missing.", nameof(plate));
var box = zone.Bounds;
var x = box.X + left;
var y = box.Y + bottom;
if (!(box.Length > 0) || !(box.Width > 0) || !double.IsFinite(box.Length)
|| !double.IsFinite(box.Width) || !double.IsFinite(box.X) || !double.IsFinite(box.Y)
|| !double.IsFinite(x) || !double.IsFinite(y)
|| !double.IsFinite(x + box.Length) || !double.IsFinite(y + box.Width))
throw new ArgumentException(
$"Clamp zone '{zone.Id}' must have finite bounds and a positive size.", nameof(plate));
mapped[i] = new ClampZone(zone.Id, new Box(x, y, box.Length, box.Width));
}
return mapped;
}
}