style: apply CSharpier formatting to all C# sources
Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests pass after reformat, full solution builds 0 errors. Added .csharpierignore so csproj/config XML keeps its existing layout (CSharpier's XML wrapping churns attributes with zero benefit). Formatting is now enforceable: dotnet csharpier check . passes.
This commit is contained in:
@@ -44,7 +44,9 @@ public class EngineOverlapTests
|
||||
var item = new NestItem { Drawing = drawing };
|
||||
var success = engine.Fill(item);
|
||||
|
||||
_output.WriteLine($"Engine: {engine.Name}, Parts: {plate.Parts.Count}, Utilization: {plate.Utilization():P1}");
|
||||
_output.WriteLine(
|
||||
$"Engine: {engine.Name}, Parts: {plate.Parts.Count}, Utilization: {plate.Utilization():P1}"
|
||||
);
|
||||
|
||||
if (engine is DefaultNestEngine defaultEngine)
|
||||
{
|
||||
@@ -54,8 +56,8 @@ public class EngineOverlapTests
|
||||
}
|
||||
|
||||
// Show rotation distribution
|
||||
var rotGroups = plate.Parts
|
||||
.GroupBy(p => System.Math.Round(OpenNest.Math.Angle.ToDegrees(p.Rotation), 1))
|
||||
var rotGroups = plate
|
||||
.Parts.GroupBy(p => System.Math.Round(OpenNest.Math.Angle.ToDegrees(p.Rotation), 1))
|
||||
.OrderBy(g => g.Key);
|
||||
foreach (var g in rotGroups)
|
||||
_output.WriteLine($" Rotation {g.Key:F1}°: {g.Count()} parts");
|
||||
@@ -69,20 +71,28 @@ public class EngineOverlapTests
|
||||
_output.WriteLine($" ({collisionPoints[i].X:F2}, {collisionPoints[i].Y:F2})");
|
||||
}
|
||||
|
||||
Assert.False(hasOverlaps,
|
||||
$"Engine '{engineName}' produced {collisionPoints.Count} collision point(s) with {plate.Parts.Count} parts");
|
||||
Assert.False(
|
||||
hasOverlaps,
|
||||
$"Engine '{engineName}' produced {collisionPoints.Count} collision point(s) with {plate.Parts.Count} parts"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AdjacentParts_ShouldNotOverlap()
|
||||
{
|
||||
var plate = TestHelpers.MakePlate(60, 120,
|
||||
var plate = TestHelpers.MakePlate(
|
||||
60,
|
||||
120,
|
||||
TestHelpers.MakePartAt(0, 0, 10),
|
||||
TestHelpers.MakePartAt(10, 0, 10));
|
||||
TestHelpers.MakePartAt(10, 0, 10)
|
||||
);
|
||||
|
||||
var hasOverlaps = plate.HasOverlappingParts(out var pts);
|
||||
_output.WriteLine($"Adjacent squares: overlaps={hasOverlaps}, collision count={pts.Count}");
|
||||
|
||||
Assert.False(hasOverlaps, "Adjacent edge-touching parts should not be reported as overlapping");
|
||||
Assert.False(
|
||||
hasOverlaps,
|
||||
"Adjacent edge-touching parts should not be reported as overlapping"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,12 @@ public class EngineRefactorSmokeTests
|
||||
var engine = new DefaultNestEngine(plate);
|
||||
var item = new NestItem { Drawing = MakeRectDrawing(20, 10) };
|
||||
|
||||
var parts = engine.Fill(item, plate.WorkArea(), null, System.Threading.CancellationToken.None);
|
||||
var parts = engine.Fill(
|
||||
item,
|
||||
plate.WorkArea(),
|
||||
null,
|
||||
System.Threading.CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.True(parts.Count > 0, "DefaultNestEngine should fill parts");
|
||||
}
|
||||
@@ -35,7 +40,12 @@ public class EngineRefactorSmokeTests
|
||||
var drawing = MakeRectDrawing(20, 10);
|
||||
var groupParts = new List<Part> { new Part(drawing) };
|
||||
|
||||
var parts = engine.Fill(groupParts, plate.WorkArea(), null, System.Threading.CancellationToken.None);
|
||||
var parts = engine.Fill(
|
||||
groupParts,
|
||||
plate.WorkArea(),
|
||||
null,
|
||||
System.Threading.CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.True(parts.Count > 0, "DefaultNestEngine group fill should produce parts");
|
||||
}
|
||||
@@ -48,7 +58,12 @@ public class EngineRefactorSmokeTests
|
||||
engine.ForceFullAngleSweep = true;
|
||||
var item = new NestItem { Drawing = MakeRectDrawing(20, 10) };
|
||||
|
||||
var parts = engine.Fill(item, plate.WorkArea(), null, System.Threading.CancellationToken.None);
|
||||
var parts = engine.Fill(
|
||||
item,
|
||||
plate.WorkArea(),
|
||||
null,
|
||||
System.Threading.CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.True(parts.Count > 0, "ForceFullAngleSweep should still produce results");
|
||||
}
|
||||
@@ -91,7 +106,11 @@ public class EngineRefactorSmokeTests
|
||||
var plate = new Plate(60, 120);
|
||||
var drawing = MakeRectDrawing(20, 10);
|
||||
|
||||
var result = OpenNest.Engine.ML.BruteForceRunner.Run(drawing, plate, forceFullAngleSweep: true);
|
||||
var result = OpenNest.Engine.ML.BruteForceRunner.Run(
|
||||
drawing,
|
||||
plate,
|
||||
forceFullAngleSweep: true
|
||||
);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.PartCount > 0);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.IO;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
@@ -18,6 +18,7 @@ public class MultiPlateNesterTests
|
||||
{
|
||||
_output = output;
|
||||
}
|
||||
|
||||
private static Drawing MakeDrawing(string name, double width, double length)
|
||||
{
|
||||
var program = new OpenNest.CNC.Program();
|
||||
@@ -33,11 +34,7 @@ public class MultiPlateNesterTests
|
||||
|
||||
private static NestItem MakeItem(string name, double width, double length, int qty = 1)
|
||||
{
|
||||
return new NestItem
|
||||
{
|
||||
Drawing = MakeDrawing(name, width, length),
|
||||
Quantity = qty,
|
||||
};
|
||||
return new NestItem { Drawing = MakeDrawing(name, width, length), Quantity = qty };
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -62,9 +59,9 @@ public class MultiPlateNesterTests
|
||||
{
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
MakeItem("short-wide", 50, 20), // longest = 50
|
||||
MakeItem("tall-narrow", 10, 80), // longest = 80
|
||||
MakeItem("square", 30, 30), // longest = 30
|
||||
MakeItem("short-wide", 50, 20), // longest = 50
|
||||
MakeItem("tall-narrow", 10, 80), // longest = 80
|
||||
MakeItem("square", 30, 30), // longest = 30
|
||||
};
|
||||
|
||||
var sorted = MultiPlateNester.SortItems(items, PartSortOrder.Size);
|
||||
@@ -153,8 +150,10 @@ public class MultiPlateNesterTests
|
||||
// All returned zones should have both dims < 12
|
||||
foreach (var zone in scrap)
|
||||
{
|
||||
Assert.True(zone.Width < 12.0 && zone.Length < 12.0,
|
||||
$"Zone {zone.Width:F1}x{zone.Length:F1} is not scrap — at least one dimension >= 12");
|
||||
Assert.True(
|
||||
zone.Width < 12.0 && zone.Length < 12.0,
|
||||
$"Zone {zone.Width:F1}x{zone.Length:F1} is not scrap — at least one dimension >= 12"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +163,13 @@ public class MultiPlateNesterTests
|
||||
public void CreatePlate_UsesTemplateWhenNoOptions()
|
||||
{
|
||||
var template = new Plate(96, 48) { PartSpacing = 0.25, Quadrant = 1 };
|
||||
template.EdgeSpacing = new Spacing { Left = 1, Right = 1, Top = 1, Bottom = 1 };
|
||||
template.EdgeSpacing = new Spacing
|
||||
{
|
||||
Left = 1,
|
||||
Right = 1,
|
||||
Top = 1,
|
||||
Bottom = 1,
|
||||
};
|
||||
|
||||
var plate = MultiPlateNester.CreatePlate(template, null, null);
|
||||
|
||||
@@ -178,13 +183,34 @@ public class MultiPlateNesterTests
|
||||
public void CreatePlate_PicksSmallestFittingOption()
|
||||
{
|
||||
var template = new Plate(96, 48) { PartSpacing = 0.25, Quadrant = 1 };
|
||||
template.EdgeSpacing = new Spacing { Left = 1, Right = 1, Top = 1, Bottom = 1 };
|
||||
template.EdgeSpacing = new Spacing
|
||||
{
|
||||
Left = 1,
|
||||
Right = 1,
|
||||
Top = 1,
|
||||
Bottom = 1,
|
||||
};
|
||||
|
||||
var options = new List<PlateOption>
|
||||
{
|
||||
new() { Width = 48, Length = 96, Cost = 100 },
|
||||
new() { Width = 60, Length = 120, Cost = 200 },
|
||||
new() { Width = 72, Length = 144, Cost = 300 },
|
||||
new()
|
||||
{
|
||||
Width = 48,
|
||||
Length = 96,
|
||||
Cost = 100,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 60,
|
||||
Length = 120,
|
||||
Cost = 200,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 72,
|
||||
Length = 144,
|
||||
Cost = 300,
|
||||
},
|
||||
};
|
||||
|
||||
// Part needs 50x50 work area — 48x96 (after edge spacing: 46x94) — 46 < 50, doesn't fit.
|
||||
@@ -200,9 +226,24 @@ public class MultiPlateNesterTests
|
||||
[Fact]
|
||||
public void EvaluateUpgrade_PrefersCheaperOption()
|
||||
{
|
||||
var currentOption = new PlateOption { Width = 48, Length = 96, Cost = 100 };
|
||||
var upgradeOption = new PlateOption { Width = 60, Length = 120, Cost = 160 };
|
||||
var newPlateOption = new PlateOption { Width = 48, Length = 96, Cost = 100 };
|
||||
var currentOption = new PlateOption
|
||||
{
|
||||
Width = 48,
|
||||
Length = 96,
|
||||
Cost = 100,
|
||||
};
|
||||
var upgradeOption = new PlateOption
|
||||
{
|
||||
Width = 60,
|
||||
Length = 120,
|
||||
Cost = 160,
|
||||
};
|
||||
var newPlateOption = new PlateOption
|
||||
{
|
||||
Width = 48,
|
||||
Length = 96,
|
||||
Cost = 100,
|
||||
};
|
||||
|
||||
// Upgrade cost = 160 - 100 = 60
|
||||
// New plate cost with 50% utilization, 50% salvage:
|
||||
@@ -210,7 +251,12 @@ public class MultiPlateNesterTests
|
||||
// netNewCost = 100 - 25 = 75
|
||||
// Upgrade (60) < new plate (75), so upgrade wins
|
||||
var decision = MultiPlateNester.EvaluateUpgradeVsNew(
|
||||
currentOption, upgradeOption, newPlateOption, 0.5, 0.5);
|
||||
currentOption,
|
||||
upgradeOption,
|
||||
newPlateOption,
|
||||
0.5,
|
||||
0.5
|
||||
);
|
||||
|
||||
Assert.True(decision.ShouldUpgrade);
|
||||
}
|
||||
@@ -223,19 +269,17 @@ public class MultiPlateNesterTests
|
||||
var template = new Plate(96, 48) { PartSpacing = 0.25, Quadrant = 1 };
|
||||
template.EdgeSpacing = new Spacing();
|
||||
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
MakeItem("big1", 80, 40, 1),
|
||||
MakeItem("big2", 70, 35, 1),
|
||||
};
|
||||
var items = new List<NestItem> { MakeItem("big1", 80, 40, 1), MakeItem("big2", 70, 35, 1) };
|
||||
|
||||
var options = new MultiPlateNestOptions { Template = template };
|
||||
|
||||
var result = MultiPlateNester.Nest(items, options);
|
||||
|
||||
// Each large part should be on its own plate.
|
||||
Assert.True(result.Plates.Count >= 2,
|
||||
$"Expected at least 2 plates, got {result.Plates.Count}");
|
||||
Assert.True(
|
||||
result.Plates.Count >= 2,
|
||||
$"Expected at least 2 plates, got {result.Plates.Count}"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -260,8 +304,10 @@ public class MultiPlateNesterTests
|
||||
|
||||
// Both small drawing types should share space — not each on their own plate.
|
||||
// With consolidation, they pack into remaining space alongside the big part.
|
||||
Assert.True(result.Plates.Count <= 2,
|
||||
$"Expected at most 2 plates (small parts consolidated), got {result.Plates.Count}");
|
||||
Assert.True(
|
||||
result.Plates.Count <= 2,
|
||||
$"Expected at most 2 plates (small parts consolidated), got {result.Plates.Count}"
|
||||
);
|
||||
Assert.Equal(0, result.UnplacedItems.Count);
|
||||
}
|
||||
|
||||
@@ -271,17 +317,9 @@ public class MultiPlateNesterTests
|
||||
var template = new Plate(96, 48) { PartSpacing = 0.25, Quadrant = 1 };
|
||||
template.EdgeSpacing = new Spacing();
|
||||
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
MakeItem("big1", 80, 40, 1),
|
||||
MakeItem("big2", 70, 35, 1),
|
||||
};
|
||||
var items = new List<NestItem> { MakeItem("big1", 80, 40, 1), MakeItem("big2", 70, 35, 1) };
|
||||
|
||||
var options = new MultiPlateNestOptions
|
||||
{
|
||||
Template = template,
|
||||
AllowPlateCreation = false,
|
||||
};
|
||||
var options = new MultiPlateNestOptions { Template = template, AllowPlateCreation = false };
|
||||
|
||||
var result = MultiPlateNester.Nest(items, options);
|
||||
|
||||
@@ -303,15 +341,15 @@ public class MultiPlateNesterTests
|
||||
// Plate WorkArea: Width=96, Length=48. Half: 48, 24.
|
||||
// Part 24x22: Length=24 (not > 24), Width=22 (not > 48) — not Large.
|
||||
// Area = 528 > 4608/9 = 512 — Medium.
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
MakeItem("medium", 24, 22, 1),
|
||||
};
|
||||
var items = new List<NestItem> { MakeItem("medium", 24, 22, 1) };
|
||||
|
||||
var options = new MultiPlateNestOptions { Template = template };
|
||||
|
||||
var result = MultiPlateNester.Nest(items, options,
|
||||
existingPlates: new List<Plate> { existingPlate });
|
||||
var result = MultiPlateNester.Nest(
|
||||
items,
|
||||
options,
|
||||
existingPlates: new List<Plate> { existingPlate }
|
||||
);
|
||||
|
||||
// Part should be placed on the existing plate, not a new one.
|
||||
Assert.Single(result.Plates);
|
||||
@@ -331,34 +369,43 @@ public class MultiPlateNesterTests
|
||||
var nest = new NestReader(nestPath).Read();
|
||||
var template = nest.PlateDefaults.CreateNew();
|
||||
|
||||
_output.WriteLine($"Plate: {template.Size.Width}x{template.Size.Length}, " +
|
||||
$"spacing={template.PartSpacing}, edge=({template.EdgeSpacing.Left},{template.EdgeSpacing.Bottom},{template.EdgeSpacing.Right},{template.EdgeSpacing.Top})");
|
||||
_output.WriteLine(
|
||||
$"Plate: {template.Size.Width}x{template.Size.Length}, "
|
||||
+ $"spacing={template.PartSpacing}, edge=({template.EdgeSpacing.Left},{template.EdgeSpacing.Bottom},{template.EdgeSpacing.Right},{template.EdgeSpacing.Top})"
|
||||
);
|
||||
|
||||
var wa = template.WorkArea();
|
||||
_output.WriteLine($"Work area: {wa.Width:F1}x{wa.Length:F1}");
|
||||
_output.WriteLine($"Classification thresholds: Large if dim > {wa.Width / 2:F1} or {wa.Length / 2:F1}, " +
|
||||
$"Medium if area > {wa.Width * wa.Length / 9:F0}");
|
||||
_output.WriteLine(
|
||||
$"Classification thresholds: Large if dim > {wa.Width / 2:F1} or {wa.Length / 2:F1}, "
|
||||
+ $"Medium if area > {wa.Width * wa.Length / 9:F0}"
|
||||
);
|
||||
_output.WriteLine("---");
|
||||
|
||||
var items = new List<NestItem>();
|
||||
foreach (var d in nest.Drawings)
|
||||
{
|
||||
var qty = d.Quantity.Required > 0 ? d.Quantity.Required : d.Quantity.Remaining;
|
||||
if (qty <= 0) qty = 1;
|
||||
if (qty <= 0)
|
||||
qty = 1;
|
||||
|
||||
var bb = d.Program.BoundingBox();
|
||||
var classification = MultiPlateNester.Classify(bb, wa);
|
||||
|
||||
_output.WriteLine($" {d.Name,-25} {bb.Width:F1}x{bb.Length:F1} (area={bb.Width * bb.Length:F0}) qty={qty} class={classification}");
|
||||
_output.WriteLine(
|
||||
$" {d.Name, -25} {bb.Width:F1}x{bb.Length:F1} (area={bb.Width * bb.Length:F0}) qty={qty} class={classification}"
|
||||
);
|
||||
|
||||
items.Add(new NestItem
|
||||
{
|
||||
Drawing = d,
|
||||
Quantity = qty,
|
||||
StepAngle = d.Constraints.StepAngle,
|
||||
RotationStart = d.Constraints.StartAngle,
|
||||
RotationEnd = d.Constraints.EndAngle,
|
||||
});
|
||||
items.Add(
|
||||
new NestItem
|
||||
{
|
||||
Drawing = d,
|
||||
Quantity = qty,
|
||||
StepAngle = d.Constraints.StepAngle,
|
||||
RotationStart = d.Constraints.StartAngle,
|
||||
RotationEnd = d.Constraints.EndAngle,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_output.WriteLine("---");
|
||||
@@ -366,18 +413,65 @@ public class MultiPlateNesterTests
|
||||
|
||||
var plateOptions = new List<PlateOption>
|
||||
{
|
||||
new() { Width = 48, Length = 96, Cost = 0 },
|
||||
new() { Width = 48, Length = 120, Cost = 0 },
|
||||
new() { Width = 48, Length = 144, Cost = 0 },
|
||||
new() { Width = 60, Length = 96, Cost = 0 },
|
||||
new() { Width = 60, Length = 120, Cost = 0 },
|
||||
new() { Width = 60, Length = 144, Cost = 0 },
|
||||
new() { Width = 72, Length = 96, Cost = 0 },
|
||||
new() { Width = 72, Length = 120, Cost = 0 },
|
||||
new() { Width = 72, Length = 144, Cost = 0 },
|
||||
new()
|
||||
{
|
||||
Width = 48,
|
||||
Length = 96,
|
||||
Cost = 0,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 48,
|
||||
Length = 120,
|
||||
Cost = 0,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 48,
|
||||
Length = 144,
|
||||
Cost = 0,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 60,
|
||||
Length = 96,
|
||||
Cost = 0,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 60,
|
||||
Length = 120,
|
||||
Cost = 0,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 60,
|
||||
Length = 144,
|
||||
Cost = 0,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 72,
|
||||
Length = 96,
|
||||
Cost = 0,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 72,
|
||||
Length = 120,
|
||||
Cost = 0,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 72,
|
||||
Length = 144,
|
||||
Cost = 0,
|
||||
},
|
||||
};
|
||||
|
||||
_output.WriteLine($"Plate options: {string.Join(", ", plateOptions.Select(o => $"{o.Width}x{o.Length}"))}");
|
||||
_output.WriteLine(
|
||||
$"Plate options: {string.Join(", ", plateOptions.Select(o => $"{o.Width}x{o.Length}"))}"
|
||||
);
|
||||
_output.WriteLine("");
|
||||
|
||||
var options = new MultiPlateNestOptions
|
||||
@@ -393,16 +487,21 @@ public class MultiPlateNesterTests
|
||||
for (var i = 0; i < result.Plates.Count; i++)
|
||||
{
|
||||
var pr = result.Plates[i];
|
||||
var groups = pr.Parts.GroupBy(p => p.BaseDrawing.Name)
|
||||
var groups = pr
|
||||
.Parts.GroupBy(p => p.BaseDrawing.Name)
|
||||
.Select(g => $"{g.Key} x{g.Count()}")
|
||||
.ToList();
|
||||
_output.WriteLine($" Plate {i + 1} ({pr.Plate.Size.Width}x{pr.Plate.Size.Length}): " +
|
||||
$"{pr.Parts.Count} parts, util={pr.Plate.Utilization():P1} [{string.Join(", ", groups)}]");
|
||||
_output.WriteLine(
|
||||
$" Plate {i + 1} ({pr.Plate.Size.Width}x{pr.Plate.Size.Length}): "
|
||||
+ $"{pr.Parts.Count} parts, util={pr.Plate.Utilization():P1} [{string.Join(", ", groups)}]"
|
||||
);
|
||||
}
|
||||
|
||||
if (result.UnplacedItems.Count > 0)
|
||||
{
|
||||
_output.WriteLine($" Unplaced: {string.Join(", ", result.UnplacedItems.Select(i => $"{i.Drawing.Name} x{i.Quantity}"))}");
|
||||
_output.WriteLine(
|
||||
$" Unplaced: {string.Join(", ", result.UnplacedItems.Select(i => $"{i.Drawing.Name} x{i.Quantity}"))}"
|
||||
);
|
||||
}
|
||||
|
||||
_output.WriteLine($"\nTotal parts placed: {result.Plates.Sum(p => p.Parts.Count)}");
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Threading;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Engine;
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Threading;
|
||||
|
||||
namespace OpenNest.Tests.Engine;
|
||||
|
||||
@@ -31,18 +31,20 @@ public class NestInvarianceTests
|
||||
return new Drawing("L", pgm);
|
||||
}
|
||||
|
||||
private static Plate MakePlate() => new Plate(new Size(500, 500))
|
||||
{
|
||||
Quadrant = 1,
|
||||
PartSpacing = 2,
|
||||
};
|
||||
private static Plate MakePlate() =>
|
||||
new Plate(new Size(500, 500)) { Quadrant = 1, PartSpacing = 2 };
|
||||
|
||||
private static int RunFillCount(Drawing drawing, Plate plate)
|
||||
{
|
||||
BestFitCache.Clear();
|
||||
var engine = new DefaultNestEngine(plate);
|
||||
var item = new NestItem { Drawing = drawing };
|
||||
var parts = engine.Fill(item, plate.WorkArea(), progress: null, token: CancellationToken.None);
|
||||
var parts = engine.Fill(
|
||||
item,
|
||||
plate.WorkArea(),
|
||||
progress: null,
|
||||
token: CancellationToken.None
|
||||
);
|
||||
return parts?.Count ?? 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,15 +30,26 @@ public class PartClassifierTests
|
||||
var result = PartClassifier.Classify(drawing);
|
||||
|
||||
Assert.Equal(PartType.Rectangle, result.Type);
|
||||
Assert.True(result.Rectangularity >= 0.99, $"Expected rectangularity>=0.99, got {result.Rectangularity:F4}");
|
||||
Assert.True(result.PerimeterRatio >= 0.99, $"Expected perimeterRatio>=0.99, got {result.PerimeterRatio:F4}");
|
||||
Assert.True(
|
||||
result.Rectangularity >= 0.99,
|
||||
$"Expected rectangularity>=0.99, got {result.Rectangularity:F4}"
|
||||
);
|
||||
Assert.True(
|
||||
result.PerimeterRatio >= 0.99,
|
||||
$"Expected perimeterRatio>=0.99, got {result.PerimeterRatio:F4}"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Classify_RoundedRectangle_ReturnsRectangle()
|
||||
{
|
||||
// Use the built-in shape builder so arc geometry is constructed correctly.
|
||||
var shape = new RoundedRectangleShape { Length = 100, Width = 50, Radius = 5 };
|
||||
var shape = new RoundedRectangleShape
|
||||
{
|
||||
Length = 100,
|
||||
Width = 50,
|
||||
Radius = 5,
|
||||
};
|
||||
var drawing = shape.GetDrawing();
|
||||
|
||||
var result = PartClassifier.Classify(drawing);
|
||||
@@ -54,14 +65,14 @@ public class PartClassifierTests
|
||||
var pgm = new OpenNest.CNC.Program();
|
||||
pgm.Codes.Add(new RapidMove(new Vector(0, 0)));
|
||||
// Bottom edge left section -> notch -> bottom edge right section
|
||||
pgm.Codes.Add(new LinearMove(new Vector(45, 0))); // along bottom to notch start
|
||||
pgm.Codes.Add(new LinearMove(new Vector(45, 2))); // up into notch
|
||||
pgm.Codes.Add(new LinearMove(new Vector(50, 2))); // across notch (5 wide)
|
||||
pgm.Codes.Add(new LinearMove(new Vector(50, 0))); // back down
|
||||
pgm.Codes.Add(new LinearMove(new Vector(100, 0))); // remainder of bottom edge
|
||||
pgm.Codes.Add(new LinearMove(new Vector(45, 0))); // along bottom to notch start
|
||||
pgm.Codes.Add(new LinearMove(new Vector(45, 2))); // up into notch
|
||||
pgm.Codes.Add(new LinearMove(new Vector(50, 2))); // across notch (5 wide)
|
||||
pgm.Codes.Add(new LinearMove(new Vector(50, 0))); // back down
|
||||
pgm.Codes.Add(new LinearMove(new Vector(100, 0))); // remainder of bottom edge
|
||||
pgm.Codes.Add(new LinearMove(new Vector(100, 50))); // right edge
|
||||
pgm.Codes.Add(new LinearMove(new Vector(0, 50))); // top edge
|
||||
pgm.Codes.Add(new LinearMove(new Vector(0, 0))); // left edge back to start
|
||||
pgm.Codes.Add(new LinearMove(new Vector(0, 50))); // top edge
|
||||
pgm.Codes.Add(new LinearMove(new Vector(0, 0))); // left edge back to start
|
||||
var drawing = new Drawing("rect-notch", pgm);
|
||||
|
||||
var result = PartClassifier.Classify(drawing);
|
||||
@@ -78,8 +89,10 @@ public class PartClassifierTests
|
||||
var result = PartClassifier.Classify(drawing);
|
||||
|
||||
Assert.Equal(PartType.Circle, result.Type);
|
||||
Assert.True(result.Circularity >= PartClassifier.CircularityThreshold,
|
||||
$"Expected circularity>={PartClassifier.CircularityThreshold}, got {result.Circularity:F4}");
|
||||
Assert.True(
|
||||
result.Circularity >= PartClassifier.CircularityThreshold,
|
||||
$"Expected circularity>={PartClassifier.CircularityThreshold}, got {result.Circularity:F4}"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -151,8 +164,10 @@ public class PartClassifierTests
|
||||
var result = PartClassifier.Classify(drawing);
|
||||
|
||||
Assert.Equal(PartType.Irregular, result.Type);
|
||||
Assert.True(result.PerimeterRatio < PartClassifier.PerimeterRatioThreshold,
|
||||
$"Expected perimeterRatio<{PartClassifier.PerimeterRatioThreshold}, got {result.PerimeterRatio:F4}");
|
||||
Assert.True(
|
||||
result.PerimeterRatio < PartClassifier.PerimeterRatioThreshold,
|
||||
$"Expected perimeterRatio<{PartClassifier.PerimeterRatioThreshold}, got {result.PerimeterRatio:F4}"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -186,8 +201,10 @@ public class PartClassifierTests
|
||||
var result = PartClassifier.Classify(drawing);
|
||||
|
||||
// The MBR must be tilted — primary angle should be non-zero.
|
||||
Assert.True(System.Math.Abs(result.PrimaryAngle) > 0.01,
|
||||
$"Expected non-zero primary angle for 30°-tilted rect, got {result.PrimaryAngle:F4} rad");
|
||||
Assert.True(
|
||||
System.Math.Abs(result.PrimaryAngle) > 0.01,
|
||||
$"Expected non-zero primary angle for 30°-tilted rect, got {result.PrimaryAngle:F4} rad"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -20,14 +20,24 @@ public class PlateOptimizerTests
|
||||
{
|
||||
var options = new List<PlateOption>
|
||||
{
|
||||
new() { Width = 20, Length = 20, Cost = 100 },
|
||||
new() { Width = 40, Length = 40, Cost = 400 },
|
||||
new()
|
||||
{
|
||||
Width = 20,
|
||||
Length = 20,
|
||||
Cost = 100,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 40,
|
||||
Length = 40,
|
||||
Cost = 400,
|
||||
},
|
||||
};
|
||||
|
||||
var templatePlate = new Plate(40, 40) { PartSpacing = 0 };
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new() { Drawing = MakeRectDrawing(10, 10), Quantity = 1 }
|
||||
new() { Drawing = MakeRectDrawing(10, 10), Quantity = 1 },
|
||||
};
|
||||
|
||||
var result = PlateOptimizer.Optimize(items, options, 0.0, templatePlate);
|
||||
@@ -42,14 +52,24 @@ public class PlateOptimizerTests
|
||||
{
|
||||
var options = new List<PlateOption>
|
||||
{
|
||||
new() { Width = 12, Length = 12, Cost = 50 },
|
||||
new() { Width = 24, Length = 12, Cost = 100 },
|
||||
new()
|
||||
{
|
||||
Width = 12,
|
||||
Length = 12,
|
||||
Cost = 50,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 24,
|
||||
Length = 12,
|
||||
Cost = 100,
|
||||
},
|
||||
};
|
||||
|
||||
var templatePlate = new Plate(24, 12) { PartSpacing = 0 };
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new() { Drawing = MakeRectDrawing(10, 10), Quantity = 2 }
|
||||
new() { Drawing = MakeRectDrawing(10, 10), Quantity = 2 },
|
||||
};
|
||||
|
||||
var result = PlateOptimizer.Optimize(items, options, 0.0, templatePlate);
|
||||
@@ -68,15 +88,25 @@ public class PlateOptimizerTests
|
||||
// Net = 800 - 1500*(800/1600)*1.0 = 800-750 = 50
|
||||
var options = new List<PlateOption>
|
||||
{
|
||||
new() { Width = 20, Length = 20, Cost = 400 },
|
||||
new() { Width = 40, Length = 40, Cost = 800 },
|
||||
new()
|
||||
{
|
||||
Width = 20,
|
||||
Length = 20,
|
||||
Cost = 400,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 40,
|
||||
Length = 40,
|
||||
Cost = 800,
|
||||
},
|
||||
};
|
||||
|
||||
var templatePlate = new Plate(40, 40) { PartSpacing = 0 };
|
||||
templatePlate.EdgeSpacing = new Spacing();
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new() { Drawing = MakeRectDrawing(10, 10), Quantity = 1 }
|
||||
new() { Drawing = MakeRectDrawing(10, 10), Quantity = 1 },
|
||||
};
|
||||
|
||||
var result = PlateOptimizer.Optimize(items, options, 1.0, templatePlate);
|
||||
@@ -90,14 +120,24 @@ public class PlateOptimizerTests
|
||||
{
|
||||
var options = new List<PlateOption>
|
||||
{
|
||||
new() { Width = 20, Length = 20, Cost = 100 },
|
||||
new() { Width = 40, Length = 40, Cost = 400 },
|
||||
new()
|
||||
{
|
||||
Width = 20,
|
||||
Length = 20,
|
||||
Cost = 100,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Width = 40,
|
||||
Length = 40,
|
||||
Cost = 400,
|
||||
},
|
||||
};
|
||||
|
||||
var templatePlate = new Plate(40, 40) { PartSpacing = 0 };
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new() { Drawing = MakeRectDrawing(30, 30), Quantity = 1 }
|
||||
new() { Drawing = MakeRectDrawing(30, 30), Quantity = 1 },
|
||||
};
|
||||
|
||||
var result = PlateOptimizer.Optimize(items, options, 0.0, templatePlate);
|
||||
@@ -111,13 +151,18 @@ public class PlateOptimizerTests
|
||||
{
|
||||
var options = new List<PlateOption>
|
||||
{
|
||||
new() { Width = 10, Length = 10, Cost = 50 },
|
||||
new()
|
||||
{
|
||||
Width = 10,
|
||||
Length = 10,
|
||||
Cost = 50,
|
||||
},
|
||||
};
|
||||
|
||||
var templatePlate = new Plate(10, 10) { PartSpacing = 0 };
|
||||
var items = new List<NestItem>
|
||||
{
|
||||
new() { Drawing = MakeRectDrawing(20, 20), Quantity = 1 }
|
||||
new() { Drawing = MakeRectDrawing(20, 20), Quantity = 1 },
|
||||
};
|
||||
|
||||
var result = PlateOptimizer.Optimize(items, options, 0.0, templatePlate);
|
||||
|
||||
@@ -20,7 +20,7 @@ public class PlateProcessorTests
|
||||
var processor = new PlateProcessor
|
||||
{
|
||||
Sequencer = new RightSideSequencer(),
|
||||
RapidPlanner = new SafeHeightRapidPlanner()
|
||||
RapidPlanner = new SafeHeightRapidPlanner(),
|
||||
};
|
||||
|
||||
var result = processor.Process(plate);
|
||||
@@ -40,7 +40,7 @@ public class PlateProcessorTests
|
||||
var processor = new PlateProcessor
|
||||
{
|
||||
Sequencer = new RightSideSequencer(),
|
||||
RapidPlanner = new SafeHeightRapidPlanner()
|
||||
RapidPlanner = new SafeHeightRapidPlanner(),
|
||||
};
|
||||
|
||||
var result = processor.Process(plate);
|
||||
@@ -60,11 +60,8 @@ public class PlateProcessorTests
|
||||
var processor = new PlateProcessor
|
||||
{
|
||||
Sequencer = new LeftSideSequencer(),
|
||||
CuttingStrategy = new ContourCuttingStrategy
|
||||
{
|
||||
Parameters = new CuttingParameters()
|
||||
},
|
||||
RapidPlanner = new SafeHeightRapidPlanner()
|
||||
CuttingStrategy = new ContourCuttingStrategy { Parameters = new CuttingParameters() },
|
||||
RapidPlanner = new SafeHeightRapidPlanner(),
|
||||
};
|
||||
|
||||
var result = processor.Process(plate);
|
||||
@@ -83,7 +80,7 @@ public class PlateProcessorTests
|
||||
var processor = new PlateProcessor
|
||||
{
|
||||
Sequencer = new LeftSideSequencer(),
|
||||
RapidPlanner = new SafeHeightRapidPlanner()
|
||||
RapidPlanner = new SafeHeightRapidPlanner(),
|
||||
};
|
||||
|
||||
var result = processor.Process(plate);
|
||||
@@ -101,7 +98,7 @@ public class PlateProcessorTests
|
||||
var processor = new PlateProcessor
|
||||
{
|
||||
Sequencer = new LeftSideSequencer(),
|
||||
RapidPlanner = new SafeHeightRapidPlanner()
|
||||
RapidPlanner = new SafeHeightRapidPlanner(),
|
||||
};
|
||||
|
||||
var result = processor.Process(plate);
|
||||
@@ -117,7 +114,7 @@ public class PlateProcessorTests
|
||||
var processor = new PlateProcessor
|
||||
{
|
||||
Sequencer = new LeftSideSequencer(),
|
||||
RapidPlanner = new SafeHeightRapidPlanner()
|
||||
RapidPlanner = new SafeHeightRapidPlanner(),
|
||||
};
|
||||
|
||||
var result = processor.Process(plate);
|
||||
|
||||
@@ -42,7 +42,12 @@ public class RemnantEngineTests
|
||||
var engine = new VerticalRemnantEngine(plate);
|
||||
var item = new NestItem { Drawing = MakeRectDrawing(20, 10) };
|
||||
|
||||
var parts = engine.Fill(item, plate.WorkArea(), null, System.Threading.CancellationToken.None);
|
||||
var parts = engine.Fill(
|
||||
item,
|
||||
plate.WorkArea(),
|
||||
null,
|
||||
System.Threading.CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.True(parts.Count > 0, "VerticalRemnantEngine should fill parts");
|
||||
}
|
||||
@@ -54,7 +59,12 @@ public class RemnantEngineTests
|
||||
var engine = new HorizontalRemnantEngine(plate);
|
||||
var item = new NestItem { Drawing = MakeRectDrawing(20, 10) };
|
||||
|
||||
var parts = engine.Fill(item, plate.WorkArea(), null, System.Threading.CancellationToken.None);
|
||||
var parts = engine.Fill(
|
||||
item,
|
||||
plate.WorkArea(),
|
||||
null,
|
||||
System.Threading.CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.True(parts.Count > 0, "HorizontalRemnantEngine should fill parts");
|
||||
}
|
||||
@@ -77,16 +87,30 @@ public class RemnantEngineTests
|
||||
var defaultEngine = new DefaultNestEngine(plate);
|
||||
var remnantEngine = new VerticalRemnantEngine(plate);
|
||||
|
||||
var defaultParts = defaultEngine.Fill(item, plate.WorkArea(), null, System.Threading.CancellationToken.None);
|
||||
var remnantParts = remnantEngine.Fill(item, plate.WorkArea(), null, System.Threading.CancellationToken.None);
|
||||
var defaultParts = defaultEngine.Fill(
|
||||
item,
|
||||
plate.WorkArea(),
|
||||
null,
|
||||
System.Threading.CancellationToken.None
|
||||
);
|
||||
var remnantParts = remnantEngine.Fill(
|
||||
item,
|
||||
plate.WorkArea(),
|
||||
null,
|
||||
System.Threading.CancellationToken.None
|
||||
);
|
||||
|
||||
Assert.True(defaultParts.Count > 0);
|
||||
Assert.True(remnantParts.Count > 0);
|
||||
|
||||
var defaultXExtent = defaultParts.Max(p => p.BoundingBox.Right) - defaultParts.Min(p => p.BoundingBox.Left);
|
||||
var remnantXExtent = remnantParts.Max(p => p.BoundingBox.Right) - remnantParts.Min(p => p.BoundingBox.Left);
|
||||
var defaultXExtent =
|
||||
defaultParts.Max(p => p.BoundingBox.Right) - defaultParts.Min(p => p.BoundingBox.Left);
|
||||
var remnantXExtent =
|
||||
remnantParts.Max(p => p.BoundingBox.Right) - remnantParts.Min(p => p.BoundingBox.Left);
|
||||
|
||||
Assert.True(remnantXExtent <= defaultXExtent + 0.01,
|
||||
$"Remnant X-extent ({remnantXExtent:F1}) should be <= default ({defaultXExtent:F1})");
|
||||
Assert.True(
|
||||
remnantXExtent <= defaultXExtent + 0.01,
|
||||
$"Remnant X-extent ({remnantXExtent:F1}) should be <= default ({defaultXExtent:F1})"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user