Task 5 of the whole-job engine API: add a geometry safety gate that validates every candidate trial before the runner commits accounting. - NestJobPlacementValidator: closed-contour validity, rotation-policy compliance, work-area containment per quadrant, hole-aware material overlap, and required part spacing. Overlap is interior-only, so zero-clearance edge/corner contact remains a valid placement. - NestJobValidator: route candidate validation through the geometry gate; reject unusable/unclosed/degenerate contours up front. - NestJobRunner: wrap candidate evaluation in a progress bridge that tags legacy engine detail with the current candidate context. - LegacyPlateNesterAdapter: forward IProgress to the legacy engine so its progress surfaces under the active candidate. - Tests: geometry (quadrants, rotations, touching, containment, holes, empty stock, real Default/Strip smoke), validation, and cancellation suites; repaired test fakes that emitted out-of-bounds or overlapping placements the gate now correctly rejects. Engine.Tests: 70 passed, 0 failed, 0 skipped in Debug and Release. Windows-only OpenNest.Tests not run on Linux.
48 lines
2.1 KiB
C#
48 lines
2.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
|
|
namespace OpenNest;
|
|
|
|
/// <summary>Basic input and candidate accounting checks, NOT a geometry/clearance safety gate.</summary>
|
|
public static class NestJobValidator
|
|
{
|
|
public static void Validate(NestJob job)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(job);
|
|
foreach (var stock in job.Plates)
|
|
{
|
|
var edges = stock.EdgeSpacing;
|
|
if (!Positive(stock.Size.Width) || !Positive(stock.Size.Length) ||
|
|
!Nonnegative(stock.PartSpacing) || !Nonnegative(edges.Left) || !Nonnegative(edges.Right) ||
|
|
!Nonnegative(edges.Top) || !Nonnegative(edges.Bottom) || stock.Quadrant < 1 || stock.Quadrant > 4 ||
|
|
edges.Left + edges.Right >= stock.Size.Length || edges.Top + edges.Bottom >= stock.Size.Width)
|
|
throw new ArgumentException($"Invalid stock dimensions/settings: {stock.Id}.", nameof(job));
|
|
}
|
|
foreach (var part in job.Parts)
|
|
{
|
|
if (part.Geometry.Motions.Count == 0 || part.Geometry.Motions.Any(m =>
|
|
!double.IsFinite(m.X) || !double.IsFinite(m.Y) ||
|
|
!double.IsFinite(m.CenterX) || !double.IsFinite(m.CenterY)))
|
|
throw new ArgumentException($"Geometry must contain finite motions: {part.Id}.", nameof(job));
|
|
try
|
|
{
|
|
NestJobPlacementValidator.ValidateGeometry(part.Geometry);
|
|
}
|
|
catch (ArgumentException exception)
|
|
{
|
|
throw new ArgumentException($"Geometry must contain usable closed edges: {part.Id}.", nameof(job), exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static void ValidateCandidate(PlateCandidate candidate, NestPlateStock stock,
|
|
IReadOnlyDictionary<string, int> remaining, IReadOnlyDictionary<string, NestJobPart> parts)
|
|
{
|
|
NestJobPlacementValidator.ValidateCandidate(candidate, stock, remaining, parts);
|
|
}
|
|
|
|
private static bool Positive(double value) => double.IsFinite(value) && value > 0;
|
|
private static bool Nonnegative(double value) => double.IsFinite(value) && value >= 0;
|
|
}
|