Files
OpenNest/OpenNest.Engine/BestFit/BestFitResult.cs
T
ajandClaude Opus 5.5 1b5e1b14a6 fix: leave etch/scribe marks out of nesting geometry
Every nesting-geometry consumer filtered only rapids, so scribe/etch
moves counted as part material. An etch tick that ends a hair outside
the outline (PEP bend ticks start on the notch edge) made the part
"open geometry leaving the material region": the job validator threw
and every built-in engine plus Gpt6Astra crashed on real PEP jobs
(PT75, drawing 4980 A01 PT77). Marks are only on the surface, so they
should never affect placement, collision, area, or validation.

- SpecialLayers.IsMaterial excludes Rapid and Scribe; used by drawing
  area, canonical angle, part collision, PartGeometry, plate perimeter,
  best-fit/pair evaluation, rotation analysis, GPU evaluators, and both
  validators. Timing, display, splitting and posts still see marks.
- ConvertGeometry also maps the saved SCRIBE layer name to Scribe, so
  programs rebuilt from stored entities keep their marks.
- NestReader repairs older files (e.g. PepNestExport output) whose
  programs saved etch as cut moves while source entities kept SCRIBE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 06:44:29 -04:00

129 lines
3.7 KiB
C#

using System.Collections.Generic;
using System.Linq;
using OpenNest.Converters;
using OpenNest.Engine;
using OpenNest.Geometry;
using OpenNest.Math;
namespace OpenNest.Engine.BestFit
{
public class BestFitResult
{
public PairCandidate Candidate { get; set; }
public double RotatedArea { get; set; }
public double BoundingWidth { get; set; }
public double BoundingHeight { get; set; }
public double OptimalRotation { get; set; }
public bool Keep { get; set; }
public string Reason { get; set; }
public double TrueArea { get; set; }
public List<double> HullAngles { get; set; }
public double Utilization
{
get { return RotatedArea > 0 ? TrueArea / RotatedArea : 0; }
}
public double LongestSide
{
get { return System.Math.Max(BoundingWidth, BoundingHeight); }
}
public double ShortestSide
{
get { return System.Math.Min(BoundingWidth, BoundingHeight); }
}
public List<Part> BuildParts(Drawing drawing)
{
var part1 = Part.CreateAtOrigin(drawing);
var part2 = Part.CreateAtOrigin(drawing, Candidate.Part2Rotation);
part2.Location = Candidate.Part2Offset;
part2.UpdateBounds();
if (!OptimalRotation.IsEqualTo(0))
{
var pairBounds = (
(IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }
).GetBoundingBox();
var center = pairBounds.Center;
part1.Rotate(-OptimalRotation, center);
part2.Rotate(-OptimalRotation, center);
}
var finalBounds = (
(IEnumerable<IBoundable>)new IBoundable[] { part1, part2 }
).GetBoundingBox();
var offset = new Vector(-finalBounds.Left, -finalBounds.Bottom);
part1.Offset(offset);
part2.Offset(offset);
return new List<Part> { part1, part2 };
}
public List<Part> BuildCanonicalParts()
{
return NormalizeToCutOrigin(BuildParts(Candidate.Drawing));
}
public List<Part> BuildSourceParts(Drawing drawing)
{
var parts = BuildCanonicalParts();
return NormalizeToCutOrigin(CanonicalFrame.RebindToOriginal(parts, drawing));
}
public Box GetCutBounds(List<Part> parts)
{
return GetCutBoundingBox(parts);
}
private static List<Part> NormalizeToCutOrigin(List<Part> parts)
{
if (parts == null || parts.Count == 0)
return parts;
var bounds = GetCutBoundingBox(parts);
var offset = new Vector(-bounds.Left, -bounds.Bottom);
foreach (var part in parts)
part.Offset(offset);
return parts;
}
private static Box GetCutBoundingBox(List<Part> parts)
{
var entities = new List<IBoundable>();
foreach (var part in parts)
{
var partEntities = ConvertProgram
.ToGeometry(part.Program)
.Where(e => SpecialLayers.IsMaterial(e.Layer))
.ToList();
foreach (var entity in partEntities)
{
entity.Offset(part.Location);
entities.Add(entity);
}
}
return entities.GetBoundingBox();
}
}
public enum BestFitSortField
{
Area,
LongestSide,
ShortestSide,
Type,
OriginalSequence,
Keep,
WhyKeepDrop,
}
}