merge: resolve conflicts from remote nesting progress changes
Kept using OpenNest.Api in Timing.cs and EditNestForm.cs alongside remote's reorganized usings and namespace changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
20
CLAUDE.md
20
CLAUDE.md
@@ -37,16 +37,15 @@ Nesting algorithms with a pluggable engine architecture. `NestEngineBase` is the
|
||||
|
||||
- **Engine hierarchy**: `NestEngineBase` (abstract) → `DefaultNestEngine` (Linear, Pairs, RectBestFit, Remainder phases). Custom engines subclass `NestEngineBase` and register via `NestEngineRegistry.Register()` or as plugin DLLs in `Engines/`.
|
||||
- **NestEngineRegistry**: Static registry — `Create(Plate)` factory, `ActiveEngineName` global selection, `LoadPlugins(directory)` for DLL discovery. All callsites use `NestEngineRegistry.Create(plate)` except `BruteForceRunner` which uses `new DefaultNestEngine(plate)` directly for training consistency.
|
||||
- **BestFit/**: NFP-based pair evaluation pipeline — `BestFitFinder` orchestrates angle sweeps, `PairEvaluator`/`IPairEvaluator` scores part pairs, `RotationSlideStrategy`/`ISlideComputer` computes slide distances. `BestFitCache` and `BestFitFilter` optimize repeated lookups.
|
||||
- **RectanglePacking/**: `FillBestFit` (single-item fill, tries horizontal and vertical orientations), `PackBottomLeft` (multi-item bin packing, sorts by area descending). Both operate on `Bin`/`Item` abstractions.
|
||||
- **CirclePacking/**: Alternative packing for circular parts.
|
||||
- **ML/**: `AnglePredictor` (ONNX model for predicting good rotation angles), `FeatureExtractor` (part geometry features), `BruteForceRunner` (full angle sweep for training data).
|
||||
- `FillLinear`: Grid-based fill with directional sliding.
|
||||
- `Compactor`: Post-fill gravity compaction — pushes parts toward a plate edge to close gaps.
|
||||
- `FillScore`: Lexicographic comparison struct for fill results (count > utilization > compactness).
|
||||
- **Fill/** (`namespace OpenNest.Engine.Fill`): Fill algorithms — `FillLinear` (grid-based), `FillExtents` (extents-based pair tiling), `PairFiller` (interlocking pairs), `ShrinkFiller`, `RemnantFiller`/`RemnantFinder`, `Compactor` (post-fill gravity compaction), `FillScore` (lexicographic comparison: count > utilization > compactness), `Pattern`/`PatternTiler`, `PartBoundary`, `RotationAnalysis`, `AngleCandidateBuilder`, `BestCombination`, `AccumulatingProgress`.
|
||||
- **Strategies/** (`namespace OpenNest.Engine.Strategies`): Pluggable fill strategy layer — `IFillStrategy` interface, `FillContext`, `FillStrategyRegistry` (auto-discovers strategies via reflection, supports plugin DLLs), `FillHelpers`. Built-in strategies: `LinearFillStrategy`, `PairsFillStrategy`, `RectBestFitStrategy`, `ExtentsFillStrategy`.
|
||||
- **BestFit/** (`namespace OpenNest.Engine.BestFit`): NFP-based pair evaluation pipeline — `BestFitFinder` orchestrates angle sweeps, `PairEvaluator`/`IPairEvaluator` scores part pairs, `RotationSlideStrategy`/`ISlideComputer` computes slide distances. `BestFitCache` and `BestFitFilter` optimize repeated lookups.
|
||||
- **RectanglePacking/** (`namespace OpenNest.RectanglePacking`): `FillBestFit` (single-item fill, tries horizontal and vertical orientations), `PackBottomLeft` (multi-item bin packing, sorts by area descending). Both operate on `Bin`/`Item` abstractions.
|
||||
- **CirclePacking/** (`namespace OpenNest.CirclePacking`): Alternative packing for circular parts.
|
||||
- **Nfp/** (`namespace OpenNest.Engine.Nfp`): NFP-based nesting (not yet integrated) — `AutoNester` (mixed-part nesting with simulated annealing), `BottomLeftFill` (BLF placement), `NfpCache` (computed NFP caching), `SimulatedAnnealing` (optimizer), `INestOptimizer`/`NestResult`.
|
||||
- **ML/** (`namespace OpenNest.Engine.ML`): `AnglePredictor` (ONNX model for predicting good rotation angles), `FeatureExtractor` (part geometry features), `BruteForceRunner` (full angle sweep for training data).
|
||||
- `NestItem`: Input to the engine — wraps a `Drawing` with quantity, priority, and rotation constraints.
|
||||
- `NestProgress`: Progress reporting model with `NestPhase` enum for UI feedback.
|
||||
- `RotationAnalysis`: Analyzes part geometry to determine valid rotation angles.
|
||||
|
||||
### OpenNest.IO (class library, depends on Core)
|
||||
File I/O and format conversion. Uses ACadSharp for DXF/DWG support.
|
||||
@@ -99,9 +98,14 @@ Always use Roslyn Bridge MCP tools (`mcp__RoslynBridge__*`) as the primary metho
|
||||
|
||||
- Always use `var` instead of explicit types (e.g., `var parts = new List<Part>();` not `List<Part> parts = new List<Part>();`).
|
||||
|
||||
## Documentation Maintenance
|
||||
|
||||
Always keep `README.md` and `CLAUDE.md` up to date when making changes that affect project structure, architecture, build instructions, dependencies, or key patterns. If you add a new project, change a namespace, modify the build process, or alter significant behavior, update both files as part of the same change.
|
||||
|
||||
## Key Patterns
|
||||
|
||||
- OpenNest.Core uses multiple namespaces: `OpenNest` (root domain), `OpenNest.CNC`, `OpenNest.Geometry`, `OpenNest.Converters`, `OpenNest.Math`, `OpenNest.Collections`.
|
||||
- OpenNest.Engine uses sub-namespaces: `OpenNest.Engine.Fill` (fill algorithms), `OpenNest.Engine.Strategies` (pluggable strategy layer), `OpenNest.Engine.BestFit`, `OpenNest.Engine.Nfp` (NFP-based nesting, not yet integrated), `OpenNest.Engine.ML`, `OpenNest.Engine.RapidPlanning`, `OpenNest.Engine.Sequencing`.
|
||||
- `ObservableList<T>` provides ItemAdded/ItemRemoved/ItemChanged events used for automatic quantity tracking between plates and drawings.
|
||||
- Angles throughout the codebase are in **radians** (use `Angle.ToRadians()`/`Angle.ToDegrees()` for conversion).
|
||||
- `Tolerance.Epsilon` is used for floating-point comparisons across geometry operations.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using OpenNest;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.IO;
|
||||
|
||||
return NestConsole.Run(args);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Converters
|
||||
{
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Converters
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
public int Remaining
|
||||
{
|
||||
get
|
||||
get
|
||||
{
|
||||
var x = Required - Nested;
|
||||
return x < 0 ? 0: x;
|
||||
return x < 0 ? 0 : x;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Clipper2Lib;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
public override string ToString() => $"{Width} x {Length}";
|
||||
|
||||
|
||||
public string ToString(int decimalPlaces) => $"{System.Math.Round(Width, decimalPlaces)} x {System.Math.Round(Length, decimalPlaces)}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -30,41 +29,41 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
case PushDirection.Left:
|
||||
case PushDirection.Right:
|
||||
{
|
||||
var dy = p2y - p1y;
|
||||
if (System.Math.Abs(dy) < Tolerance.Epsilon)
|
||||
{
|
||||
var dy = p2y - p1y;
|
||||
if (System.Math.Abs(dy) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var t = (vy - p1y) / dy;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var ix = p1x + t * (p2x - p1x);
|
||||
var dist = direction == PushDirection.Left ? vx - ix : ix - vx;
|
||||
|
||||
if (dist > Tolerance.Epsilon) return dist;
|
||||
if (dist >= -Tolerance.Epsilon) return 0;
|
||||
return double.MaxValue;
|
||||
|
||||
var t = (vy - p1y) / dy;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var ix = p1x + t * (p2x - p1x);
|
||||
var dist = direction == PushDirection.Left ? vx - ix : ix - vx;
|
||||
|
||||
if (dist > Tolerance.Epsilon) return dist;
|
||||
if (dist >= -Tolerance.Epsilon) return 0;
|
||||
return double.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
case PushDirection.Down:
|
||||
case PushDirection.Up:
|
||||
{
|
||||
var dx = p2x - p1x;
|
||||
if (System.Math.Abs(dx) < Tolerance.Epsilon)
|
||||
{
|
||||
var dx = p2x - p1x;
|
||||
if (System.Math.Abs(dx) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var t = (vx - p1x) / dx;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var iy = p1y + t * (p2y - p1y);
|
||||
var dist = direction == PushDirection.Down ? vy - iy : iy - vy;
|
||||
|
||||
if (dist > Tolerance.Epsilon) return dist;
|
||||
if (dist >= -Tolerance.Epsilon) return 0;
|
||||
return double.MaxValue;
|
||||
|
||||
var t = (vx - p1x) / dx;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var iy = p1y + t * (p2y - p1y);
|
||||
var dist = direction == PushDirection.Down ? vy - iy : iy - vy;
|
||||
|
||||
if (dist > Tolerance.Epsilon) return dist;
|
||||
if (dist >= -Tolerance.Epsilon) return 0;
|
||||
return double.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return double.MaxValue;
|
||||
@@ -363,10 +362,10 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return box.Left - boundary.Left;
|
||||
case PushDirection.Left: return box.Left - boundary.Left;
|
||||
case PushDirection.Right: return boundary.Right - box.Right;
|
||||
case PushDirection.Up: return boundary.Top - box.Top;
|
||||
case PushDirection.Down: return box.Bottom - boundary.Bottom;
|
||||
case PushDirection.Up: return boundary.Top - box.Top;
|
||||
case PushDirection.Down: return box.Bottom - boundary.Bottom;
|
||||
default: return double.MaxValue;
|
||||
}
|
||||
}
|
||||
@@ -375,10 +374,10 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return new Vector(-distance, 0);
|
||||
case PushDirection.Left: return new Vector(-distance, 0);
|
||||
case PushDirection.Right: return new Vector(distance, 0);
|
||||
case PushDirection.Up: return new Vector(0, distance);
|
||||
case PushDirection.Down: return new Vector(0, -distance);
|
||||
case PushDirection.Up: return new Vector(0, distance);
|
||||
case PushDirection.Down: return new Vector(0, -distance);
|
||||
default: return new Vector();
|
||||
}
|
||||
}
|
||||
@@ -387,10 +386,10 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return from.Left - to.Right;
|
||||
case PushDirection.Left: return from.Left - to.Right;
|
||||
case PushDirection.Right: return to.Left - from.Right;
|
||||
case PushDirection.Up: return to.Bottom - from.Top;
|
||||
case PushDirection.Down: return from.Bottom - to.Top;
|
||||
case PushDirection.Up: return to.Bottom - from.Top;
|
||||
case PushDirection.Down: return from.Bottom - to.Top;
|
||||
default: return double.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest.Math
|
||||
namespace OpenNest.Math
|
||||
{
|
||||
public static class Angle
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest.Math
|
||||
namespace OpenNest.Math
|
||||
{
|
||||
public static class Tolerance
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace OpenNest.Math
|
||||
namespace OpenNest.Math
|
||||
{
|
||||
public static class Trigonometry
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using OpenNest.Collections;
|
||||
using OpenNest.Collections;
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -174,10 +174,10 @@ namespace OpenNest
|
||||
|
||||
switch (facingDirection)
|
||||
{
|
||||
case PushDirection.Left: keep = -sign * dy > 0; break;
|
||||
case PushDirection.Right: keep = sign * dy > 0; break;
|
||||
case PushDirection.Up: keep = -sign * dx > 0; break;
|
||||
case PushDirection.Down: keep = sign * dx > 0; break;
|
||||
case PushDirection.Left: keep = -sign * dy > 0; break;
|
||||
case PushDirection.Right: keep = sign * dy > 0; break;
|
||||
case PushDirection.Up: keep = -sign * dx > 0; break;
|
||||
case PushDirection.Down: keep = sign * dx > 0; break;
|
||||
default: keep = true; break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Collections;
|
||||
using OpenNest.Collections;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using OpenNest.Api;
|
||||
using OpenNest.Api;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -84,7 +84,7 @@ namespace OpenNest
|
||||
time += TimeSpan.FromSeconds(info.TravelDistance / cutParams.RapidTravelRate);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
time += TimeSpan.FromTicks(info.PierceCount * cutParams.PierceTime.Ticks);
|
||||
|
||||
return time;
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace OpenNest
|
||||
case Units.Millimeters:
|
||||
return "mm";
|
||||
|
||||
default:
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ namespace OpenNest
|
||||
case Units.Millimeters:
|
||||
return "millimeters";
|
||||
|
||||
default:
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ namespace OpenNest
|
||||
case Units.Millimeters:
|
||||
return "sec";
|
||||
|
||||
default:
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using OpenNest.Engine.ML;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds candidate rotation angles for single-item fill. Encapsulates the
|
||||
/// full pipeline: base angles, narrow-area sweep, ML prediction, and
|
||||
/// known-good pruning across fills.
|
||||
/// </summary>
|
||||
public class AngleCandidateBuilder
|
||||
{
|
||||
private readonly HashSet<double> knownGoodAngles = new();
|
||||
|
||||
public bool ForceFullSweep { get; set; }
|
||||
|
||||
public List<double> Build(NestItem item, double bestRotation, Box workArea)
|
||||
{
|
||||
var angles = new List<double> { bestRotation, bestRotation + Angle.HalfPI };
|
||||
|
||||
var testPart = new Part(item.Drawing);
|
||||
if (!bestRotation.IsEqualTo(0))
|
||||
testPart.Rotate(bestRotation);
|
||||
testPart.UpdateBounds();
|
||||
|
||||
var partLongestSide = System.Math.Max(testPart.BoundingBox.Width, testPart.BoundingBox.Length);
|
||||
var workAreaShortSide = System.Math.Min(workArea.Width, workArea.Length);
|
||||
var needsSweep = workAreaShortSide < partLongestSide || ForceFullSweep;
|
||||
|
||||
if (needsSweep)
|
||||
{
|
||||
var step = Angle.ToRadians(5);
|
||||
for (var a = 0.0; a < System.Math.PI; a += step)
|
||||
{
|
||||
if (!angles.Any(existing => existing.IsEqualTo(a)))
|
||||
angles.Add(a);
|
||||
}
|
||||
}
|
||||
|
||||
if (!ForceFullSweep && angles.Count > 2)
|
||||
{
|
||||
var features = FeatureExtractor.Extract(item.Drawing);
|
||||
if (features != null)
|
||||
{
|
||||
var predicted = AnglePredictor.PredictAngles(
|
||||
features, workArea.Width, workArea.Length);
|
||||
|
||||
if (predicted != null)
|
||||
{
|
||||
var mlAngles = new List<double>(predicted);
|
||||
|
||||
if (!mlAngles.Any(a => a.IsEqualTo(bestRotation)))
|
||||
mlAngles.Add(bestRotation);
|
||||
if (!mlAngles.Any(a => a.IsEqualTo(bestRotation + Angle.HalfPI)))
|
||||
mlAngles.Add(bestRotation + Angle.HalfPI);
|
||||
|
||||
Debug.WriteLine($"[AngleCandidateBuilder] ML: {angles.Count} angles -> {mlAngles.Count} predicted");
|
||||
angles = mlAngles;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (knownGoodAngles.Count > 0 && !ForceFullSweep)
|
||||
{
|
||||
var pruned = new List<double> { bestRotation, bestRotation + Angle.HalfPI };
|
||||
|
||||
foreach (var a in knownGoodAngles)
|
||||
{
|
||||
if (!pruned.Any(existing => existing.IsEqualTo(a)))
|
||||
pruned.Add(a);
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[AngleCandidateBuilder] Pruned: {angles.Count} -> {pruned.Count} angles (known-good)");
|
||||
return pruned;
|
||||
}
|
||||
|
||||
return angles;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records angles that produced results. These are used to prune
|
||||
/// subsequent Build() calls.
|
||||
/// </summary>
|
||||
public void RecordProductive(List<AngleResult> angleResults)
|
||||
{
|
||||
foreach (var ar in angleResults)
|
||||
{
|
||||
if (ar.PartCount > 0)
|
||||
knownGoodAngles.Add(Angle.ToRadians(ar.AngleDeg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Engine.BestFit.Tiling;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.BestFit
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Engine.BestFit.Tiling
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Engine.BestFit.Tiling
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CirclePacking
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
|
||||
namespace OpenNest.CirclePacking
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
|
||||
namespace OpenNest.CirclePacking
|
||||
{
|
||||
|
||||
@@ -1,363 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
/// <summary>
|
||||
/// Pushes a group of parts left and down to close gaps after placement.
|
||||
/// Uses the same directional-distance logic as PlateView.PushSelected
|
||||
/// but operates on Part objects directly.
|
||||
/// </summary>
|
||||
public static class Compactor
|
||||
{
|
||||
private const double ChordTolerance = 0.001;
|
||||
|
||||
/// <summary>
|
||||
/// Compacts movingParts toward the bottom-left of the plate work area.
|
||||
/// Everything already on the plate (excluding movingParts) is treated
|
||||
/// as stationary obstacles.
|
||||
/// </summary>
|
||||
private const double RepeatThreshold = 0.01;
|
||||
private const int MaxIterations = 20;
|
||||
|
||||
public static void Compact(List<Part> movingParts, Plate plate)
|
||||
{
|
||||
if (movingParts == null || movingParts.Count == 0)
|
||||
return;
|
||||
|
||||
var savedPositions = SavePositions(movingParts);
|
||||
|
||||
// Try left-first.
|
||||
var leftFirst = CompactLoop(movingParts, plate, PushDirection.Left, PushDirection.Down);
|
||||
|
||||
// Restore and try down-first.
|
||||
RestorePositions(movingParts, savedPositions);
|
||||
var downFirst = CompactLoop(movingParts, plate, PushDirection.Down, PushDirection.Left);
|
||||
|
||||
// Keep left-first if it traveled further.
|
||||
if (leftFirst > downFirst)
|
||||
{
|
||||
RestorePositions(movingParts, savedPositions);
|
||||
CompactLoop(movingParts, plate, PushDirection.Left, PushDirection.Down);
|
||||
}
|
||||
}
|
||||
|
||||
private static double CompactLoop(List<Part> parts, Plate plate,
|
||||
PushDirection first, PushDirection second)
|
||||
{
|
||||
var total = 0.0;
|
||||
|
||||
for (var i = 0; i < MaxIterations; i++)
|
||||
{
|
||||
var a = Push(parts, plate, first);
|
||||
var b = Push(parts, plate, second);
|
||||
total += a + b;
|
||||
|
||||
if (a <= RepeatThreshold && b <= RepeatThreshold)
|
||||
break;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
private static Vector[] SavePositions(List<Part> parts)
|
||||
{
|
||||
var positions = new Vector[parts.Count];
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
positions[i] = parts[i].Location;
|
||||
return positions;
|
||||
}
|
||||
|
||||
private static void RestorePositions(List<Part> parts, Vector[] positions)
|
||||
{
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
parts[i].Location = positions[i];
|
||||
}
|
||||
|
||||
public static double Push(List<Part> movingParts, Plate plate, PushDirection direction)
|
||||
{
|
||||
var obstacleParts = plate.Parts
|
||||
.Where(p => !movingParts.Contains(p))
|
||||
.ToList();
|
||||
|
||||
return Push(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up).
|
||||
/// </summary>
|
||||
public static double Push(List<Part> movingParts, Plate plate, double angle)
|
||||
{
|
||||
var obstacleParts = plate.Parts
|
||||
.Where(p => !movingParts.Contains(p))
|
||||
.ToList();
|
||||
|
||||
return Push(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, angle);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up).
|
||||
/// </summary>
|
||||
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
||||
Box workArea, double partSpacing, double angle)
|
||||
{
|
||||
var direction = new Vector(System.Math.Cos(angle), System.Math.Sin(angle));
|
||||
var opposite = -direction;
|
||||
|
||||
var obstacleBoxes = new Box[obstacleParts.Count];
|
||||
var obstacleLines = new List<Line>[obstacleParts.Count];
|
||||
|
||||
for (var i = 0; i < obstacleParts.Count; i++)
|
||||
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
|
||||
|
||||
var halfSpacing = partSpacing / 2;
|
||||
var distance = double.MaxValue;
|
||||
|
||||
foreach (var moving in movingParts)
|
||||
{
|
||||
var edgeDist = SpatialQuery.EdgeDistance(moving.BoundingBox, workArea, direction);
|
||||
if (edgeDist <= 0)
|
||||
distance = 0;
|
||||
else if (edgeDist < distance)
|
||||
distance = edgeDist;
|
||||
|
||||
var movingBox = moving.BoundingBox;
|
||||
List<Line> movingLines = null;
|
||||
|
||||
for (var i = 0; i < obstacleBoxes.Length; i++)
|
||||
{
|
||||
var reverseGap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], opposite);
|
||||
if (reverseGap > 0)
|
||||
continue;
|
||||
|
||||
var gap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], direction);
|
||||
if (gap >= distance)
|
||||
continue;
|
||||
|
||||
if (!SpatialQuery.PerpendicularOverlap(movingBox, obstacleBoxes[i], direction))
|
||||
continue;
|
||||
|
||||
movingLines ??= halfSpacing > 0
|
||||
? PartGeometry.GetOffsetPartLines(moving, halfSpacing, direction, ChordTolerance)
|
||||
: PartGeometry.GetPartLines(moving, direction, ChordTolerance);
|
||||
|
||||
obstacleLines[i] ??= halfSpacing > 0
|
||||
? PartGeometry.GetOffsetPartLines(obstacleParts[i], halfSpacing, opposite, ChordTolerance)
|
||||
: PartGeometry.GetPartLines(obstacleParts[i], opposite, ChordTolerance);
|
||||
|
||||
var d = SpatialQuery.DirectionalDistance(movingLines, obstacleLines[i], direction);
|
||||
if (d < distance)
|
||||
distance = d;
|
||||
}
|
||||
}
|
||||
|
||||
if (distance < double.MaxValue && distance > 0)
|
||||
{
|
||||
var offset = direction * distance;
|
||||
foreach (var moving in movingParts)
|
||||
moving.Offset(offset);
|
||||
return distance;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
||||
Box workArea, double partSpacing, PushDirection direction)
|
||||
{
|
||||
var obstacleBoxes = new Box[obstacleParts.Count];
|
||||
var obstacleLines = new List<Line>[obstacleParts.Count];
|
||||
|
||||
for (var i = 0; i < obstacleParts.Count; i++)
|
||||
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
|
||||
|
||||
var opposite = SpatialQuery.OppositeDirection(direction);
|
||||
var halfSpacing = partSpacing / 2;
|
||||
var isHorizontal = SpatialQuery.IsHorizontalDirection(direction);
|
||||
var distance = double.MaxValue;
|
||||
|
||||
// BB gap at which offset geometries are expected to be touching.
|
||||
var contactGap = (halfSpacing + ChordTolerance) * 2;
|
||||
|
||||
foreach (var moving in movingParts)
|
||||
{
|
||||
var edgeDist = SpatialQuery.EdgeDistance(moving.BoundingBox, workArea, direction);
|
||||
if (edgeDist <= 0)
|
||||
distance = 0;
|
||||
else if (edgeDist < distance)
|
||||
distance = edgeDist;
|
||||
|
||||
var movingBox = moving.BoundingBox;
|
||||
List<Line> movingLines = null;
|
||||
|
||||
for (var i = 0; i < obstacleBoxes.Length; i++)
|
||||
{
|
||||
// Use the reverse-direction gap to check if the obstacle is entirely
|
||||
// behind the moving part. The forward gap (gap < 0) is unreliable for
|
||||
// irregular shapes whose bounding boxes overlap even when the actual
|
||||
// geometry still has a valid contact in the push direction.
|
||||
var reverseGap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], opposite);
|
||||
if (reverseGap > 0)
|
||||
continue;
|
||||
|
||||
var gap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], direction);
|
||||
if (gap >= distance)
|
||||
continue;
|
||||
|
||||
var perpOverlap = isHorizontal
|
||||
? movingBox.IsHorizontalTo(obstacleBoxes[i], out _)
|
||||
: movingBox.IsVerticalTo(obstacleBoxes[i], out _);
|
||||
|
||||
if (!perpOverlap)
|
||||
continue;
|
||||
|
||||
movingLines ??= halfSpacing > 0
|
||||
? PartGeometry.GetOffsetPartLines(moving, halfSpacing, direction, ChordTolerance)
|
||||
: PartGeometry.GetPartLines(moving, direction, ChordTolerance);
|
||||
|
||||
obstacleLines[i] ??= halfSpacing > 0
|
||||
? PartGeometry.GetOffsetPartLines(obstacleParts[i], halfSpacing, opposite, ChordTolerance)
|
||||
: PartGeometry.GetPartLines(obstacleParts[i], opposite, ChordTolerance);
|
||||
|
||||
var d = SpatialQuery.DirectionalDistance(movingLines, obstacleLines[i], direction);
|
||||
if (d < distance)
|
||||
distance = d;
|
||||
}
|
||||
}
|
||||
|
||||
if (distance < double.MaxValue && distance > 0)
|
||||
{
|
||||
var offset = SpatialQuery.DirectionToOffset(direction, distance);
|
||||
foreach (var moving in movingParts)
|
||||
moving.Offset(offset);
|
||||
return distance;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts using bounding-box distances only (no geometry lines).
|
||||
/// Much faster but less precise — use as a coarse positioning pass before
|
||||
/// a full geometry Push.
|
||||
/// </summary>
|
||||
public static double PushBoundingBox(List<Part> movingParts, Plate plate, PushDirection direction)
|
||||
{
|
||||
var obstacleParts = plate.Parts
|
||||
.Where(p => !movingParts.Contains(p))
|
||||
.ToList();
|
||||
|
||||
return PushBoundingBox(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction);
|
||||
}
|
||||
|
||||
public static double PushBoundingBox(List<Part> movingParts, List<Part> obstacleParts,
|
||||
Box workArea, double partSpacing, PushDirection direction)
|
||||
{
|
||||
var obstacleBoxes = new Box[obstacleParts.Count];
|
||||
for (var i = 0; i < obstacleParts.Count; i++)
|
||||
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
|
||||
|
||||
var opposite = SpatialQuery.OppositeDirection(direction);
|
||||
var isHorizontal = SpatialQuery.IsHorizontalDirection(direction);
|
||||
var distance = double.MaxValue;
|
||||
|
||||
foreach (var moving in movingParts)
|
||||
{
|
||||
var edgeDist = SpatialQuery.EdgeDistance(moving.BoundingBox, workArea, direction);
|
||||
if (edgeDist <= 0)
|
||||
distance = 0;
|
||||
else if (edgeDist < distance)
|
||||
distance = edgeDist;
|
||||
|
||||
var movingBox = moving.BoundingBox;
|
||||
|
||||
for (var i = 0; i < obstacleBoxes.Length; i++)
|
||||
{
|
||||
var reverseGap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], opposite);
|
||||
if (reverseGap > 0)
|
||||
continue;
|
||||
|
||||
var perpOverlap = isHorizontal
|
||||
? movingBox.IsHorizontalTo(obstacleBoxes[i], out _)
|
||||
: movingBox.IsVerticalTo(obstacleBoxes[i], out _);
|
||||
|
||||
if (!perpOverlap)
|
||||
continue;
|
||||
|
||||
var gap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], direction);
|
||||
var d = gap - partSpacing;
|
||||
if (d < 0) d = 0;
|
||||
if (d < distance)
|
||||
distance = d;
|
||||
}
|
||||
}
|
||||
|
||||
if (distance < double.MaxValue && distance > 0)
|
||||
{
|
||||
var offset = SpatialQuery.DirectionToOffset(direction, distance);
|
||||
foreach (var moving in movingParts)
|
||||
moving.Offset(offset);
|
||||
return distance;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compacts parts individually toward the bottom-left of the work area.
|
||||
/// Each part is pushed against all others as obstacles, closing geometry-based gaps.
|
||||
/// Does not require parts to be on a plate.
|
||||
/// </summary>
|
||||
public static void CompactIndividual(List<Part> parts, Box workArea, double partSpacing)
|
||||
{
|
||||
if (parts == null || parts.Count < 2)
|
||||
return;
|
||||
|
||||
var savedPositions = SavePositions(parts);
|
||||
|
||||
var leftFirst = CompactIndividualLoop(parts, workArea, partSpacing,
|
||||
PushDirection.Left, PushDirection.Down);
|
||||
|
||||
RestorePositions(parts, savedPositions);
|
||||
var downFirst = CompactIndividualLoop(parts, workArea, partSpacing,
|
||||
PushDirection.Down, PushDirection.Left);
|
||||
|
||||
if (leftFirst > downFirst)
|
||||
{
|
||||
RestorePositions(parts, savedPositions);
|
||||
CompactIndividualLoop(parts, workArea, partSpacing,
|
||||
PushDirection.Left, PushDirection.Down);
|
||||
}
|
||||
}
|
||||
|
||||
private static double CompactIndividualLoop(List<Part> parts, Box workArea,
|
||||
double partSpacing, PushDirection first, PushDirection second)
|
||||
{
|
||||
var total = 0.0;
|
||||
|
||||
for (var pass = 0; pass < MaxIterations; pass++)
|
||||
{
|
||||
var moved = 0.0;
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var single = new List<Part>(1) { part };
|
||||
var obstacles = new List<Part>(parts.Count - 1);
|
||||
foreach (var p in parts)
|
||||
if (p != part) obstacles.Add(p);
|
||||
|
||||
moved += Push(single, obstacles, workArea, partSpacing, first);
|
||||
moved += Push(single, obstacles, workArea, partSpacing, second);
|
||||
}
|
||||
|
||||
total += moved;
|
||||
if (moved <= RepeatThreshold)
|
||||
break;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Engine.Strategies;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.RectanglePacking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using OpenNest.RectanglePacking;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -56,7 +55,8 @@ namespace OpenNest
|
||||
if (item.Quantity > 0 && best.Count > item.Quantity)
|
||||
best = best.Take(item.Quantity).ToList();
|
||||
|
||||
ReportProgress(progress, WinnerPhase, PlateNumber, best, workArea, BuildProgressSummary());
|
||||
ReportProgress(progress, WinnerPhase, PlateNumber, best, workArea, BuildProgressSummary(),
|
||||
isOverallBest: true);
|
||||
|
||||
return best;
|
||||
}
|
||||
@@ -67,89 +67,24 @@ namespace OpenNest
|
||||
if (groupParts == null || groupParts.Count == 0)
|
||||
return new List<Part>();
|
||||
|
||||
// Single part: delegate to the strategy pipeline.
|
||||
if (groupParts.Count == 1)
|
||||
{
|
||||
var nestItem = new NestItem { Drawing = groupParts[0].BaseDrawing };
|
||||
return Fill(nestItem, workArea, progress, token);
|
||||
}
|
||||
|
||||
// Multi-part group: linear pattern fill only.
|
||||
PhaseResults.Clear();
|
||||
var engine = new FillLinear(workArea, Plate.PartSpacing);
|
||||
var angles = RotationAnalysis.FindHullEdgeAngles(groupParts);
|
||||
var best = FillPattern(engine, groupParts, angles, workArea);
|
||||
var best = FillHelpers.FillPattern(engine, groupParts, angles, workArea);
|
||||
PhaseResults.Add(new PhaseResult(NestPhase.Linear, best?.Count ?? 0, 0));
|
||||
|
||||
Debug.WriteLine($"[Fill(groupParts,Box)] Linear: {best?.Count ?? 0} parts | WorkArea: {workArea.Width:F1}x{workArea.Length:F1}");
|
||||
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());
|
||||
|
||||
if (groupParts.Count == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var nestItem = new NestItem { Drawing = groupParts[0].BaseDrawing };
|
||||
var binItem = BinConverter.ToItem(nestItem, Plate.PartSpacing);
|
||||
var bin = BinConverter.CreateBin(workArea, Plate.PartSpacing);
|
||||
var rectEngine = new FillBestFit(bin);
|
||||
rectEngine.Fill(binItem);
|
||||
var rectResult = BinConverter.ToParts(bin, new List<NestItem> { nestItem });
|
||||
PhaseResults.Add(new PhaseResult(NestPhase.RectBestFit, rectResult?.Count ?? 0, 0));
|
||||
|
||||
Debug.WriteLine($"[Fill(groupParts,Box)] RectBestFit: {rectResult?.Count ?? 0} parts");
|
||||
|
||||
if (IsBetterFill(rectResult, best, workArea))
|
||||
{
|
||||
best = rectResult;
|
||||
ReportProgress(progress, NestPhase.RectBestFit, PlateNumber, best, workArea, BuildProgressSummary());
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var pairFiller = new PairFiller(Plate.Size, Plate.PartSpacing);
|
||||
var pairResult = pairFiller.Fill(nestItem, workArea, PlateNumber, token, progress);
|
||||
PhaseResults.Add(new PhaseResult(NestPhase.Pairs, pairResult.Count, 0));
|
||||
|
||||
Debug.WriteLine($"[Fill(groupParts,Box)] Pair: {pairResult.Count} parts | Winner: {(IsBetterFill(pairResult, best, workArea) ? "Pair" : "Linear")}");
|
||||
|
||||
if (IsBetterFill(pairResult, best, workArea))
|
||||
{
|
||||
best = pairResult;
|
||||
ReportProgress(progress, NestPhase.Pairs, PlateNumber, best, workArea, BuildProgressSummary());
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var extentsFiller = new FillExtents(workArea, Plate.PartSpacing);
|
||||
var bestFits2 = BestFitCache.GetOrCompute(
|
||||
groupParts[0].BaseDrawing, Plate.Size.Length, Plate.Size.Width, Plate.PartSpacing);
|
||||
var extentsAngles2 = new[] { groupParts[0].Rotation, groupParts[0].Rotation + Angle.HalfPI };
|
||||
List<Part> bestExtents2 = null;
|
||||
|
||||
foreach (var angle in extentsAngles2)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
var result = extentsFiller.Fill(groupParts[0].BaseDrawing, angle, PlateNumber, token, progress, bestFits2);
|
||||
if (result != null && result.Count > (bestExtents2?.Count ?? 0))
|
||||
bestExtents2 = result;
|
||||
}
|
||||
|
||||
PhaseResults.Add(new PhaseResult(NestPhase.Extents, bestExtents2?.Count ?? 0, 0));
|
||||
Debug.WriteLine($"[Fill(groupParts,Box)] Extents: {bestExtents2?.Count ?? 0} parts");
|
||||
|
||||
if (IsBetterFill(bestExtents2, best, workArea))
|
||||
{
|
||||
best = bestExtents2;
|
||||
ReportProgress(progress, NestPhase.Extents, PlateNumber, best, workArea, BuildProgressSummary());
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Debug.WriteLine("[Fill(groupParts,Box)] Cancelled, returning current best");
|
||||
}
|
||||
}
|
||||
|
||||
// Always report the final winner so the UI's temporary parts
|
||||
// match the returned result.
|
||||
var winPhase = PhaseResults.Count > 0
|
||||
? PhaseResults.OrderByDescending(r => r.PartCount).First().Phase
|
||||
: NestPhase.Linear;
|
||||
ReportProgress(progress, winPhase, PlateNumber, best, workArea, BuildProgressSummary());
|
||||
ReportProgress(progress, NestPhase.Linear, PlateNumber, best, workArea, BuildProgressSummary(),
|
||||
isOverallBest: true);
|
||||
|
||||
return best ?? new List<Part>();
|
||||
}
|
||||
@@ -201,8 +136,13 @@ namespace OpenNest
|
||||
context.CurrentBest = result;
|
||||
context.CurrentBestScore = FillScore.Compute(result, context.WorkArea);
|
||||
context.WinnerPhase = strategy.Phase;
|
||||
ReportProgress(context.Progress, strategy.Phase, PlateNumber,
|
||||
result, context.WorkArea, BuildProgressSummary());
|
||||
}
|
||||
|
||||
if (context.CurrentBest != null && context.CurrentBest.Count > 0)
|
||||
{
|
||||
ReportProgress(context.Progress, context.WinnerPhase, PlateNumber,
|
||||
context.CurrentBest, context.WorkArea, BuildProgressSummary(),
|
||||
isOverallBest: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -214,13 +154,5 @@ namespace OpenNest
|
||||
angleBuilder.RecordProductive(context.AngleResults);
|
||||
}
|
||||
|
||||
// --- Pattern helpers ---
|
||||
|
||||
internal static Pattern BuildRotatedPattern(List<Part> groupParts, double angle)
|
||||
=> FillHelpers.BuildRotatedPattern(groupParts, angle);
|
||||
|
||||
internal static List<Part> FillPattern(FillLinear engine, List<Part> groupParts, List<double> angles, Box workArea)
|
||||
=> FillHelpers.FillPattern(engine, groupParts, angles, workArea);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps an IProgress to prepend previously placed parts to each report,
|
||||
114
OpenNest.Engine/Fill/AngleCandidateBuilder.cs
Normal file
114
OpenNest.Engine/Fill/AngleCandidateBuilder.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using OpenNest.Engine.ML;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds candidate rotation angles for single-item fill. Encapsulates the
|
||||
/// full pipeline: base angles, narrow-area sweep, ML prediction, and
|
||||
/// known-good pruning across fills.
|
||||
/// </summary>
|
||||
public class AngleCandidateBuilder
|
||||
{
|
||||
private readonly HashSet<double> knownGoodAngles = new();
|
||||
|
||||
public bool ForceFullSweep { get; set; }
|
||||
|
||||
public List<double> Build(NestItem item, double bestRotation, Box workArea)
|
||||
{
|
||||
var baseAngles = new[] { bestRotation, bestRotation + Angle.HalfPI };
|
||||
|
||||
if (knownGoodAngles.Count > 0 && !ForceFullSweep)
|
||||
return BuildPrunedList(baseAngles);
|
||||
|
||||
var angles = new List<double>(baseAngles);
|
||||
|
||||
if (NeedsSweep(item, bestRotation, workArea))
|
||||
AddSweepAngles(angles);
|
||||
|
||||
if (!ForceFullSweep && angles.Count > 2)
|
||||
angles = ApplyMlPrediction(item, workArea, baseAngles, angles);
|
||||
|
||||
return angles;
|
||||
}
|
||||
|
||||
private bool NeedsSweep(NestItem item, double bestRotation, Box workArea)
|
||||
{
|
||||
var testPart = new Part(item.Drawing);
|
||||
if (!bestRotation.IsEqualTo(0))
|
||||
testPart.Rotate(bestRotation);
|
||||
testPart.UpdateBounds();
|
||||
|
||||
var partLongestSide = System.Math.Max(testPart.BoundingBox.Width, testPart.BoundingBox.Length);
|
||||
var workAreaShortSide = System.Math.Min(workArea.Width, workArea.Length);
|
||||
return workAreaShortSide < partLongestSide || ForceFullSweep;
|
||||
}
|
||||
|
||||
private static void AddSweepAngles(List<double> angles)
|
||||
{
|
||||
var step = Angle.ToRadians(5);
|
||||
for (var a = 0.0; a < System.Math.PI; a += step)
|
||||
{
|
||||
if (!ContainsAngle(angles, a))
|
||||
angles.Add(a);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<double> ApplyMlPrediction(
|
||||
NestItem item, Box workArea, double[] baseAngles, List<double> fallback)
|
||||
{
|
||||
var features = FeatureExtractor.Extract(item.Drawing);
|
||||
if (features == null)
|
||||
return fallback;
|
||||
|
||||
var predicted = AnglePredictor.PredictAngles(features, workArea.Width, workArea.Length);
|
||||
if (predicted == null)
|
||||
return fallback;
|
||||
|
||||
var mlAngles = new List<double>(predicted);
|
||||
foreach (var b in baseAngles)
|
||||
{
|
||||
if (!ContainsAngle(mlAngles, b))
|
||||
mlAngles.Add(b);
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[AngleCandidateBuilder] ML: {fallback.Count} angles -> {mlAngles.Count} predicted");
|
||||
return mlAngles;
|
||||
}
|
||||
|
||||
private List<double> BuildPrunedList(double[] baseAngles)
|
||||
{
|
||||
var pruned = new List<double>(baseAngles);
|
||||
foreach (var a in knownGoodAngles)
|
||||
{
|
||||
if (!ContainsAngle(pruned, a))
|
||||
pruned.Add(a);
|
||||
}
|
||||
|
||||
Debug.WriteLine($"[AngleCandidateBuilder] Pruned to {pruned.Count} angles (known-good)");
|
||||
return pruned;
|
||||
}
|
||||
|
||||
private static bool ContainsAngle(List<double> angles, double angle)
|
||||
{
|
||||
return angles.Any(existing => existing.IsEqualTo(angle));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records angles that produced results. These are used to prune
|
||||
/// subsequent Build() calls.
|
||||
/// </summary>
|
||||
public void RecordProductive(List<AngleResult> angleResults)
|
||||
{
|
||||
foreach (var ar in angleResults)
|
||||
{
|
||||
if (ar.PartCount > 0)
|
||||
knownGoodAngles.Add(Angle.ToRadians(ar.AngleDeg));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
178
OpenNest.Engine/Fill/Compactor.cs
Normal file
178
OpenNest.Engine/Fill/Compactor.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
/// <summary>
|
||||
/// Pushes a group of parts left and down to close gaps after placement.
|
||||
/// Uses the same directional-distance logic as PlateView.PushSelected
|
||||
/// but operates on Part objects directly.
|
||||
/// </summary>
|
||||
public static class Compactor
|
||||
{
|
||||
private const double ChordTolerance = 0.001;
|
||||
|
||||
public static double Push(List<Part> movingParts, Plate plate, PushDirection direction)
|
||||
{
|
||||
var obstacleParts = plate.Parts
|
||||
.Where(p => !movingParts.Contains(p))
|
||||
.ToList();
|
||||
|
||||
return Push(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up).
|
||||
/// </summary>
|
||||
public static double Push(List<Part> movingParts, Plate plate, double angle)
|
||||
{
|
||||
var obstacleParts = plate.Parts
|
||||
.Where(p => !movingParts.Contains(p))
|
||||
.ToList();
|
||||
|
||||
var direction = new Vector(System.Math.Cos(angle), System.Math.Sin(angle));
|
||||
return Push(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts along an arbitrary angle (radians, 0 = right, π/2 = up).
|
||||
/// </summary>
|
||||
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
||||
Box workArea, double partSpacing, Vector direction)
|
||||
{
|
||||
var opposite = -direction;
|
||||
|
||||
var obstacleBoxes = new Box[obstacleParts.Count];
|
||||
var obstacleLines = new List<Line>[obstacleParts.Count];
|
||||
|
||||
for (var i = 0; i < obstacleParts.Count; i++)
|
||||
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
|
||||
|
||||
var halfSpacing = partSpacing / 2;
|
||||
var distance = double.MaxValue;
|
||||
|
||||
foreach (var moving in movingParts)
|
||||
{
|
||||
var edgeDist = SpatialQuery.EdgeDistance(moving.BoundingBox, workArea, direction);
|
||||
if (edgeDist <= 0)
|
||||
distance = 0;
|
||||
else if (edgeDist < distance)
|
||||
distance = edgeDist;
|
||||
|
||||
var movingBox = moving.BoundingBox;
|
||||
List<Line> movingLines = null;
|
||||
|
||||
for (var i = 0; i < obstacleBoxes.Length; i++)
|
||||
{
|
||||
var reverseGap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], opposite);
|
||||
if (reverseGap > 0)
|
||||
continue;
|
||||
|
||||
var gap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], direction);
|
||||
if (gap >= distance)
|
||||
continue;
|
||||
|
||||
if (!SpatialQuery.PerpendicularOverlap(movingBox, obstacleBoxes[i], direction))
|
||||
continue;
|
||||
|
||||
movingLines ??= halfSpacing > 0
|
||||
? PartGeometry.GetOffsetPartLines(moving, halfSpacing, direction, ChordTolerance)
|
||||
: PartGeometry.GetPartLines(moving, direction, ChordTolerance);
|
||||
|
||||
obstacleLines[i] ??= halfSpacing > 0
|
||||
? PartGeometry.GetOffsetPartLines(obstacleParts[i], halfSpacing, opposite, ChordTolerance)
|
||||
: PartGeometry.GetPartLines(obstacleParts[i], opposite, ChordTolerance);
|
||||
|
||||
var d = SpatialQuery.DirectionalDistance(movingLines, obstacleLines[i], direction);
|
||||
if (d < distance)
|
||||
distance = d;
|
||||
}
|
||||
}
|
||||
|
||||
if (distance < double.MaxValue && distance > 0)
|
||||
{
|
||||
var offset = direction * distance;
|
||||
foreach (var moving in movingParts)
|
||||
moving.Offset(offset);
|
||||
return distance;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static double Push(List<Part> movingParts, List<Part> obstacleParts,
|
||||
Box workArea, double partSpacing, PushDirection direction)
|
||||
{
|
||||
var vector = SpatialQuery.DirectionToOffset(direction, 1.0);
|
||||
return Push(movingParts, obstacleParts, workArea, partSpacing, vector);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pushes movingParts using bounding-box distances only (no geometry lines).
|
||||
/// Much faster but less precise — use as a coarse positioning pass before
|
||||
/// a full geometry Push.
|
||||
/// </summary>
|
||||
public static double PushBoundingBox(List<Part> movingParts, Plate plate, PushDirection direction)
|
||||
{
|
||||
var obstacleParts = plate.Parts
|
||||
.Where(p => !movingParts.Contains(p))
|
||||
.ToList();
|
||||
|
||||
return PushBoundingBox(movingParts, obstacleParts, plate.WorkArea(), plate.PartSpacing, direction);
|
||||
}
|
||||
|
||||
public static double PushBoundingBox(List<Part> movingParts, List<Part> obstacleParts,
|
||||
Box workArea, double partSpacing, PushDirection direction)
|
||||
{
|
||||
var obstacleBoxes = new Box[obstacleParts.Count];
|
||||
for (var i = 0; i < obstacleParts.Count; i++)
|
||||
obstacleBoxes[i] = obstacleParts[i].BoundingBox;
|
||||
|
||||
var opposite = SpatialQuery.OppositeDirection(direction);
|
||||
var isHorizontal = SpatialQuery.IsHorizontalDirection(direction);
|
||||
var distance = double.MaxValue;
|
||||
|
||||
foreach (var moving in movingParts)
|
||||
{
|
||||
var edgeDist = SpatialQuery.EdgeDistance(moving.BoundingBox, workArea, direction);
|
||||
if (edgeDist <= 0)
|
||||
distance = 0;
|
||||
else if (edgeDist < distance)
|
||||
distance = edgeDist;
|
||||
|
||||
var movingBox = moving.BoundingBox;
|
||||
|
||||
for (var i = 0; i < obstacleBoxes.Length; i++)
|
||||
{
|
||||
var reverseGap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], opposite);
|
||||
if (reverseGap > 0)
|
||||
continue;
|
||||
|
||||
var perpOverlap = isHorizontal
|
||||
? movingBox.IsHorizontalTo(obstacleBoxes[i], out _)
|
||||
: movingBox.IsVerticalTo(obstacleBoxes[i], out _);
|
||||
|
||||
if (!perpOverlap)
|
||||
continue;
|
||||
|
||||
var gap = SpatialQuery.DirectionalGap(movingBox, obstacleBoxes[i], direction);
|
||||
var d = gap - partSpacing;
|
||||
if (d < 0) d = 0;
|
||||
if (d < distance)
|
||||
distance = d;
|
||||
}
|
||||
}
|
||||
|
||||
if (distance < double.MaxValue && distance > 0)
|
||||
{
|
||||
var offset = SpatialQuery.DirectionToOffset(direction, distance);
|
||||
foreach (var moving in movingParts)
|
||||
moving.Offset(offset);
|
||||
return distance;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
public class FillExtents
|
||||
{
|
||||
@@ -173,18 +173,12 @@ namespace OpenNest
|
||||
if (minSlide >= double.MaxValue || minSlide < 0)
|
||||
return pairHeight + partSpacing;
|
||||
|
||||
// Boundaries are inflated by halfSpacing, so when inflated edges touch
|
||||
// the actual parts have partSpacing gap. Match FillLinear's pattern:
|
||||
// startOffset = pairHeight (no extra spacing), copyDist = height - slide.
|
||||
// Match FillLinear.ComputeCopyDistance: copyDist = startOffset - slide,
|
||||
// clamped so it never goes below pairHeight + partSpacing to prevent
|
||||
// bounding-box overlap from spurious slide values.
|
||||
var copyDist = pairHeight - minSlide;
|
||||
|
||||
// Boundaries are inflated by halfSpacing, so the geometry-aware
|
||||
// distance already guarantees partSpacing gap. Only fall back to
|
||||
// bounding-box distance if the calculation produced a non-positive value.
|
||||
if (copyDist <= Tolerance.Epsilon)
|
||||
return pairHeight + partSpacing;
|
||||
|
||||
return copyDist;
|
||||
return System.Math.Max(copyDist, pairHeight + partSpacing);
|
||||
}
|
||||
|
||||
private static double SlideDistance(
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
public class FillLinear
|
||||
{
|
||||
@@ -16,7 +16,7 @@ namespace OpenNest
|
||||
public Box WorkArea { get; }
|
||||
|
||||
public double PartSpacing { get; }
|
||||
|
||||
|
||||
public double HalfSpacing => PartSpacing / 2;
|
||||
|
||||
/// <summary>
|
||||
@@ -110,47 +110,40 @@ namespace OpenNest
|
||||
var pushDir = GetPushDirection(direction);
|
||||
var opposite = SpatialQuery.OppositeDirection(pushDir);
|
||||
|
||||
// Compute a starting offset large enough that every part-pair in
|
||||
// patternB has its offset geometry beyond patternA's offset geometry.
|
||||
var maxUpper = double.MinValue;
|
||||
var minLower = double.MaxValue;
|
||||
|
||||
for (var i = 0; i < patternA.Parts.Count; i++)
|
||||
{
|
||||
var bb = patternA.Parts[i].BoundingBox;
|
||||
var upper = direction == NestDirection.Horizontal ? bb.Right : bb.Top;
|
||||
var lower = direction == NestDirection.Horizontal ? bb.Left : bb.Bottom;
|
||||
|
||||
if (upper > maxUpper) maxUpper = upper;
|
||||
if (lower < minLower) minLower = lower;
|
||||
}
|
||||
|
||||
var startOffset = System.Math.Max(bboxDim,
|
||||
maxUpper - minLower + PartSpacing + Tolerance.Epsilon);
|
||||
|
||||
// bboxDim already spans max(upper) - min(lower) across all parts,
|
||||
// so the start offset just needs to push beyond that plus spacing.
|
||||
var startOffset = bboxDim + PartSpacing + Tolerance.Epsilon;
|
||||
var offset = MakeOffset(direction, startOffset);
|
||||
|
||||
// Pre-cache edge arrays.
|
||||
var movingEdges = new (Vector start, Vector end)[patternA.Parts.Count][];
|
||||
var stationaryEdges = new (Vector start, Vector end)[patternA.Parts.Count][];
|
||||
var maxCopyDistance = FindMaxPairDistance(
|
||||
patternA.Parts, boundaries, offset, pushDir, opposite, startOffset);
|
||||
|
||||
for (var i = 0; i < patternA.Parts.Count; i++)
|
||||
{
|
||||
movingEdges[i] = boundaries[i].GetEdges(pushDir);
|
||||
stationaryEdges[i] = boundaries[i].GetEdges(opposite);
|
||||
}
|
||||
if (maxCopyDistance < Tolerance.Epsilon)
|
||||
return bboxDim + PartSpacing;
|
||||
|
||||
return maxCopyDistance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests every pair of parts across adjacent pattern copies and returns the
|
||||
/// maximum copy distance found. Returns 0 if no valid slide was found.
|
||||
/// </summary>
|
||||
private static double FindMaxPairDistance(
|
||||
List<Part> parts, PartBoundary[] boundaries, Vector offset,
|
||||
PushDirection pushDir, PushDirection opposite, double startOffset)
|
||||
{
|
||||
var maxCopyDistance = 0.0;
|
||||
|
||||
for (var j = 0; j < patternA.Parts.Count; j++)
|
||||
for (var j = 0; j < parts.Count; j++)
|
||||
{
|
||||
var locationB = patternA.Parts[j].Location + offset;
|
||||
var movingEdges = boundaries[j].GetEdges(pushDir);
|
||||
var locationB = parts[j].Location + offset;
|
||||
|
||||
for (var i = 0; i < patternA.Parts.Count; i++)
|
||||
for (var i = 0; i < parts.Count; i++)
|
||||
{
|
||||
var slideDistance = SpatialQuery.DirectionalDistance(
|
||||
movingEdges[j], locationB,
|
||||
stationaryEdges[i], patternA.Parts[i].Location,
|
||||
movingEdges, locationB,
|
||||
boundaries[i].GetEdges(opposite), parts[i].Location,
|
||||
pushDir);
|
||||
|
||||
if (slideDistance >= double.MaxValue || slideDistance < 0)
|
||||
@@ -163,9 +156,6 @@ namespace OpenNest
|
||||
}
|
||||
}
|
||||
|
||||
if (maxCopyDistance < Tolerance.Epsilon)
|
||||
return bboxDim + PartSpacing;
|
||||
|
||||
return maxCopyDistance;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
public readonly struct FillScore : System.IComparable<FillScore>
|
||||
{
|
||||
@@ -1,23 +1,35 @@
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.Engine.Strategies;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.Engine.BestFit;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
/// <summary>
|
||||
/// Fills a work area using interlocking part pairs from BestFitCache.
|
||||
/// Extracted from DefaultNestEngine.FillWithPairs.
|
||||
/// </summary>
|
||||
public class PairFiller
|
||||
{
|
||||
private const int MaxTopCandidates = 50;
|
||||
private const int MaxStripCandidates = 100;
|
||||
private const double MinStripUtilization = 0.3;
|
||||
private const int EarlyExitMinTried = 10;
|
||||
private const int EarlyExitStaleLimit = 10;
|
||||
|
||||
private readonly Size plateSize;
|
||||
private readonly double partSpacing;
|
||||
|
||||
/// <summary>
|
||||
/// The best-fit results computed during the last Fill call.
|
||||
/// Available after Fill returns so callers can reuse without recomputing.
|
||||
/// </summary>
|
||||
public List<BestFitResult> BestFits { get; private set; }
|
||||
|
||||
public PairFiller(Size plateSize, double partSpacing)
|
||||
{
|
||||
this.plateSize = plateSize;
|
||||
@@ -29,11 +41,11 @@ namespace OpenNest
|
||||
CancellationToken token = default,
|
||||
IProgress<NestProgress> progress = null)
|
||||
{
|
||||
var bestFits = BestFitCache.GetOrCompute(
|
||||
BestFits = BestFitCache.GetOrCompute(
|
||||
item.Drawing, plateSize.Length, plateSize.Width, partSpacing);
|
||||
|
||||
var candidates = SelectPairCandidates(bestFits, workArea);
|
||||
Debug.WriteLine($"[PairFiller] Total: {bestFits.Count}, Kept: {bestFits.Count(r => r.Keep)}, Trying: {candidates.Count}");
|
||||
var candidates = SelectPairCandidates(BestFits, workArea);
|
||||
Debug.WriteLine($"[PairFiller] Total: {BestFits.Count}, Kept: {BestFits.Count(r => r.Keep)}, Trying: {candidates.Count}");
|
||||
Debug.WriteLine($"[PairFiller] Plate: {plateSize.Length:F2}x{plateSize.Width:F2}, WorkArea: {workArea.Width:F2}x{workArea.Length:F2}");
|
||||
|
||||
List<Part> best = null;
|
||||
@@ -46,17 +58,7 @@ namespace OpenNest
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var result = candidates[i];
|
||||
var pairParts = result.BuildParts(item.Drawing);
|
||||
var angles = result.HullAngles;
|
||||
var engine = new FillLinear(workArea, partSpacing);
|
||||
|
||||
// Let the remainder strip try pair-based filling too.
|
||||
var p0 = DefaultNestEngine.BuildRotatedPattern(pairParts, 0);
|
||||
var p90 = DefaultNestEngine.BuildRotatedPattern(pairParts, Angle.HalfPI);
|
||||
engine.RemainderPatterns = new List<Pattern> { p0, p90 };
|
||||
|
||||
var filled = DefaultNestEngine.FillPattern(engine, pairParts, angles, workArea);
|
||||
var filled = EvaluateCandidate(candidates[i], item.Drawing, workArea);
|
||||
|
||||
if (filled != null && filled.Count > 0)
|
||||
{
|
||||
@@ -80,8 +82,7 @@ namespace OpenNest
|
||||
NestEngineBase.ReportProgress(progress, NestPhase.Pairs, plateNumber, best, workArea,
|
||||
$"Pairs: {i + 1}/{candidates.Count} candidates, best = {bestScore.Count} parts");
|
||||
|
||||
// Early exit: stop if we've tried enough candidates without improvement.
|
||||
if (i >= 9 && sinceImproved >= 10)
|
||||
if (i + 1 >= EarlyExitMinTried && sinceImproved >= EarlyExitStaleLimit)
|
||||
{
|
||||
Debug.WriteLine($"[PairFiller] Early exit at {i + 1}/{candidates.Count} — no improvement in last {sinceImproved} candidates");
|
||||
break;
|
||||
@@ -97,10 +98,22 @@ namespace OpenNest
|
||||
return best ?? new List<Part>();
|
||||
}
|
||||
|
||||
private List<Part> EvaluateCandidate(BestFitResult candidate, Drawing drawing, Box workArea)
|
||||
{
|
||||
var pairParts = candidate.BuildParts(drawing);
|
||||
var engine = new FillLinear(workArea, partSpacing);
|
||||
|
||||
var p0 = FillHelpers.BuildRotatedPattern(pairParts, 0);
|
||||
var p90 = FillHelpers.BuildRotatedPattern(pairParts, Angle.HalfPI);
|
||||
engine.RemainderPatterns = new List<Pattern> { p0, p90 };
|
||||
|
||||
return FillHelpers.FillPattern(engine, pairParts, candidate.HullAngles, workArea);
|
||||
}
|
||||
|
||||
private List<BestFitResult> SelectPairCandidates(List<BestFitResult> bestFits, Box workArea)
|
||||
{
|
||||
var kept = bestFits.Where(r => r.Keep).ToList();
|
||||
var top = kept.Take(50).ToList();
|
||||
var top = kept.Take(MaxTopCandidates).ToList();
|
||||
|
||||
var workShortSide = System.Math.Min(workArea.Width, workArea.Length);
|
||||
var plateShortSide = System.Math.Min(plateSize.Width, plateSize.Length);
|
||||
@@ -109,14 +122,14 @@ namespace OpenNest
|
||||
{
|
||||
var stripCandidates = bestFits
|
||||
.Where(r => r.ShortestSide <= workShortSide + Tolerance.Epsilon
|
||||
&& r.Utilization >= 0.3)
|
||||
&& r.Utilization >= MinStripUtilization)
|
||||
.OrderByDescending(r => r.Utilization);
|
||||
|
||||
var existing = new HashSet<BestFitResult>(top);
|
||||
|
||||
foreach (var r in stripCandidates)
|
||||
{
|
||||
if (top.Count >= 100)
|
||||
if (top.Count >= MaxStripCandidates)
|
||||
break;
|
||||
|
||||
if (existing.Add(r))
|
||||
@@ -1,9 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
/// <summary>
|
||||
/// Pre-computed offset boundary polygons for a part's geometry.
|
||||
@@ -87,9 +87,9 @@ namespace OpenNest
|
||||
var edge = (verts[i - 1], verts[i]);
|
||||
|
||||
if (-sign * dy > 0) left.Add(edge);
|
||||
if ( sign * dy > 0) right.Add(edge);
|
||||
if (sign * dy > 0) right.Add(edge);
|
||||
if (-sign * dx > 0) up.Add(edge);
|
||||
if ( sign * dx > 0) down.Add(edge);
|
||||
if (sign * dx > 0) down.Add(edge);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,11 +145,11 @@ namespace OpenNest
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return _leftEdges;
|
||||
case PushDirection.Left: return _leftEdges;
|
||||
case PushDirection.Right: return _rightEdges;
|
||||
case PushDirection.Up: return _upEdges;
|
||||
case PushDirection.Down: return _downEdges;
|
||||
default: return _leftEdges;
|
||||
case PushDirection.Up: return _upEdges;
|
||||
case PushDirection.Down: return _downEdges;
|
||||
default: return _leftEdges;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
public class Pattern
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Engine
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
public static class PatternTiler
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
/// <summary>
|
||||
/// Iteratively fills remnant boxes with items using a RemnantFinder.
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
/// <summary>
|
||||
/// A remnant box with a priority tier.
|
||||
@@ -1,10 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
internal static class RotationAnalysis
|
||||
{
|
||||
@@ -1,10 +1,10 @@
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Fill
|
||||
{
|
||||
public enum ShrinkAxis { Width, Height }
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Microsoft.ML.OnnxRuntime;
|
||||
using Microsoft.ML.OnnxRuntime.Tensors;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Engine.ML
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Engine.ML
|
||||
{
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Geometry;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Engine.ML
|
||||
{
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -190,7 +191,8 @@ namespace OpenNest
|
||||
int plateNumber,
|
||||
List<Part> best,
|
||||
Box workArea,
|
||||
string description)
|
||||
string description,
|
||||
bool isOverallBest = false)
|
||||
{
|
||||
if (progress == null || best == null || best.Count == 0)
|
||||
return;
|
||||
@@ -212,9 +214,13 @@ namespace OpenNest
|
||||
$"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(
|
||||
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 { }
|
||||
$"{DateTime.Now:HH:mm:ss.fff} {msg}\n");
|
||||
}
|
||||
catch { }
|
||||
|
||||
progress.Report(new NestProgress
|
||||
{
|
||||
@@ -228,6 +234,7 @@ namespace OpenNest
|
||||
BestParts = clonedParts,
|
||||
Description = description,
|
||||
ActiveWorkArea = workArea,
|
||||
IsOverallBest = isOverallBest,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -46,5 +46,6 @@ namespace OpenNest
|
||||
public List<Part> BestParts { get; set; }
|
||||
public string Description { get; set; }
|
||||
public Box ActiveWorkArea { get; set; }
|
||||
public bool IsOverallBest { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Nfp
|
||||
{
|
||||
/// <summary>
|
||||
/// Mixed-part geometry-aware nesting using NFP-based collision avoidance
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Nfp
|
||||
{
|
||||
/// <summary>
|
||||
/// NFP-based Bottom-Left Fill (BLF) placement engine.
|
||||
@@ -1,8 +1,9 @@
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Nfp
|
||||
{
|
||||
/// <summary>
|
||||
/// Result of a nest optimization run.
|
||||
@@ -1,8 +1,8 @@
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Nfp
|
||||
{
|
||||
/// <summary>
|
||||
/// Caches computed No-Fit Polygons keyed by (DrawingA.Id, RotationA, DrawingB.Id, RotationB).
|
||||
@@ -1,11 +1,12 @@
|
||||
using OpenNest.Engine.Fill;
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest.Engine.Nfp
|
||||
{
|
||||
/// <summary>
|
||||
/// Simulated annealing optimizer for NFP-based nesting.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user