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>
129 lines
3.7 KiB
C#
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,
|
|
}
|
|
}
|