mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-15 22:41:36 -04:00
feat(rectangles): repeat completed sheets from remaining demand
This commit is contained in:
1 parent
c1d5a827bc
commit
49017c4a91
5 files changed
+423
-4
No files matched your search
@@ -32,6 +32,10 @@ public sealed class RectanglesNestingEngine : INestingEngine
|
||||
|
||||
private static readonly PickMode[] Modes = { PickMode.Global, PickMode.Ordered };
|
||||
|
||||
// Instance-owned diagnostics; no process-global counters or mutable solver state.
|
||||
internal Action<PickMode>? SheetPacked { get; init; }
|
||||
internal Action<RepeatBatch>? BatchSelected { get; init; }
|
||||
|
||||
public NestJobResult Solve(
|
||||
NestJob job,
|
||||
IProgress<NestJobProgress>? progress = null,
|
||||
@@ -49,11 +53,13 @@ public sealed class RectanglesNestingEngine : INestingEngine
|
||||
remaining[t.Index] = 0;
|
||||
|
||||
var used = job.Plates.ToDictionary(s => s.Id, _ => 0, StringComparer.Ordinal);
|
||||
var parts = job.Parts.ToDictionary(p => p.Id, StringComparer.Ordinal);
|
||||
var result = new NestJobResultBuilder(job, progress);
|
||||
NestJobStopReason reason;
|
||||
|
||||
while (true)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
if (remaining.All(r => r == 0))
|
||||
{
|
||||
reason = NestJobStopReason.NoPlacementFound; // Builder reports Completed when demand is met.
|
||||
@@ -77,6 +83,7 @@ public sealed class RectanglesNestingEngine : INestingEngine
|
||||
foreach (var rule in Rules)
|
||||
{
|
||||
var plan = SheetPacker.Pack(types, remaining, stock, rule, mode, token);
|
||||
SheetPacked?.Invoke(mode);
|
||||
if (plan.Parts.Count > 0)
|
||||
trials.Add((plan, NetCost(job, plan)));
|
||||
}
|
||||
@@ -90,10 +97,39 @@ public sealed class RectanglesNestingEngine : INestingEngine
|
||||
}
|
||||
|
||||
var chosen = Choose(types, remaining, trials);
|
||||
result.AddSheet(chosen.Stock, chosen.Poses());
|
||||
used[chosen.Stock.Id]++;
|
||||
foreach (var p in chosen.Parts)
|
||||
remaining[p.Type.Index]--;
|
||||
var poses = chosen.Poses().ToArray();
|
||||
var demand = types.ToDictionary(t => t.Id, t => remaining[t.Index], StringComparer.Ordinal);
|
||||
var counts = chosen.Parts.GroupBy(p => p.Type.Id, StringComparer.Ordinal)
|
||||
.ToDictionary(g => g.Key, g => g.Count(), StringComparer.Ordinal);
|
||||
var availableEntries = job.Plates.Count(s => s.Quantity == null || used[s.Id] < s.Quantity);
|
||||
// Both private MaxRects modes return only after their normal demand/no-fit search;
|
||||
// neither returns partial/budget-limited output. This is not a geometric impossibility proof.
|
||||
var copies = CompletedLayoutRepeat.Limit(demand, counts, searchCompleted: true, availableEntries,
|
||||
chosen.Stock.Quantity - used[chosen.Stock.Id], job.Options.MaxPlates - result.SheetsUsed(job));
|
||||
if (copies > 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
NestJobValidator.ValidateCandidate(new PlateCandidate(poses.Select(p =>
|
||||
new NestJobPlacement(p.PartId, 0, p.X, p.Y, p.Rotation))), chosen.Stock, demand, parts);
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// Keep the original one-sheet path if the candidate validator cannot certify
|
||||
// reuse (including its stricter work-area tolerance). Final job validation still applies.
|
||||
copies = 1;
|
||||
}
|
||||
}
|
||||
BatchSelected?.Invoke(new RepeatBatch(chosen.Stock.Id, copies, availableEntries, result.SheetsUsed(job), false));
|
||||
for (var copy = 0; copy < copies; copy++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
result.AddSheet(chosen.Stock, poses); // Builder owns fresh placements/instance indices.
|
||||
}
|
||||
used[chosen.Stock.Id] = checked(used[chosen.Stock.Id] + copies);
|
||||
foreach (var type in types)
|
||||
if (counts.TryGetValue(type.Id, out var count))
|
||||
remaining[type.Index] = checked(remaining[type.Index] - checked(count * copies));
|
||||
}
|
||||
|
||||
return result.Build(reason);
|
||||
|
||||
Reference in new issue
Block a user