mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-04 11:21:37 -05:00
The best-fit filter rejected pairs over a 5:1 aspect (or the plate's) unless 75% utilized. A long part such as U251-DET3 (27.4 x 3.5, aspect 7.7) is past that alone, so its tightest interlocked pairs (5.1:1) were all dropped even though they are less elongated than the part itself. On the 60 x 120 sheet that cost 12 parts: 66 placed instead of 78. The limit now widens to the part's own bounding-box aspect, so side-by-side pairs survive while end-to-end pairs of compact parts are still rejected. The learned-angle characterization nests re-captured: their 46 x 3 bar now nests as an interlocked pair, which left the Fill nest identical to a full sweep, so the notched quantity drops from 6 to 4 to keep the fixture sensitive to learned angles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
3.1 KiB
C#
84 lines
3.1 KiB
C#
using System.Collections.Generic;
|
|
|
|
namespace OpenNest.Engine.BestFit
|
|
{
|
|
public class BestFitFilter
|
|
{
|
|
public double MaxPlateWidth { get; set; }
|
|
public double MaxPlateHeight { get; set; }
|
|
public double MaxAspectRatio { get; set; } = 5.0;
|
|
|
|
/// <summary>
|
|
/// A high-aspect pair is kept anyway when this much of its rotated bounding box is
|
|
/// actual part area. Utilization is only ever an exception here, never a rejection:
|
|
/// thin-framed, hollow, or concave (e.g. S-shaped) parts have inherently low
|
|
/// part-to-bbox utilization, yet can nest tightly — their quality is judged by the
|
|
/// pair bounding-box area the results are sorted on, not by utilization.
|
|
/// </summary>
|
|
public double UtilizationOverride { get; set; } = 0.75;
|
|
|
|
public void Apply(List<BestFitResult> results)
|
|
{
|
|
var partAspects = new Dictionary<Drawing, double>(ReferenceEqualityComparer.Instance);
|
|
|
|
foreach (var result in results)
|
|
{
|
|
if (!result.Keep)
|
|
continue;
|
|
|
|
if (
|
|
result.ShortestSide > System.Math.Min(MaxPlateWidth, MaxPlateHeight)
|
|
|| result.LongestSide > System.Math.Max(MaxPlateWidth, MaxPlateHeight)
|
|
)
|
|
{
|
|
result.Keep = false;
|
|
result.Reason = "Exceeds plate dimensions";
|
|
continue;
|
|
}
|
|
|
|
var aspect = result.LongestSide / result.ShortestSide;
|
|
var maxAspect = System.Math.Max(
|
|
MaxAspectRatio,
|
|
PartAspect(result.Candidate?.Drawing, partAspects)
|
|
);
|
|
|
|
if (aspect > maxAspect && result.Utilization < UtilizationOverride)
|
|
{
|
|
result.Keep = false;
|
|
result.Reason = string.Format(
|
|
"Aspect ratio {0:F1} exceeds max {1:0.#}",
|
|
aspect,
|
|
maxAspect
|
|
);
|
|
continue;
|
|
}
|
|
|
|
result.Reason = "Valid";
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// A pair is never held to a tighter aspect limit than one part alone: two long, thin
|
|
/// parts laid side by side are less elongated than either, so a fixed limit would reject
|
|
/// every pair of a part longer than it. Measured on the drawing's own frame (canonical
|
|
/// for cached pairs); any other frame only understates the aspect.
|
|
/// </summary>
|
|
private static double PartAspect(Drawing drawing, Dictionary<Drawing, double> cache)
|
|
{
|
|
if (drawing?.Program == null)
|
|
return 0;
|
|
|
|
if (!cache.TryGetValue(drawing, out var aspect))
|
|
{
|
|
var box = drawing.Program.BoundingBox();
|
|
var shortest = System.Math.Min(box.Width, box.Length);
|
|
aspect =
|
|
shortest > 0 ? System.Math.Max(box.Width, box.Length) / shortest : 0;
|
|
cache[drawing] = aspect;
|
|
}
|
|
|
|
return aspect;
|
|
}
|
|
}
|
|
}
|