Compare commits
8 Commits
93a8981d0a
...
a548d5329a
| Author | SHA1 | Date | |
|---|---|---|---|
| a548d5329a | |||
| 07012033c7 | |||
| 92b17b2963 | |||
| b6ee04f038 | |||
| 8ffdacd6c0 | |||
| ccd402c50f | |||
| b1e872577c | |||
| 9903478d3e |
@@ -66,8 +66,15 @@ namespace OpenNest
|
||||
if (item.Quantity > 0 && best.Count > item.Quantity)
|
||||
best = ShrinkFiller.TrimToCount(best, item.Quantity, ShrinkAxis.Width);
|
||||
|
||||
ReportProgress(progress, WinnerPhase, PlateNumber, best, workArea, BuildProgressSummary(),
|
||||
isOverallBest: true);
|
||||
ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = WinnerPhase,
|
||||
PlateNumber = PlateNumber,
|
||||
Parts = best,
|
||||
WorkArea = workArea,
|
||||
Description = BuildProgressSummary(),
|
||||
IsOverallBest = true,
|
||||
});
|
||||
|
||||
return best;
|
||||
}
|
||||
@@ -94,8 +101,15 @@ namespace OpenNest
|
||||
|
||||
Debug.WriteLine($"[Fill(groupParts,Box)] Linear pattern: {best?.Count ?? 0} parts | WorkArea: {workArea.Width:F1}x{workArea.Length:F1}");
|
||||
|
||||
ReportProgress(progress, NestPhase.Linear, PlateNumber, best, workArea, BuildProgressSummary(),
|
||||
isOverallBest: true);
|
||||
ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Linear,
|
||||
PlateNumber = PlateNumber,
|
||||
Parts = best,
|
||||
WorkArea = workArea,
|
||||
Description = BuildProgressSummary(),
|
||||
IsOverallBest = true,
|
||||
});
|
||||
|
||||
return best ?? new List<Part>();
|
||||
}
|
||||
@@ -151,9 +165,15 @@ namespace OpenNest
|
||||
|
||||
if (context.CurrentBest != null && context.CurrentBest.Count > 0)
|
||||
{
|
||||
ReportProgress(context.Progress, context.WinnerPhase, PlateNumber,
|
||||
context.CurrentBest, context.WorkArea, BuildProgressSummary(),
|
||||
isOverallBest: true);
|
||||
ReportProgress(context.Progress, new ProgressReport
|
||||
{
|
||||
Phase = context.WinnerPhase,
|
||||
PlateNumber = PlateNumber,
|
||||
Parts = context.CurrentBest,
|
||||
WorkArea = context.WorkArea,
|
||||
Description = BuildProgressSummary(),
|
||||
IsOverallBest = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ namespace OpenNest.Engine.Fill
|
||||
combined.AddRange(previousParts);
|
||||
combined.AddRange(value.BestParts);
|
||||
value.BestParts = combined;
|
||||
value.BestPartCount = combined.Count;
|
||||
}
|
||||
|
||||
inner.Report(value);
|
||||
|
||||
@@ -7,74 +7,30 @@ namespace OpenNest
|
||||
public static bool FindFrom2(double length1, double length2, double overallLength, out int count1, out int count2)
|
||||
{
|
||||
overallLength += Tolerance.Epsilon;
|
||||
|
||||
if (length1 > overallLength)
|
||||
{
|
||||
if (length2 > overallLength)
|
||||
{
|
||||
count1 = 0;
|
||||
count2 = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
count1 = 0;
|
||||
count2 = (int)System.Math.Floor(overallLength / length2);
|
||||
return true;
|
||||
}
|
||||
var maxCount1 = (int)System.Math.Floor(overallLength / length1);
|
||||
var bestRemnant = overallLength + 1;
|
||||
|
||||
if (length2 > overallLength)
|
||||
for (var c1 = 0; c1 <= maxCount1; c1++)
|
||||
{
|
||||
count1 = (int)System.Math.Floor(overallLength / length1);
|
||||
count2 = 0;
|
||||
return true;
|
||||
}
|
||||
var remaining = overallLength - c1 * length1;
|
||||
var c2 = (int)System.Math.Floor(remaining / length2);
|
||||
var remnant = remaining - c2 * length2;
|
||||
|
||||
var maxCountLength1 = (int)System.Math.Floor(overallLength / length1);
|
||||
if (!(remnant < bestRemnant))
|
||||
continue;
|
||||
|
||||
count1 = maxCountLength1;
|
||||
count2 = 0;
|
||||
|
||||
var remnant = overallLength - maxCountLength1 * length1;
|
||||
count1 = c1;
|
||||
count2 = c2;
|
||||
bestRemnant = remnant;
|
||||
|
||||
if (remnant.IsEqualTo(0))
|
||||
return true;
|
||||
|
||||
for (int countLength1 = 0; countLength1 <= maxCountLength1; ++countLength1)
|
||||
{
|
||||
var remnant1 = overallLength - countLength1 * length1;
|
||||
|
||||
if (remnant1 >= length2)
|
||||
{
|
||||
var countLength2 = (int)System.Math.Floor(remnant1 / length2);
|
||||
var remnant2 = remnant1 - length2 * countLength2;
|
||||
|
||||
if (!(remnant2 < remnant))
|
||||
continue;
|
||||
|
||||
count1 = countLength1;
|
||||
count2 = countLength2;
|
||||
|
||||
if (remnant2.IsEqualTo(0))
|
||||
break;
|
||||
|
||||
remnant = remnant2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!(remnant1 < remnant))
|
||||
continue;
|
||||
|
||||
count1 = countLength1;
|
||||
count2 = 0;
|
||||
|
||||
if (remnant1.IsEqualTo(0))
|
||||
break;
|
||||
|
||||
remnant = remnant1;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return count1 > 0 || count2 > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,18 +36,36 @@ namespace OpenNest.Engine.Fill
|
||||
if (column.Count == 0)
|
||||
return new List<Part>();
|
||||
|
||||
NestEngineBase.ReportProgress(progress, NestPhase.Extents, plateNumber,
|
||||
column, workArea, $"Extents: initial column {column.Count} parts");
|
||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Extents,
|
||||
PlateNumber = plateNumber,
|
||||
Parts = column,
|
||||
WorkArea = workArea,
|
||||
Description = $"Extents: initial column {column.Count} parts",
|
||||
});
|
||||
|
||||
var adjusted = AdjustColumn(pair.Value, column, token);
|
||||
|
||||
NestEngineBase.ReportProgress(progress, NestPhase.Extents, plateNumber,
|
||||
adjusted, workArea, $"Extents: adjusted column {adjusted.Count} parts");
|
||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Extents,
|
||||
PlateNumber = plateNumber,
|
||||
Parts = adjusted,
|
||||
WorkArea = workArea,
|
||||
Description = $"Extents: adjusted column {adjusted.Count} parts",
|
||||
});
|
||||
|
||||
var result = RepeatColumns(adjusted, token);
|
||||
|
||||
NestEngineBase.ReportProgress(progress, NestPhase.Extents, plateNumber,
|
||||
result, workArea, $"Extents: {result.Count} parts total");
|
||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Extents,
|
||||
PlateNumber = plateNumber,
|
||||
Parts = result,
|
||||
WorkArea = workArea,
|
||||
Description = $"Extents: {result.Count} parts total",
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.Fill;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks evaluated grid configurations so duplicate pattern/direction/workArea
|
||||
/// combinations can be skipped across fill strategies.
|
||||
/// </summary>
|
||||
public class GridDedup
|
||||
{
|
||||
public const string SharedStateKey = "GridDedup";
|
||||
|
||||
private readonly ConcurrentDictionary<GridKey, byte> _seen = new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this configuration has NOT been seen before (i.e., should be evaluated).
|
||||
/// Returns false if it's a duplicate.
|
||||
/// </summary>
|
||||
public bool TryAdd(Box patternBox, Box workArea, NestDirection dir)
|
||||
{
|
||||
var key = new GridKey(patternBox, workArea, dir);
|
||||
return _seen.TryAdd(key, 0);
|
||||
}
|
||||
|
||||
public int Count => _seen.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or creates a GridDedup from FillContext.SharedState.
|
||||
/// </summary>
|
||||
public static GridDedup GetOrCreate(System.Collections.Generic.Dictionary<string, object> sharedState)
|
||||
{
|
||||
if (sharedState.TryGetValue(SharedStateKey, out var existing))
|
||||
return (GridDedup)existing;
|
||||
|
||||
var dedup = new GridDedup();
|
||||
sharedState[SharedStateKey] = dedup;
|
||||
return dedup;
|
||||
}
|
||||
|
||||
private readonly struct GridKey : IEquatable<GridKey>
|
||||
{
|
||||
private readonly int _patternW, _patternL, _workW, _workL, _dir;
|
||||
|
||||
public GridKey(Box patternBox, Box workArea, NestDirection dir)
|
||||
{
|
||||
_patternW = (int)System.Math.Round(patternBox.Width * 10);
|
||||
_patternL = (int)System.Math.Round(patternBox.Length * 10);
|
||||
_workW = (int)System.Math.Round(workArea.Width * 10);
|
||||
_workL = (int)System.Math.Round(workArea.Length * 10);
|
||||
_dir = (int)dir;
|
||||
}
|
||||
|
||||
public bool Equals(GridKey other) =>
|
||||
_patternW == other._patternW && _patternL == other._patternL &&
|
||||
_workW == other._workW && _workL == other._workL &&
|
||||
_dir == other._dir;
|
||||
|
||||
public override bool Equals(object obj) => obj is GridKey other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hash = _patternW;
|
||||
hash = hash * 397 ^ _patternL;
|
||||
hash = hash * 397 ^ _workW;
|
||||
hash = hash * 397 ^ _workL;
|
||||
hash = hash * 397 ^ _dir;
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,8 @@ namespace OpenNest.Engine.Fill
|
||||
double spacing,
|
||||
CancellationToken token = default,
|
||||
IProgress<NestProgress> progress = null,
|
||||
int plateNumber = 0)
|
||||
int plateNumber = 0,
|
||||
Func<NestItem, Box, List<Part>> widthFillFunc = null)
|
||||
{
|
||||
if (items == null || items.Count == 0)
|
||||
return new IterativeShrinkResult();
|
||||
@@ -72,6 +73,8 @@ namespace OpenNest.Engine.Fill
|
||||
// include them in progress reports.
|
||||
var placedSoFar = new List<Part>();
|
||||
|
||||
var wFillFunc = widthFillFunc ?? fillFunc;
|
||||
|
||||
Func<NestItem, Box, List<Part>> shrinkWrapper = (ni, box) =>
|
||||
{
|
||||
var target = ni.Quantity > 0 ? ni.Quantity : 0;
|
||||
@@ -84,7 +87,7 @@ namespace OpenNest.Engine.Fill
|
||||
Parallel.Invoke(
|
||||
() => heightResult = ShrinkFiller.Shrink(fillFunc, ni, box, spacing, ShrinkAxis.Height, token,
|
||||
targetCount: target, progress: progress, plateNumber: plateNumber, placedParts: placedSoFar),
|
||||
() => widthResult = ShrinkFiller.Shrink(fillFunc, ni, box, spacing, ShrinkAxis.Width, token,
|
||||
() => widthResult = ShrinkFiller.Shrink(wFillFunc, ni, box, spacing, ShrinkAxis.Width, token,
|
||||
targetCount: target, progress: progress, plateNumber: plateNumber, placedParts: placedSoFar)
|
||||
);
|
||||
|
||||
@@ -108,8 +111,15 @@ namespace OpenNest.Engine.Fill
|
||||
var allParts = new List<Part>(placedSoFar.Count + best.Count);
|
||||
allParts.AddRange(placedSoFar);
|
||||
allParts.AddRange(best);
|
||||
NestEngineBase.ReportProgress(progress, NestPhase.Custom, plateNumber,
|
||||
allParts, box, $"Shrink: {best.Count} parts placed", isOverallBest: true);
|
||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Custom,
|
||||
PlateNumber = plateNumber,
|
||||
Parts = allParts,
|
||||
WorkArea = box,
|
||||
Description = $"Shrink: {best.Count} parts placed",
|
||||
IsOverallBest = true,
|
||||
});
|
||||
}
|
||||
|
||||
// Accumulate for the next item's progress reports.
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Engine;
|
||||
|
||||
namespace OpenNest.Engine.Fill
|
||||
@@ -32,13 +33,15 @@ namespace OpenNest.Engine.Fill
|
||||
private readonly Size plateSize;
|
||||
private readonly double partSpacing;
|
||||
private readonly IFillComparer comparer;
|
||||
private readonly GridDedup dedup;
|
||||
|
||||
public PairFiller(Plate plate, IFillComparer comparer = null)
|
||||
public PairFiller(Plate plate, IFillComparer comparer = null, GridDedup dedup = null)
|
||||
{
|
||||
this.plate = plate;
|
||||
this.plateSize = plate.Size;
|
||||
this.partSpacing = plate.PartSpacing;
|
||||
this.comparer = comparer ?? new DefaultFillComparer();
|
||||
this.dedup = dedup ?? new GridDedup();
|
||||
}
|
||||
|
||||
public PairFillResult Fill(NestItem item, Box workArea,
|
||||
@@ -68,32 +71,60 @@ namespace OpenNest.Engine.Fill
|
||||
List<Part> best = null;
|
||||
var sinceImproved = 0;
|
||||
var effectiveWorkArea = workArea;
|
||||
var batchSize = System.Math.Max(2, Environment.ProcessorCount);
|
||||
|
||||
var maxUtilization = candidates.Count > 0 ? candidates.Max(c => c.Utilization) : 1.0;
|
||||
var partBox = drawing.Program.BoundingBox();
|
||||
var partArea = System.Math.Max(partBox.Width * partBox.Length, 1);
|
||||
|
||||
FillStrategyRegistry.SetEnabled("Pairs", "RectBestFit", "Extents", "Linear");
|
||||
try
|
||||
{
|
||||
for (var i = 0; i < candidates.Count; i++)
|
||||
for (var batchStart = 0; batchStart < candidates.Count; batchStart += batchSize)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var filled = EvaluateCandidate(candidates[i], drawing, effectiveWorkArea, token);
|
||||
var batchEnd = System.Math.Min(batchStart + batchSize, candidates.Count);
|
||||
var batchCount = batchEnd - batchStart;
|
||||
var batchWorkArea = effectiveWorkArea;
|
||||
var minCountToBeat = best?.Count ?? 0;
|
||||
|
||||
if (comparer.IsBetter(filled, best, effectiveWorkArea))
|
||||
var results = new List<Part>[batchCount];
|
||||
Parallel.For(0, batchCount,
|
||||
new ParallelOptions { CancellationToken = token },
|
||||
j =>
|
||||
{
|
||||
best = filled;
|
||||
results[j] = EvaluateCandidate(
|
||||
candidates[batchStart + j], drawing, batchWorkArea,
|
||||
minCountToBeat, maxUtilization, partArea, token);
|
||||
});
|
||||
|
||||
for (var j = 0; j < batchCount; j++)
|
||||
{
|
||||
if (comparer.IsBetter(results[j], best, effectiveWorkArea))
|
||||
{
|
||||
best = results[j];
|
||||
sinceImproved = 0;
|
||||
effectiveWorkArea = TryReduceWorkArea(filled, targetCount, workArea, effectiveWorkArea);
|
||||
effectiveWorkArea = TryReduceWorkArea(best, targetCount, workArea, effectiveWorkArea);
|
||||
}
|
||||
else
|
||||
{
|
||||
sinceImproved++;
|
||||
}
|
||||
|
||||
NestEngineBase.ReportProgress(progress, NestPhase.Pairs, plateNumber, best, workArea,
|
||||
$"Pairs: {i + 1}/{candidates.Count} candidates, best = {best?.Count ?? 0} parts");
|
||||
|
||||
if (i + 1 >= EarlyExitMinTried && sinceImproved >= EarlyExitStaleLimit)
|
||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Debug.WriteLine($"[PairFiller] Early exit at {i + 1}/{candidates.Count} — no improvement in last {sinceImproved} candidates");
|
||||
Phase = NestPhase.Pairs,
|
||||
PlateNumber = plateNumber,
|
||||
Parts = best,
|
||||
WorkArea = workArea,
|
||||
Description = $"Pairs: {batchStart + j + 1}/{candidates.Count} candidates, best = {best?.Count ?? 0} parts",
|
||||
});
|
||||
}
|
||||
|
||||
if (batchEnd >= EarlyExitMinTried && sinceImproved >= EarlyExitStaleLimit)
|
||||
{
|
||||
Debug.WriteLine($"[PairFiller] Early exit at {batchEnd}/{candidates.Count} — no improvement in last {sinceImproved} candidates");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -102,6 +133,10 @@ namespace OpenNest.Engine.Fill
|
||||
{
|
||||
Debug.WriteLine("[PairFiller] Cancelled mid-phase, using results so far");
|
||||
}
|
||||
finally
|
||||
{
|
||||
FillStrategyRegistry.SetEnabled(null);
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[PairFiller] Best pair result: {best?.Count ?? 0} parts");
|
||||
return best ?? new List<Part>();
|
||||
@@ -145,7 +180,8 @@ namespace OpenNest.Engine.Fill
|
||||
}
|
||||
|
||||
private List<Part> EvaluateCandidate(BestFitResult candidate, Drawing drawing,
|
||||
Box workArea, CancellationToken token)
|
||||
Box workArea, int minCountToBeat, double maxUtilization, double partArea,
|
||||
CancellationToken token)
|
||||
{
|
||||
var pairParts = candidate.BuildParts(drawing);
|
||||
var angles = BuildTilingAngles(candidate);
|
||||
@@ -162,6 +198,9 @@ namespace OpenNest.Engine.Fill
|
||||
var engine = new FillLinear(workArea, partSpacing);
|
||||
foreach (var dir in new[] { NestDirection.Horizontal, NestDirection.Vertical })
|
||||
{
|
||||
if (!dedup.TryAdd(pattern.BoundingBox, workArea, dir))
|
||||
continue;
|
||||
|
||||
var gridParts = engine.Fill(pattern, dir);
|
||||
if (gridParts != null && gridParts.Count > 0)
|
||||
grids.Add((gridParts, dir));
|
||||
@@ -174,17 +213,34 @@ namespace OpenNest.Engine.Fill
|
||||
// Sort by count descending so we try the best grids first
|
||||
grids.Sort((a, b) => b.Parts.Count.CompareTo(a.Parts.Count));
|
||||
|
||||
// Early abort: if the best grid + optimistic remnant can't beat the global best, skip Phase 2
|
||||
if (minCountToBeat > 0)
|
||||
{
|
||||
var topCount = grids[0].Parts.Count;
|
||||
var optimisticRemnant = EstimateRemnantUpperBound(
|
||||
grids[0].Parts, workArea, maxUtilization, partArea);
|
||||
if (topCount + optimisticRemnant <= minCountToBeat)
|
||||
{
|
||||
Debug.WriteLine($"[PairFiller] Skipping candidate: grid {topCount} + estimate {optimisticRemnant} <= best {minCountToBeat}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: try remnant for each grid, skip if grid is too far behind
|
||||
List<Part> best = null;
|
||||
var maxRemnantEstimate = EstimateMaxRemnantParts(drawing, workArea);
|
||||
|
||||
foreach (var (gridParts, dir) in grids)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
// If this grid + max possible remnant can't beat current best, skip
|
||||
if (best != null && gridParts.Count + maxRemnantEstimate <= best.Count)
|
||||
if (best != null)
|
||||
{
|
||||
var remnantBound = EstimateRemnantUpperBound(
|
||||
gridParts, workArea, maxUtilization, partArea);
|
||||
if (gridParts.Count + remnantBound <= best.Count)
|
||||
break; // sorted descending, so remaining are even smaller
|
||||
}
|
||||
|
||||
var remnantParts = FillRemnant(gridParts, drawing, workArea, token);
|
||||
List<Part> total;
|
||||
@@ -206,12 +262,20 @@ namespace OpenNest.Engine.Fill
|
||||
return best;
|
||||
}
|
||||
|
||||
private static int EstimateMaxRemnantParts(Drawing drawing, Box workArea)
|
||||
private int EstimateRemnantUpperBound(List<Part> gridParts, Box workArea,
|
||||
double maxUtilization, double partArea)
|
||||
{
|
||||
var partBox = drawing.Program.BoundingBox();
|
||||
var partArea = System.Math.Max(partBox.Width * partBox.Length, 1);
|
||||
var remnantArea = workArea.Area() * 0.3; // remnant is at most ~30% of work area
|
||||
return (int)(remnantArea / partArea) + 1;
|
||||
var gridBox = ((IEnumerable<IBoundable>)gridParts).GetBoundingBox();
|
||||
|
||||
// L-shaped remnant: top strip (full width) + right strip (grid height only)
|
||||
var topHeight = System.Math.Max(0, workArea.Top - gridBox.Top);
|
||||
var rightWidth = System.Math.Max(0, workArea.Right - gridBox.Right);
|
||||
|
||||
var topArea = workArea.Width * topHeight;
|
||||
var rightArea = rightWidth * System.Math.Min(gridBox.Top - workArea.Y, workArea.Length);
|
||||
var remnantArea = topArea + rightArea;
|
||||
|
||||
return (int)(remnantArea * maxUtilization / partArea) + 1;
|
||||
}
|
||||
|
||||
private List<Part> FillRemnant(List<Part> gridParts, Drawing drawing,
|
||||
@@ -257,9 +321,6 @@ namespace OpenNest.Engine.Fill
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
FillStrategyRegistry.SetEnabled("Pairs", "RectBestFit", "Extents", "Linear");
|
||||
try
|
||||
{
|
||||
var remnantEngine = NestEngineRegistry.Create(plate);
|
||||
var item = new NestItem { Drawing = drawing };
|
||||
var parts = remnantEngine.Fill(item, remnantBox, null, token);
|
||||
@@ -275,11 +336,6 @@ namespace OpenNest.Engine.Fill
|
||||
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
FillStrategyRegistry.SetEnabled(null);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<double> BuildTilingAngles(BestFitResult candidate)
|
||||
{
|
||||
|
||||
@@ -79,8 +79,14 @@ namespace OpenNest.Engine.Fill
|
||||
|
||||
var desc = $"Shrink {axis}: {bestParts.Count} parts, dim={dim:F1}";
|
||||
|
||||
NestEngineBase.ReportProgress(progress, NestPhase.Custom, plateNumber,
|
||||
allParts, workArea, desc);
|
||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Custom,
|
||||
PlateNumber = plateNumber,
|
||||
Parts = allParts,
|
||||
WorkArea = workArea,
|
||||
Description = desc,
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -20,6 +20,7 @@ public class StripeFiller
|
||||
private readonly FillContext _context;
|
||||
private readonly NestDirection _primaryAxis;
|
||||
private readonly IFillComparer _comparer;
|
||||
private readonly GridDedup _dedup;
|
||||
|
||||
/// <summary>
|
||||
/// When true, only complete stripes are placed — no partial rows/columns.
|
||||
@@ -38,6 +39,7 @@ public class StripeFiller
|
||||
_context = context;
|
||||
_primaryAxis = primaryAxis;
|
||||
_comparer = context.Policy?.Comparer ?? new DefaultFillComparer();
|
||||
_dedup = GridDedup.GetOrCreate(context.SharedState);
|
||||
}
|
||||
|
||||
public List<Part> Fill()
|
||||
@@ -93,9 +95,14 @@ public class StripeFiller
|
||||
}
|
||||
}
|
||||
|
||||
NestEngineBase.ReportProgress(_context.Progress, NestPhase.Custom,
|
||||
_context.PlateNumber, bestParts, workArea,
|
||||
$"{strategyName}: {i + 1}/{bestFits.Count} pairs, best = {bestParts?.Count ?? 0} parts");
|
||||
NestEngineBase.ReportProgress(_context.Progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Custom,
|
||||
PlateNumber = _context.PlateNumber,
|
||||
Parts = bestParts,
|
||||
WorkArea = workArea,
|
||||
Description = $"{strategyName}: {i + 1}/{bestFits.Count} pairs, best = {bestParts?.Count ?? 0} parts",
|
||||
});
|
||||
}
|
||||
|
||||
return bestParts ?? new List<Part>();
|
||||
@@ -110,6 +117,10 @@ public class StripeFiller
|
||||
var rotatedPattern = FillHelpers.BuildRotatedPattern(pairParts, angle);
|
||||
var perpDim = GetDimension(rotatedPattern.BoundingBox, perpAxis);
|
||||
var stripeBox = MakeStripeBox(workArea, perpDim, primaryAxis);
|
||||
|
||||
if (!_dedup.TryAdd(rotatedPattern.BoundingBox, workArea, primaryAxis))
|
||||
return null;
|
||||
|
||||
var stripeEngine = new FillLinear(stripeBox, spacing);
|
||||
var stripeParts = stripeEngine.Fill(rotatedPattern, primaryAxis);
|
||||
|
||||
|
||||
@@ -210,55 +210,26 @@ namespace OpenNest
|
||||
// --- Protected utilities ---
|
||||
|
||||
internal static void ReportProgress(
|
||||
IProgress<NestProgress> progress,
|
||||
NestPhase phase,
|
||||
int plateNumber,
|
||||
List<Part> best,
|
||||
Box workArea,
|
||||
string description,
|
||||
bool isOverallBest = false)
|
||||
IProgress<NestProgress> progress, ProgressReport report)
|
||||
{
|
||||
if (progress == null || best == null || best.Count == 0)
|
||||
if (progress == null || report.Parts == null || report.Parts.Count == 0)
|
||||
return;
|
||||
|
||||
var score = FillScore.Compute(best, workArea);
|
||||
var clonedParts = new List<Part>(best.Count);
|
||||
var totalPartArea = 0.0;
|
||||
|
||||
foreach (var part in best)
|
||||
{
|
||||
var clonedParts = new List<Part>(report.Parts.Count);
|
||||
foreach (var part in report.Parts)
|
||||
clonedParts.Add((Part)part.Clone());
|
||||
totalPartArea += part.BaseDrawing.Area;
|
||||
}
|
||||
|
||||
var bounds = best.GetBoundingBox();
|
||||
|
||||
var msg = $"[Progress] Phase={phase}, Plate={plateNumber}, Parts={score.Count}, " +
|
||||
$"Density={score.Density:P1}, Nested={bounds.Width:F1}x{bounds.Length:F1}, " +
|
||||
$"PartArea={totalPartArea:F0}, Remnant={workArea.Area() - totalPartArea:F0}, " +
|
||||
$"WorkArea={workArea.Width:F1}x{workArea.Length:F1} | {description}";
|
||||
Debug.WriteLine(msg);
|
||||
try
|
||||
{
|
||||
System.IO.File.AppendAllText(
|
||||
System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "nest-debug.log"),
|
||||
$"{DateTime.Now:HH:mm:ss.fff} {msg}\n");
|
||||
}
|
||||
catch { }
|
||||
Debug.WriteLine($"[Progress] Phase={report.Phase}, Plate={report.PlateNumber}, " +
|
||||
$"Parts={clonedParts.Count} | {report.Description}");
|
||||
|
||||
progress.Report(new NestProgress
|
||||
{
|
||||
Phase = phase,
|
||||
PlateNumber = plateNumber,
|
||||
BestPartCount = score.Count,
|
||||
BestDensity = score.Density,
|
||||
NestedWidth = bounds.Width,
|
||||
NestedLength = bounds.Length,
|
||||
NestedArea = totalPartArea,
|
||||
Phase = report.Phase,
|
||||
PlateNumber = report.PlateNumber,
|
||||
BestParts = clonedParts,
|
||||
Description = description,
|
||||
ActiveWorkArea = workArea,
|
||||
IsOverallBest = isOverallBest,
|
||||
Description = report.Description,
|
||||
ActiveWorkArea = report.WorkArea,
|
||||
IsOverallBest = report.IsOverallBest,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -270,7 +241,7 @@ namespace OpenNest
|
||||
var parts = new List<string>(PhaseResults.Count);
|
||||
|
||||
foreach (var r in PhaseResults)
|
||||
parts.Add($"{FormatPhaseName(r.Phase)}: {r.PartCount}");
|
||||
parts.Add($"{r.Phase.ShortName()}: {r.PartCount}");
|
||||
|
||||
return string.Join(" | ", parts);
|
||||
}
|
||||
@@ -323,17 +294,5 @@ namespace OpenNest
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static string FormatPhaseName(NestPhase phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case NestPhase.Pairs: return "Pairs";
|
||||
case NestPhase.Linear: return "Linear";
|
||||
case NestPhase.RectBestFit: return "BestFit";
|
||||
case NestPhase.Extents: return "Extents";
|
||||
case NestPhase.Custom: return "Custom";
|
||||
default: return phase.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+123
-12
@@ -1,16 +1,52 @@
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reflection;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
internal class ShortNameAttribute(string name) : Attribute
|
||||
{
|
||||
public string Name { get; } = name;
|
||||
}
|
||||
|
||||
public enum NestPhase
|
||||
{
|
||||
Linear,
|
||||
RectBestFit,
|
||||
Pairs,
|
||||
Nfp,
|
||||
Extents,
|
||||
Custom
|
||||
[Description("Trying rotations..."), ShortName("Linear")] Linear,
|
||||
[Description("Trying best fit..."), ShortName("BestFit")] RectBestFit,
|
||||
[Description("Trying pairs..."), ShortName("Pairs")] Pairs,
|
||||
[Description("Trying NFP..."), ShortName("NFP")] Nfp,
|
||||
[Description("Trying extents..."), ShortName("Extents")] Extents,
|
||||
[Description("Custom"), ShortName("Custom")] Custom
|
||||
}
|
||||
|
||||
public static class NestPhaseExtensions
|
||||
{
|
||||
private static readonly ConcurrentDictionary<NestPhase, string> DisplayNames = new();
|
||||
private static readonly ConcurrentDictionary<NestPhase, string> ShortNames = new();
|
||||
|
||||
public static string DisplayName(this NestPhase phase)
|
||||
{
|
||||
return DisplayNames.GetOrAdd(phase, p =>
|
||||
{
|
||||
var field = typeof(NestPhase).GetField(p.ToString());
|
||||
var attr = field?.GetCustomAttribute<DescriptionAttribute>();
|
||||
return attr?.Description ?? p.ToString();
|
||||
});
|
||||
}
|
||||
|
||||
public static string ShortName(this NestPhase phase)
|
||||
{
|
||||
return ShortNames.GetOrAdd(phase, p =>
|
||||
{
|
||||
var field = typeof(NestPhase).GetField(p.ToString());
|
||||
var attr = field?.GetCustomAttribute<ShortNameAttribute>();
|
||||
return attr?.Name ?? p.ToString();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class PhaseResult
|
||||
@@ -34,18 +70,93 @@ namespace OpenNest
|
||||
public int PartCount { get; set; }
|
||||
}
|
||||
|
||||
internal readonly struct ProgressReport
|
||||
{
|
||||
public NestPhase Phase { get; init; }
|
||||
public int PlateNumber { get; init; }
|
||||
public List<Part> Parts { get; init; }
|
||||
public Box WorkArea { get; init; }
|
||||
public string Description { get; init; }
|
||||
public bool IsOverallBest { get; init; }
|
||||
}
|
||||
|
||||
public class NestProgress
|
||||
{
|
||||
public NestPhase Phase { get; set; }
|
||||
public int PlateNumber { get; set; }
|
||||
public int BestPartCount { get; set; }
|
||||
public double BestDensity { get; set; }
|
||||
public double NestedWidth { get; set; }
|
||||
public double NestedLength { get; set; }
|
||||
public double NestedArea { get; set; }
|
||||
public List<Part> BestParts { get; set; }
|
||||
|
||||
private List<Part> bestParts;
|
||||
public List<Part> BestParts
|
||||
{
|
||||
get => bestParts;
|
||||
set { bestParts = value; cachedParts = null; }
|
||||
}
|
||||
|
||||
public string Description { get; set; }
|
||||
public Box ActiveWorkArea { get; set; }
|
||||
public bool IsOverallBest { get; set; }
|
||||
|
||||
public int BestPartCount => BestParts?.Count ?? 0;
|
||||
|
||||
private List<Part> cachedParts;
|
||||
private Box cachedBounds;
|
||||
private double cachedPartArea;
|
||||
|
||||
private void EnsureCache()
|
||||
{
|
||||
if (cachedParts == bestParts) return;
|
||||
cachedParts = bestParts;
|
||||
if (bestParts == null || bestParts.Count == 0)
|
||||
{
|
||||
cachedBounds = default;
|
||||
cachedPartArea = 0;
|
||||
return;
|
||||
}
|
||||
cachedBounds = bestParts.GetBoundingBox();
|
||||
cachedPartArea = 0;
|
||||
foreach (var p in bestParts)
|
||||
cachedPartArea += p.BaseDrawing.Area;
|
||||
}
|
||||
|
||||
public double BestDensity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (BestParts == null || BestParts.Count == 0) return 0;
|
||||
EnsureCache();
|
||||
var bboxArea = cachedBounds.Width * cachedBounds.Length;
|
||||
return bboxArea > 0 ? cachedPartArea / bboxArea : 0;
|
||||
}
|
||||
}
|
||||
|
||||
public double NestedWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
if (BestParts == null || BestParts.Count == 0) return 0;
|
||||
EnsureCache();
|
||||
return cachedBounds.Width;
|
||||
}
|
||||
}
|
||||
|
||||
public double NestedLength
|
||||
{
|
||||
get
|
||||
{
|
||||
if (BestParts == null || BestParts.Count == 0) return 0;
|
||||
EnsureCache();
|
||||
return cachedBounds.Length;
|
||||
}
|
||||
}
|
||||
|
||||
public double NestedArea
|
||||
{
|
||||
get
|
||||
{
|
||||
if (BestParts == null || BestParts.Count == 0) return 0;
|
||||
EnsureCache();
|
||||
return cachedPartArea;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,15 @@ namespace OpenNest.Engine.Nfp
|
||||
|
||||
Debug.WriteLine($"[AutoNest] Result: {parts.Count} parts placed, {result.Iterations} SA iterations");
|
||||
|
||||
NestEngineBase.ReportProgress(progress, NestPhase.Nfp, 0, parts, workArea,
|
||||
$"NFP: {parts.Count} parts, {result.Iterations} iterations", isOverallBest: true);
|
||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Nfp,
|
||||
PlateNumber = 0,
|
||||
Parts = parts,
|
||||
WorkArea = workArea,
|
||||
Description = $"NFP: {parts.Count} parts, {result.Iterations} iterations",
|
||||
IsOverallBest = true,
|
||||
});
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
@@ -277,8 +277,15 @@ namespace OpenNest.Engine.Nfp
|
||||
private static void ReportBest(IProgress<NestProgress> progress, List<Part> parts,
|
||||
Box workArea, string description)
|
||||
{
|
||||
NestEngineBase.ReportProgress(progress, NestPhase.Nfp, 0, parts, workArea,
|
||||
description, isOverallBest: true);
|
||||
NestEngineBase.ReportProgress(progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Nfp,
|
||||
PlateNumber = 0,
|
||||
Parts = parts,
|
||||
WorkArea = workArea,
|
||||
Description = description,
|
||||
IsOverallBest = true,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,9 +47,14 @@ namespace OpenNest.Engine.Strategies
|
||||
best = result;
|
||||
}
|
||||
|
||||
NestEngineBase.ReportProgress(context.Progress, NestPhase.Linear,
|
||||
context.PlateNumber, best, workArea,
|
||||
$"Linear: {ai + 1}/{angles.Count} angles, {angleDeg:F0}° best = {best?.Count ?? 0} parts");
|
||||
NestEngineBase.ReportProgress(context.Progress, new ProgressReport
|
||||
{
|
||||
Phase = NestPhase.Linear,
|
||||
PlateNumber = context.PlateNumber,
|
||||
Parts = best,
|
||||
WorkArea = workArea,
|
||||
Description = $"Linear: {ai + 1}/{angles.Count} angles, {angleDeg:F0}° best = {best?.Count ?? 0} parts",
|
||||
});
|
||||
}
|
||||
|
||||
return best ?? new List<Part>();
|
||||
|
||||
@@ -12,7 +12,8 @@ namespace OpenNest.Engine.Strategies
|
||||
public List<Part> Fill(FillContext context)
|
||||
{
|
||||
var comparer = context.Policy?.Comparer;
|
||||
var filler = new PairFiller(context.Plate, comparer);
|
||||
var dedup = GridDedup.GetOrCreate(context.SharedState);
|
||||
var filler = new PairFiller(context.Plate, comparer, dedup);
|
||||
var result = filler.Fill(context.Item, context.WorkArea,
|
||||
context.PlateNumber, context.Token, context.Progress);
|
||||
|
||||
|
||||
@@ -77,17 +77,23 @@ namespace OpenNest
|
||||
// Phase 1: Iterative shrink-fill for multi-quantity items.
|
||||
if (fillItems.Count > 0)
|
||||
{
|
||||
// Pass progress through so the UI shows intermediate results
|
||||
// during the initial BestFitCache computation and fill phases.
|
||||
Func<NestItem, Box, List<Part>> fillFunc = (ni, b) =>
|
||||
// Use direction-specific engines: height shrink benefits from
|
||||
// minimizing Y-extent, width shrink from minimizing X-extent.
|
||||
Func<NestItem, Box, List<Part>> heightFillFunc = (ni, b) =>
|
||||
{
|
||||
var inner = new DefaultNestEngine(Plate);
|
||||
var inner = new HorizontalRemnantEngine(Plate);
|
||||
return inner.Fill(ni, b, progress, token);
|
||||
};
|
||||
|
||||
Func<NestItem, Box, List<Part>> widthFillFunc = (ni, b) =>
|
||||
{
|
||||
var inner = new VerticalRemnantEngine(Plate);
|
||||
return inner.Fill(ni, b, progress, token);
|
||||
};
|
||||
|
||||
var shrinkResult = IterativeShrinkFiller.Fill(
|
||||
fillItems, workArea, fillFunc, Plate.PartSpacing, token,
|
||||
progress, PlateNumber);
|
||||
fillItems, workArea, heightFillFunc, Plate.PartSpacing, token,
|
||||
progress, PlateNumber, widthFillFunc);
|
||||
|
||||
allParts.AddRange(shrinkResult.Parts);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public class AccumulatingProgressTests
|
||||
var accumulating = new AccumulatingProgress(inner, previous);
|
||||
|
||||
var newParts = new List<Part> { TestHelpers.MakePartAt(20, 0, 10) };
|
||||
accumulating.Report(new NestProgress { BestParts = newParts, BestPartCount = 1 });
|
||||
accumulating.Report(new NestProgress { BestParts = newParts });
|
||||
|
||||
Assert.NotNull(inner.Last);
|
||||
Assert.Equal(2, inner.Last.BestParts.Count);
|
||||
@@ -32,7 +32,7 @@ public class AccumulatingProgressTests
|
||||
var accumulating = new AccumulatingProgress(inner, new List<Part>());
|
||||
|
||||
var newParts = new List<Part> { TestHelpers.MakePartAt(0, 0, 10) };
|
||||
accumulating.Report(new NestProgress { BestParts = newParts, BestPartCount = 1 });
|
||||
accumulating.Report(new NestProgress { BestParts = newParts });
|
||||
|
||||
Assert.NotNull(inner.Last);
|
||||
Assert.Single(inner.Last.BestParts);
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
namespace OpenNest.Tests;
|
||||
|
||||
public class BestCombinationTests
|
||||
{
|
||||
[Fact]
|
||||
public void BothFit_FindsZeroRemnant()
|
||||
{
|
||||
// 100 = 0*30 + 5*20 (algorithm iterates from countLength1=0, finds zero remnant first)
|
||||
var result = BestCombination.FindFrom2(30, 20, 100, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(0.0, 100.0 - (c1 * 30.0 + c2 * 20.0), 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnlyLength1Fits_ReturnsMaxCount1()
|
||||
{
|
||||
var result = BestCombination.FindFrom2(10, 200, 50, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(5, c1);
|
||||
Assert.Equal(0, c2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnlyLength2Fits_ReturnsMaxCount2()
|
||||
{
|
||||
var result = BestCombination.FindFrom2(200, 10, 50, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(0, c1);
|
||||
Assert.Equal(5, c2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NeitherFits_ReturnsFalse()
|
||||
{
|
||||
var result = BestCombination.FindFrom2(100, 200, 50, out var c1, out var c2);
|
||||
|
||||
Assert.False(result);
|
||||
Assert.Equal(0, c1);
|
||||
Assert.Equal(0, c2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Length1FillsExactly_ZeroRemnant()
|
||||
{
|
||||
var result = BestCombination.FindFrom2(25, 10, 100, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(0.0, 100.0 - (c1 * 25.0 + c2 * 10.0), 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MixMinimizesRemnant()
|
||||
{
|
||||
// 7 and 3 into 20: best is 2*7 + 2*3 = 20 (zero remnant)
|
||||
var result = BestCombination.FindFrom2(7, 3, 20, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(2, c1);
|
||||
Assert.Equal(2, c2);
|
||||
Assert.True(c1 * 7 + c2 * 3 <= 20);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PrefersLessRemnant_OverMoreOfLength1()
|
||||
{
|
||||
// 6 and 5 into 17:
|
||||
// all length1: 2*6=12, remnant=5 -> actually 2*6+1*5=17 perfect
|
||||
var result = BestCombination.FindFrom2(6, 5, 17, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(0.0, 17.0 - (c1 * 6.0 + c2 * 5.0), 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EqualLengths_FillsWithLength1()
|
||||
{
|
||||
var result = BestCombination.FindFrom2(10, 10, 50, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(5, c1 + c2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SmallLengths_LargeOverall()
|
||||
{
|
||||
var result = BestCombination.FindFrom2(3, 7, 100, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
var used = c1 * 3.0 + c2 * 7.0;
|
||||
Assert.True(used <= 100);
|
||||
Assert.True(100 - used < 3); // remnant less than smallest piece
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Length2IsBetter_SoleCandidate()
|
||||
{
|
||||
// length1=9, length2=5, overall=10:
|
||||
// length1 alone: 1*9=9 remnant=1
|
||||
// length2 alone: 2*5=10 remnant=0
|
||||
var result = BestCombination.FindFrom2(9, 5, 10, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(0, c1);
|
||||
Assert.Equal(2, c2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FractionalLengths_WorkCorrectly()
|
||||
{
|
||||
var result = BestCombination.FindFrom2(2.5, 3.5, 12, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
var used = c1 * 2.5 + c2 * 3.5;
|
||||
Assert.True(used <= 12.0 + 0.001);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OverallExactlyOneOfEach()
|
||||
{
|
||||
var result = BestCombination.FindFrom2(40, 60, 100, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(1, c1);
|
||||
Assert.Equal(1, c2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OverallSmallerThanEither_ReturnsFalse()
|
||||
{
|
||||
var result = BestCombination.FindFrom2(10, 20, 5, out var c1, out var c2);
|
||||
|
||||
Assert.False(result);
|
||||
Assert.Equal(0, c1);
|
||||
Assert.Equal(0, c2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ZeroRemnant_StopsEarly()
|
||||
{
|
||||
// 4 and 6 into 24: 0*4+4*6=24 or 3*4+2*6=24 or 6*4+0*6=24
|
||||
// Algorithm iterates from 0 length1 upward, finds zero remnant and breaks
|
||||
var result = BestCombination.FindFrom2(4, 6, 24, out var c1, out var c2);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(0.0, 24.0 - (c1 * 4.0 + c2 * 6.0), 5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace OpenNest.Tests;
|
||||
|
||||
public class NestPhaseExtensionsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(NestPhase.Linear, "Trying rotations...")]
|
||||
[InlineData(NestPhase.RectBestFit, "Trying best fit...")]
|
||||
[InlineData(NestPhase.Pairs, "Trying pairs...")]
|
||||
[InlineData(NestPhase.Nfp, "Trying NFP...")]
|
||||
[InlineData(NestPhase.Extents, "Trying extents...")]
|
||||
[InlineData(NestPhase.Custom, "Custom")]
|
||||
public void DisplayName_ReturnsDescription(NestPhase phase, string expected)
|
||||
{
|
||||
Assert.Equal(expected, phase.DisplayName());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(NestPhase.Linear, "Linear")]
|
||||
[InlineData(NestPhase.RectBestFit, "BestFit")]
|
||||
[InlineData(NestPhase.Pairs, "Pairs")]
|
||||
[InlineData(NestPhase.Nfp, "NFP")]
|
||||
[InlineData(NestPhase.Extents, "Extents")]
|
||||
[InlineData(NestPhase.Custom, "Custom")]
|
||||
public void ShortName_ReturnsShortLabel(NestPhase phase, string expected)
|
||||
{
|
||||
Assert.Equal(expected, phase.ShortName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Tests;
|
||||
|
||||
public class NestProgressTests
|
||||
{
|
||||
[Fact]
|
||||
public void BestPartCount_NullParts_ReturnsZero()
|
||||
{
|
||||
var progress = new NestProgress { BestParts = null };
|
||||
Assert.Equal(0, progress.BestPartCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BestPartCount_ReturnsBestPartsCount()
|
||||
{
|
||||
var parts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 5),
|
||||
TestHelpers.MakePartAt(10, 0, 5),
|
||||
};
|
||||
var progress = new NestProgress { BestParts = parts };
|
||||
Assert.Equal(2, progress.BestPartCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BestDensity_NullParts_ReturnsZero()
|
||||
{
|
||||
var progress = new NestProgress { BestParts = null };
|
||||
Assert.Equal(0, progress.BestDensity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BestDensity_MatchesFillScoreFormula()
|
||||
{
|
||||
var parts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 5),
|
||||
TestHelpers.MakePartAt(5, 0, 5),
|
||||
};
|
||||
var workArea = new Box(0, 0, 100, 100);
|
||||
var progress = new NestProgress { BestParts = parts, ActiveWorkArea = workArea };
|
||||
Assert.Equal(1.0, progress.BestDensity, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NestedWidth_ReturnsPartsSpan()
|
||||
{
|
||||
var parts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 5),
|
||||
TestHelpers.MakePartAt(10, 0, 5),
|
||||
};
|
||||
var progress = new NestProgress { BestParts = parts };
|
||||
Assert.Equal(15, progress.NestedWidth, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NestedLength_ReturnsPartsSpan()
|
||||
{
|
||||
var parts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 5),
|
||||
TestHelpers.MakePartAt(0, 10, 5),
|
||||
};
|
||||
var progress = new NestProgress { BestParts = parts };
|
||||
Assert.Equal(15, progress.NestedLength, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NestedArea_ReturnsSumOfPartAreas()
|
||||
{
|
||||
var parts = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 5),
|
||||
TestHelpers.MakePartAt(10, 0, 5),
|
||||
};
|
||||
var progress = new NestProgress { BestParts = parts };
|
||||
Assert.Equal(50, progress.NestedArea, precision: 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SettingBestParts_InvalidatesCache()
|
||||
{
|
||||
var parts1 = new List<Part> { TestHelpers.MakePartAt(0, 0, 5) };
|
||||
var parts2 = new List<Part>
|
||||
{
|
||||
TestHelpers.MakePartAt(0, 0, 5),
|
||||
TestHelpers.MakePartAt(10, 0, 5),
|
||||
};
|
||||
|
||||
var progress = new NestProgress { BestParts = parts1 };
|
||||
Assert.Equal(1, progress.BestPartCount);
|
||||
Assert.Equal(25, progress.NestedArea, precision: 4);
|
||||
|
||||
progress.BestParts = parts2;
|
||||
Assert.Equal(2, progress.BestPartCount);
|
||||
Assert.Equal(50, progress.NestedArea, precision: 4);
|
||||
}
|
||||
}
|
||||
@@ -59,16 +59,6 @@ namespace OpenNest.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDisplayName(NestPhase phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case NestPhase.RectBestFit: return "BestFit";
|
||||
case NestPhase.Nfp: return "NFP";
|
||||
default: return phase.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
base.OnPaint(e);
|
||||
@@ -134,7 +124,7 @@ namespace OpenNest.Controls
|
||||
}
|
||||
|
||||
// Label
|
||||
var label = GetDisplayName(phase);
|
||||
var label = phase.ShortName();
|
||||
var font = isVisited || isActive ? BoldLabelFont : LabelFont;
|
||||
var brush = isVisited || isActive ? activeTextBrush : pendingTextBrush;
|
||||
var labelSize = g.MeasureString(label, font);
|
||||
|
||||
@@ -1098,23 +1098,16 @@ namespace OpenNest.Controls
|
||||
var bounds = parts.GetBoundingBox();
|
||||
var center = bounds.Center;
|
||||
var anchor = bounds.Location;
|
||||
var rotatedPrograms = new HashSet<Program>();
|
||||
|
||||
for (int i = 0; i < SelectedParts.Count; ++i)
|
||||
for (var i = 0; i < SelectedParts.Count; ++i)
|
||||
{
|
||||
var part = SelectedParts[i];
|
||||
var basePart = part.BasePart;
|
||||
|
||||
if (rotatedPrograms.Add(basePart.Program))
|
||||
basePart.Program.Rotate(angle);
|
||||
|
||||
part.Location = part.Location.Rotate(angle, center);
|
||||
basePart.UpdateBounds();
|
||||
part.BasePart.Rotate(angle, center);
|
||||
}
|
||||
|
||||
var diff = anchor - parts.GetBoundingBox().Location;
|
||||
|
||||
for (int i = 0; i < SelectedParts.Count; ++i)
|
||||
for (var i = 0; i < SelectedParts.Count; ++i)
|
||||
SelectedParts[i].Offset(diff);
|
||||
}
|
||||
|
||||
|
||||
+32
-32
@@ -85,13 +85,13 @@ namespace OpenNest.Forms
|
||||
resultsTable.Controls.Add(nestedAreaLabel, 0, 2);
|
||||
resultsTable.Controls.Add(nestedAreaValue, 1, 2);
|
||||
resultsTable.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
resultsTable.Location = new System.Drawing.Point(14, 29);
|
||||
resultsTable.Location = new System.Drawing.Point(14, 33);
|
||||
resultsTable.Name = "resultsTable";
|
||||
resultsTable.RowCount = 3;
|
||||
resultsTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
resultsTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
resultsTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
resultsTable.Size = new System.Drawing.Size(422, 57);
|
||||
resultsTable.Size = new System.Drawing.Size(422, 69);
|
||||
resultsTable.TabIndex = 1;
|
||||
//
|
||||
// partsLabel
|
||||
@@ -102,7 +102,7 @@ namespace OpenNest.Forms
|
||||
partsLabel.Location = new System.Drawing.Point(0, 3);
|
||||
partsLabel.Margin = new System.Windows.Forms.Padding(0, 3, 5, 3);
|
||||
partsLabel.Name = "partsLabel";
|
||||
partsLabel.Size = new System.Drawing.Size(36, 13);
|
||||
partsLabel.Size = new System.Drawing.Size(43, 17);
|
||||
partsLabel.TabIndex = 0;
|
||||
partsLabel.Text = "Parts:";
|
||||
//
|
||||
@@ -110,10 +110,10 @@ namespace OpenNest.Forms
|
||||
//
|
||||
partsValue.AutoSize = true;
|
||||
partsValue.Font = new System.Drawing.Font("Consolas", 9.75F);
|
||||
partsValue.Location = new System.Drawing.Point(80, 3);
|
||||
partsValue.Location = new System.Drawing.Point(90, 3);
|
||||
partsValue.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
partsValue.Name = "partsValue";
|
||||
partsValue.Size = new System.Drawing.Size(13, 13);
|
||||
partsValue.Size = new System.Drawing.Size(13, 15);
|
||||
partsValue.TabIndex = 1;
|
||||
partsValue.Text = "�";
|
||||
//
|
||||
@@ -122,10 +122,10 @@ namespace OpenNest.Forms
|
||||
densityLabel.AutoSize = true;
|
||||
densityLabel.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold);
|
||||
densityLabel.ForeColor = System.Drawing.Color.FromArgb(51, 51, 51);
|
||||
densityLabel.Location = new System.Drawing.Point(0, 22);
|
||||
densityLabel.Location = new System.Drawing.Point(0, 26);
|
||||
densityLabel.Margin = new System.Windows.Forms.Padding(0, 3, 5, 3);
|
||||
densityLabel.Name = "densityLabel";
|
||||
densityLabel.Size = new System.Drawing.Size(49, 13);
|
||||
densityLabel.Size = new System.Drawing.Size(59, 17);
|
||||
densityLabel.TabIndex = 2;
|
||||
densityLabel.Text = "Density:";
|
||||
//
|
||||
@@ -134,10 +134,10 @@ namespace OpenNest.Forms
|
||||
densityPanel.AutoSize = true;
|
||||
densityPanel.Controls.Add(densityValue);
|
||||
densityPanel.Controls.Add(densityBar);
|
||||
densityPanel.Location = new System.Drawing.Point(80, 19);
|
||||
densityPanel.Location = new System.Drawing.Point(90, 23);
|
||||
densityPanel.Margin = new System.Windows.Forms.Padding(0);
|
||||
densityPanel.Name = "densityPanel";
|
||||
densityPanel.Size = new System.Drawing.Size(311, 19);
|
||||
densityPanel.Size = new System.Drawing.Size(262, 21);
|
||||
densityPanel.TabIndex = 3;
|
||||
densityPanel.WrapContents = false;
|
||||
//
|
||||
@@ -148,7 +148,7 @@ namespace OpenNest.Forms
|
||||
densityValue.Location = new System.Drawing.Point(0, 3);
|
||||
densityValue.Margin = new System.Windows.Forms.Padding(0, 3, 8, 3);
|
||||
densityValue.Name = "densityValue";
|
||||
densityValue.Size = new System.Drawing.Size(13, 13);
|
||||
densityValue.Size = new System.Drawing.Size(13, 15);
|
||||
densityValue.TabIndex = 0;
|
||||
densityValue.Text = "�";
|
||||
//
|
||||
@@ -157,7 +157,7 @@ namespace OpenNest.Forms
|
||||
densityBar.Location = new System.Drawing.Point(21, 5);
|
||||
densityBar.Margin = new System.Windows.Forms.Padding(0, 5, 0, 0);
|
||||
densityBar.Name = "densityBar";
|
||||
densityBar.Size = new System.Drawing.Size(290, 8);
|
||||
densityBar.Size = new System.Drawing.Size(241, 8);
|
||||
densityBar.TabIndex = 1;
|
||||
densityBar.Value = 0D;
|
||||
//
|
||||
@@ -166,10 +166,10 @@ namespace OpenNest.Forms
|
||||
nestedAreaLabel.AutoSize = true;
|
||||
nestedAreaLabel.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold);
|
||||
nestedAreaLabel.ForeColor = System.Drawing.Color.FromArgb(51, 51, 51);
|
||||
nestedAreaLabel.Location = new System.Drawing.Point(0, 41);
|
||||
nestedAreaLabel.Location = new System.Drawing.Point(0, 49);
|
||||
nestedAreaLabel.Margin = new System.Windows.Forms.Padding(0, 3, 5, 3);
|
||||
nestedAreaLabel.Name = "nestedAreaLabel";
|
||||
nestedAreaLabel.Size = new System.Drawing.Size(47, 13);
|
||||
nestedAreaLabel.Size = new System.Drawing.Size(55, 17);
|
||||
nestedAreaLabel.TabIndex = 4;
|
||||
nestedAreaLabel.Text = "Nested:";
|
||||
//
|
||||
@@ -177,10 +177,10 @@ namespace OpenNest.Forms
|
||||
//
|
||||
nestedAreaValue.AutoSize = true;
|
||||
nestedAreaValue.Font = new System.Drawing.Font("Consolas", 9.75F);
|
||||
nestedAreaValue.Location = new System.Drawing.Point(80, 41);
|
||||
nestedAreaValue.Location = new System.Drawing.Point(90, 49);
|
||||
nestedAreaValue.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
nestedAreaValue.Name = "nestedAreaValue";
|
||||
nestedAreaValue.Size = new System.Drawing.Size(13, 13);
|
||||
nestedAreaValue.Size = new System.Drawing.Size(13, 15);
|
||||
nestedAreaValue.TabIndex = 5;
|
||||
nestedAreaValue.Text = "�";
|
||||
//
|
||||
@@ -193,7 +193,7 @@ namespace OpenNest.Forms
|
||||
resultsHeader.Location = new System.Drawing.Point(14, 10);
|
||||
resultsHeader.Name = "resultsHeader";
|
||||
resultsHeader.Padding = new System.Windows.Forms.Padding(0, 0, 0, 4);
|
||||
resultsHeader.Size = new System.Drawing.Size(56, 19);
|
||||
resultsHeader.Size = new System.Drawing.Size(65, 23);
|
||||
resultsHeader.TabIndex = 0;
|
||||
resultsHeader.Text = "RESULTS";
|
||||
//
|
||||
@@ -203,7 +203,7 @@ namespace OpenNest.Forms
|
||||
statusPanel.Controls.Add(statusTable);
|
||||
statusPanel.Controls.Add(statusHeader);
|
||||
statusPanel.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
statusPanel.Location = new System.Drawing.Point(0, 165);
|
||||
statusPanel.Location = new System.Drawing.Point(0, 180);
|
||||
statusPanel.Name = "statusPanel";
|
||||
statusPanel.Padding = new System.Windows.Forms.Padding(14, 10, 14, 10);
|
||||
statusPanel.Size = new System.Drawing.Size(450, 115);
|
||||
@@ -222,13 +222,13 @@ namespace OpenNest.Forms
|
||||
statusTable.Controls.Add(descriptionLabel, 0, 2);
|
||||
statusTable.Controls.Add(descriptionValue, 1, 2);
|
||||
statusTable.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
statusTable.Location = new System.Drawing.Point(14, 29);
|
||||
statusTable.Location = new System.Drawing.Point(14, 33);
|
||||
statusTable.Name = "statusTable";
|
||||
statusTable.RowCount = 3;
|
||||
statusTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
statusTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
statusTable.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
statusTable.Size = new System.Drawing.Size(422, 57);
|
||||
statusTable.Size = new System.Drawing.Size(422, 69);
|
||||
statusTable.TabIndex = 1;
|
||||
//
|
||||
// plateLabel
|
||||
@@ -239,7 +239,7 @@ namespace OpenNest.Forms
|
||||
plateLabel.Location = new System.Drawing.Point(0, 3);
|
||||
plateLabel.Margin = new System.Windows.Forms.Padding(0, 3, 5, 3);
|
||||
plateLabel.Name = "plateLabel";
|
||||
plateLabel.Size = new System.Drawing.Size(36, 13);
|
||||
plateLabel.Size = new System.Drawing.Size(43, 17);
|
||||
plateLabel.TabIndex = 0;
|
||||
plateLabel.Text = "Plate:";
|
||||
//
|
||||
@@ -247,10 +247,10 @@ namespace OpenNest.Forms
|
||||
//
|
||||
plateValue.AutoSize = true;
|
||||
plateValue.Font = new System.Drawing.Font("Consolas", 9.75F);
|
||||
plateValue.Location = new System.Drawing.Point(80, 3);
|
||||
plateValue.Location = new System.Drawing.Point(90, 3);
|
||||
plateValue.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
plateValue.Name = "plateValue";
|
||||
plateValue.Size = new System.Drawing.Size(13, 13);
|
||||
plateValue.Size = new System.Drawing.Size(13, 15);
|
||||
plateValue.TabIndex = 1;
|
||||
plateValue.Text = "�";
|
||||
//
|
||||
@@ -259,10 +259,10 @@ namespace OpenNest.Forms
|
||||
elapsedLabel.AutoSize = true;
|
||||
elapsedLabel.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold);
|
||||
elapsedLabel.ForeColor = System.Drawing.Color.FromArgb(51, 51, 51);
|
||||
elapsedLabel.Location = new System.Drawing.Point(0, 22);
|
||||
elapsedLabel.Location = new System.Drawing.Point(0, 26);
|
||||
elapsedLabel.Margin = new System.Windows.Forms.Padding(0, 3, 5, 3);
|
||||
elapsedLabel.Name = "elapsedLabel";
|
||||
elapsedLabel.Size = new System.Drawing.Size(50, 13);
|
||||
elapsedLabel.Size = new System.Drawing.Size(59, 17);
|
||||
elapsedLabel.TabIndex = 2;
|
||||
elapsedLabel.Text = "Elapsed:";
|
||||
//
|
||||
@@ -270,10 +270,10 @@ namespace OpenNest.Forms
|
||||
//
|
||||
elapsedValue.AutoSize = true;
|
||||
elapsedValue.Font = new System.Drawing.Font("Consolas", 9.75F);
|
||||
elapsedValue.Location = new System.Drawing.Point(80, 22);
|
||||
elapsedValue.Location = new System.Drawing.Point(90, 26);
|
||||
elapsedValue.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
elapsedValue.Name = "elapsedValue";
|
||||
elapsedValue.Size = new System.Drawing.Size(31, 13);
|
||||
elapsedValue.Size = new System.Drawing.Size(35, 15);
|
||||
elapsedValue.TabIndex = 3;
|
||||
elapsedValue.Text = "0:00";
|
||||
//
|
||||
@@ -282,10 +282,10 @@ namespace OpenNest.Forms
|
||||
descriptionLabel.AutoSize = true;
|
||||
descriptionLabel.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Bold);
|
||||
descriptionLabel.ForeColor = System.Drawing.Color.FromArgb(51, 51, 51);
|
||||
descriptionLabel.Location = new System.Drawing.Point(0, 41);
|
||||
descriptionLabel.Location = new System.Drawing.Point(0, 49);
|
||||
descriptionLabel.Margin = new System.Windows.Forms.Padding(0, 3, 5, 3);
|
||||
descriptionLabel.Name = "descriptionLabel";
|
||||
descriptionLabel.Size = new System.Drawing.Size(40, 13);
|
||||
descriptionLabel.Size = new System.Drawing.Size(49, 17);
|
||||
descriptionLabel.TabIndex = 4;
|
||||
descriptionLabel.Text = "Detail:";
|
||||
//
|
||||
@@ -293,10 +293,10 @@ namespace OpenNest.Forms
|
||||
//
|
||||
descriptionValue.AutoSize = true;
|
||||
descriptionValue.Font = new System.Drawing.Font("Segoe UI", 9.75F);
|
||||
descriptionValue.Location = new System.Drawing.Point(80, 41);
|
||||
descriptionValue.Location = new System.Drawing.Point(90, 49);
|
||||
descriptionValue.Margin = new System.Windows.Forms.Padding(0, 3, 0, 3);
|
||||
descriptionValue.Name = "descriptionValue";
|
||||
descriptionValue.Size = new System.Drawing.Size(18, 13);
|
||||
descriptionValue.Size = new System.Drawing.Size(20, 17);
|
||||
descriptionValue.TabIndex = 5;
|
||||
descriptionValue.Text = "�";
|
||||
//
|
||||
@@ -309,7 +309,7 @@ namespace OpenNest.Forms
|
||||
statusHeader.Location = new System.Drawing.Point(14, 10);
|
||||
statusHeader.Name = "statusHeader";
|
||||
statusHeader.Padding = new System.Windows.Forms.Padding(0, 0, 0, 4);
|
||||
statusHeader.Size = new System.Drawing.Size(50, 19);
|
||||
statusHeader.Size = new System.Drawing.Size(59, 23);
|
||||
statusHeader.TabIndex = 0;
|
||||
statusHeader.Text = "STATUS";
|
||||
//
|
||||
@@ -320,7 +320,7 @@ namespace OpenNest.Forms
|
||||
buttonPanel.Controls.Add(acceptButton);
|
||||
buttonPanel.Dock = System.Windows.Forms.DockStyle.Top;
|
||||
buttonPanel.FlowDirection = System.Windows.Forms.FlowDirection.RightToLeft;
|
||||
buttonPanel.Location = new System.Drawing.Point(0, 265);
|
||||
buttonPanel.Location = new System.Drawing.Point(0, 295);
|
||||
buttonPanel.Name = "buttonPanel";
|
||||
buttonPanel.Padding = new System.Windows.Forms.Padding(9, 6, 9, 6);
|
||||
buttonPanel.Size = new System.Drawing.Size(450, 45);
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace OpenNest.Forms
|
||||
|
||||
descriptionValue.Text = !string.IsNullOrEmpty(progress.Description)
|
||||
? progress.Description
|
||||
: FormatPhase(progress.Phase);
|
||||
: progress.Phase.DisplayName();
|
||||
}
|
||||
|
||||
public void ShowCompleted()
|
||||
@@ -196,18 +196,5 @@ namespace OpenNest.Forms
|
||||
return DensityMidColor;
|
||||
return DensityHighColor;
|
||||
}
|
||||
|
||||
private static string FormatPhase(NestPhase phase)
|
||||
{
|
||||
switch (phase)
|
||||
{
|
||||
case NestPhase.Linear: return "Trying rotations...";
|
||||
case NestPhase.RectBestFit: return "Trying best fit...";
|
||||
case NestPhase.Pairs: return "Trying pairs...";
|
||||
case NestPhase.Extents: return "Trying extents...";
|
||||
case NestPhase.Nfp: return "Trying NFP...";
|
||||
default: return phase.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user