mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-03 18:11:36 -05:00
Equal-cost plans break ties by the largest edge offcut, from any of the four edges. A caller that wants the leftover off one way, such as a shop that keeps only end drops, or a benchmark scoring the used sheet height, had no way to say so. On the converted Nestbolt jobs the extended Irregular search then found plans whose larger offcut is a strip down the side, which Nestbolt scores as worse: b-blaz1-strip 3.601 -> 3.867, b-shirts-strip 2.674 -> 2.995. NestJobOptions.OffcutCut (a CutOffAxis, default null) counts only the strips one cut along that axis frees, for salvage credit and the compactness tie-break alike: Horizontal the full-width bottom and top strips, Vertical the full-length sides. PlateRemnant.LargestOffcut takes the axis; NestJobCost, the time limit and clamp-removal recovery pass it on. Unset, nothing changes. Benchmark manifests accept "offcutCut": "horizontal" or "vertical". With horizontal, one CPU, 20 s limit, Default: b-blaz1-strip 3.934 -> 3.333 b-shirts-strip 2.995 -> 2.610 m-box-deep 1.918 -> 1.276 (Nestbolt 1.251) Manifests accept only the words horizontal or vertical (any case); a list such as "horizontal,vertical" is rejected. PlateRemnant rejects an undefined axis. Fixed strategies keep their own no-salvage tie-break and ignore the option; desktop, Console and MCP do not offer it yet. Mutation reds: 14/14 (offcut-mutations.py).
70 lines
3.2 KiB
C#
70 lines
3.2 KiB
C#
using System;
|
|
using OpenNest;
|
|
|
|
namespace OpenNest.Engine.Jobs;
|
|
|
|
/// <summary>Immutable per-job options; selection never changes the legacy global registry.</summary>
|
|
public sealed class NestJobOptions
|
|
{
|
|
public NestJobOptions(
|
|
string placementStrategy = "Fill",
|
|
int? maxPlates = null,
|
|
double salvageRate = 0,
|
|
double minimumSalvageDimension = 0,
|
|
bool searchUntilCancelled = false,
|
|
CutOffAxis? offcutCut = null
|
|
)
|
|
{
|
|
if (offcutCut is { } axis && !Enum.IsDefined(axis))
|
|
throw new ArgumentOutOfRangeException(nameof(offcutCut));
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(placementStrategy);
|
|
if (maxPlates <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(maxPlates));
|
|
if (!double.IsFinite(salvageRate) || salvageRate < 0 || salvageRate > 1)
|
|
throw new ArgumentOutOfRangeException(nameof(salvageRate));
|
|
if (!double.IsFinite(minimumSalvageDimension) || minimumSalvageDimension < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(minimumSalvageDimension));
|
|
PlacementStrategy = placementStrategy;
|
|
MaxPlates = maxPlates;
|
|
SalvageRate = salvageRate;
|
|
MinimumSalvageDimension = minimumSalvageDimension;
|
|
SearchUntilCancelled = searchUntilCancelled;
|
|
OffcutCut = offcutCut;
|
|
}
|
|
|
|
/// <summary>
|
|
/// When set, only the edge strips one cut along this axis frees count as a sheet's offcut, for
|
|
/// salvage credit and for the compactness tie-break between equal-cost plans:
|
|
/// <see cref="CutOffAxis.Horizontal"/> keeps full-width strips at the bottom or top (the drop
|
|
/// off the end of a sheet), <see cref="CutOffAxis.Vertical"/> full-length ones at the sides.
|
|
/// Null (the default) counts the largest of all four.
|
|
/// </summary>
|
|
public CutOffAxis? OffcutCut { get; }
|
|
|
|
/// <summary>
|
|
/// Asks engines that support it to keep searching after their normal search, reporting
|
|
/// each better plan (<see cref="NestJobStage.PlanImproved"/>) until the solve's token is
|
|
/// cancelled, which then ends the solve with <see cref="OperationCanceledException"/>.
|
|
/// Meant for a caller that keeps the best reported plan at a time limit, such as
|
|
/// <see cref="NestingEngines.DeadlineNestingEngine"/>. Ignored when the token cannot be
|
|
/// cancelled. Results then depend on how long the search ran.
|
|
/// </summary>
|
|
public bool SearchUntilCancelled { get; }
|
|
|
|
/// <summary>A copy of these options with <see cref="SearchUntilCancelled"/> set.</summary>
|
|
public NestJobOptions WithSearchUntilCancelled(bool value) =>
|
|
new(PlacementStrategy, MaxPlates, SalvageRate, MinimumSalvageDimension, value, OffcutCut);
|
|
|
|
/// <summary>Fraction of eligible edge-offcut area credited by <see cref="NestJobCost.NetSheetCost"/> (0..1).</summary>
|
|
public double SalvageRate { get; }
|
|
|
|
/// <summary>Both offcut dimensions must meet this caller-supplied minimum in job units.
|
|
/// Zero disables credit; scraps and holes are never credited.</summary>
|
|
public double MinimumSalvageDimension { get; }
|
|
|
|
public string PlacementStrategy { get; }
|
|
|
|
/// <summary>Maximum physical sheets to commit, or null for no explicit cap.</summary>
|
|
public int? MaxPlates { get; }
|
|
}
|