mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-06 16:55:47 -05:00
Slice 3 of common-line cutting. On costly plate (N26-295: 3/16 RA310S at about $2,500 a sheet) pairing parts one kerf apart fits the part the spacing cannot: the last plate's 38 parts fit on one 48x120 with Auto Nest only with pairs. - Drawing Properties gains "Allow common line" (Drawing.AllowCommonLine, off by default because customer edge-quality needs vary). The nest's CommonLineUsage (Off / When it adds parts / Whenever available) is set in Auto Nest and seeded from Tools > Nest Defaults; both persist (nest.json, defaults.json). - NestPipeline runs the ordinary solve, then, with a level on, trials where eligible parts are offered as kerf-gap pair compounds. Placed pairs expand into parts plus explicit NestJobCommonLine groups, are validated again, and CommonLineRanking keeps a trial only by the level: count first, then sheets; "when it adds parts" keeps the plain layout on a tie, "whenever" also takes a shorter cut or less material, never at the cost of a part. Engines, FillScore and their comparers are unchanged; Off asks the engine exactly what it did. - NestLayoutCheck exempts members only through a sheet's explicit group list and only when the job allows common lines; materializing and committing create Plate.CommonLines. - Server nesting writes common-line jobs as version 3 and replays the recorded trial answers, so the desktop binds the same groups. - Fill Area honors the level through a PlateFillService overload. - The benchmark gains --common-lines <level> --kerf <k>. Benchmark (synthetic nests + N26-295, --parallel 1): with no flag and with --common-lines off every engine matches the base commit; with a level on no layout becomes invalid and three runs save a sheet at equal nested area. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
98 lines
4.4 KiB
C#
98 lines
4.4 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 = "Lattice",
|
|
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)
|
|
{
|
|
CommonLineUsage = CommonLineUsage,
|
|
CommonLineKerf = CommonLineKerf,
|
|
};
|
|
|
|
/// <summary>
|
|
/// When the pipeline may place parts that allow it on common lines, one
|
|
/// <see cref="CommonLineKerf"/> apart (see docs/common-line-cutting.md). Off by default;
|
|
/// without a positive kerf it acts as off.
|
|
/// </summary>
|
|
public CommonLineUsage CommonLineUsage
|
|
{
|
|
get => commonLineUsage;
|
|
init => commonLineUsage = Enum.IsDefined(value) ? value : throw new ArgumentOutOfRangeException(nameof(CommonLineUsage));
|
|
}
|
|
|
|
private readonly CommonLineUsage commonLineUsage;
|
|
private readonly double commonLineKerf;
|
|
|
|
/// <summary>The cutting kerf for the job's material and thickness, in job units; zero when unknown.</summary>
|
|
public double CommonLineKerf
|
|
{
|
|
get => commonLineKerf;
|
|
init => commonLineKerf = double.IsFinite(value) && value >= 0 ? value : throw new ArgumentOutOfRangeException(nameof(CommonLineKerf));
|
|
}
|
|
|
|
/// <summary>True when common lines may be made: a level is on and the kerf is positive.</summary>
|
|
public bool UsesCommonLines => CommonLineUsage != CommonLineUsage.Off && CommonLineKerf > 0;
|
|
|
|
/// <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; }
|
|
}
|