Compare commits
2 Commits
786b6e2e88
...
640814fdf6
| Author | SHA1 | Date | |
|---|---|---|---|
| 640814fdf6 | |||
| 6a30828fad |
@@ -11,6 +11,11 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
private record ContourEntry(Shape Shape, Vector Point, Entity Entity);
|
||||
|
||||
public CuttingResult Apply(Program partProgram, Vector approachPoint)
|
||||
{
|
||||
return Apply(partProgram, approachPoint, Vector.Invalid);
|
||||
}
|
||||
|
||||
public CuttingResult Apply(Program partProgram, Vector approachPoint, Vector nextPartStart)
|
||||
{
|
||||
var entities = partProgram.ToGeometry();
|
||||
entities.RemoveAll(e => e.Layer == SpecialLayers.Rapid);
|
||||
@@ -20,14 +25,43 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
var profile = new ShapeProfile(entities);
|
||||
|
||||
// Forward pass: sequence cutouts nearest-neighbor from perimeter
|
||||
var perimeterPoint = profile.Perimeter.ClosestPointTo(approachPoint, out _);
|
||||
var orderedCutouts = SequenceCutouts(profile.Cutouts, perimeterPoint);
|
||||
// Start from the bounding box corner opposite the origin (max X, max Y)
|
||||
var bbox = entities.GetBoundingBox();
|
||||
var startCorner = new Vector(bbox.Right, bbox.Top);
|
||||
|
||||
// Initial pass: sequence cutouts from bbox corner
|
||||
var seedPoint = startCorner;
|
||||
var orderedCutouts = SequenceCutouts(profile.Cutouts, seedPoint);
|
||||
orderedCutouts.Reverse();
|
||||
|
||||
// Backward pass: walk from perimeter back through cutting order
|
||||
// so each lead-in faces the next cutout to be cut, not the previous
|
||||
var cutoutEntries = ResolveLeadInPoints(orderedCutouts, perimeterPoint);
|
||||
var perimeterSeed = profile.Perimeter.ClosestPointTo(seedPoint, out _);
|
||||
var cutoutEntries = ResolveLeadInPoints(orderedCutouts, perimeterSeed);
|
||||
|
||||
Vector perimeterPt;
|
||||
Entity perimeterEntity;
|
||||
|
||||
if (!double.IsNaN(nextPartStart.X) && cutoutEntries.Count > 0)
|
||||
{
|
||||
// Iterate: each pass refines the perimeter lead-in which changes
|
||||
// the internal sequence which changes the last cutout position
|
||||
for (var iter = 0; iter < 3; iter++)
|
||||
{
|
||||
var lastCutoutPt = cutoutEntries[cutoutEntries.Count - 1].Point;
|
||||
perimeterSeed = FindPerimeterIntersection(profile.Perimeter, lastCutoutPt, nextPartStart, out _);
|
||||
|
||||
orderedCutouts = SequenceCutouts(profile.Cutouts, perimeterSeed);
|
||||
orderedCutouts.Reverse();
|
||||
cutoutEntries = ResolveLeadInPoints(orderedCutouts, perimeterSeed);
|
||||
}
|
||||
|
||||
var finalLastCutout = cutoutEntries[cutoutEntries.Count - 1].Point;
|
||||
perimeterPt = FindPerimeterIntersection(profile.Perimeter, finalLastCutout, nextPartStart, out perimeterEntity);
|
||||
}
|
||||
else
|
||||
{
|
||||
var perimeterRef = cutoutEntries.Count > 0 ? cutoutEntries[0].Point : approachPoint;
|
||||
perimeterPt = profile.Perimeter.ClosestPointTo(perimeterRef, out perimeterEntity);
|
||||
}
|
||||
|
||||
var result = new Program(Mode.Absolute);
|
||||
|
||||
@@ -36,9 +70,6 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
foreach (var entry in cutoutEntries)
|
||||
EmitContour(result, entry.Shape, entry.Point, entry.Entity);
|
||||
|
||||
// Perimeter last
|
||||
var lastRefPoint = cutoutEntries.Count > 0 ? cutoutEntries[cutoutEntries.Count - 1].Point : approachPoint;
|
||||
var perimeterPt = profile.Perimeter.ClosestPointTo(lastRefPoint, out var perimeterEntity);
|
||||
EmitContour(result, profile.Perimeter, perimeterPt, perimeterEntity, ContourType.External);
|
||||
|
||||
result.Mode = Mode.Incremental;
|
||||
@@ -187,6 +218,33 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
return new List<ContourEntry>(entries);
|
||||
}
|
||||
|
||||
private static Vector FindPerimeterIntersection(Shape perimeter, Vector lastCutout, Vector nextPartStart, out Entity entity)
|
||||
{
|
||||
var ray = new Line(lastCutout, nextPartStart);
|
||||
|
||||
if (perimeter.Intersects(ray, out var pts) && pts.Count > 0)
|
||||
{
|
||||
// Pick the intersection closest to the last cutout
|
||||
var best = pts[0];
|
||||
var bestDist = best.DistanceTo(lastCutout);
|
||||
|
||||
for (var i = 1; i < pts.Count; i++)
|
||||
{
|
||||
var dist = pts[i].DistanceTo(lastCutout);
|
||||
if (dist < bestDist)
|
||||
{
|
||||
best = pts[i];
|
||||
bestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
return perimeter.ClosestPointTo(best, out entity);
|
||||
}
|
||||
|
||||
// Fallback: closest point on perimeter to the last cutout
|
||||
return perimeter.ClosestPointTo(lastCutout, out entity);
|
||||
}
|
||||
|
||||
private void EmitContour(Program program, Shape shape, Vector point, Entity entity, ContourType? forceType = null)
|
||||
{
|
||||
var contourType = forceType ?? DetectContourType(shape);
|
||||
|
||||
@@ -62,10 +62,15 @@ namespace OpenNest
|
||||
public CNC.CuttingStrategy.CuttingParameters CuttingParameters { get; set; }
|
||||
|
||||
public void ApplyLeadIns(CNC.CuttingStrategy.CuttingParameters parameters, Vector approachPoint)
|
||||
{
|
||||
ApplyLeadIns(parameters, approachPoint, Geometry.Vector.Invalid);
|
||||
}
|
||||
|
||||
public void ApplyLeadIns(CNC.CuttingStrategy.CuttingParameters parameters, Vector approachPoint, Vector nextPartStart)
|
||||
{
|
||||
preLeadInRotation = Rotation;
|
||||
var strategy = new CNC.CuttingStrategy.ContourCuttingStrategy { Parameters = parameters };
|
||||
var result = strategy.Apply(Program, approachPoint);
|
||||
var result = strategy.Apply(Program, approachPoint, nextPartStart);
|
||||
Program = result.Program;
|
||||
CuttingParameters = parameters;
|
||||
HasManualLeadIns = true;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using OpenNest.CNC.CuttingStrategy;
|
||||
using OpenNest.Engine.Sequencing;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest.Engine
|
||||
@@ -15,14 +17,28 @@ namespace OpenNest.Engine
|
||||
return;
|
||||
|
||||
var sequenced = Sequencer.Sequence(plate.Parts.ToList(), plate);
|
||||
var currentPoint = PlateHelper.GetExitPoint(plate);
|
||||
var exitPoint = PlateHelper.GetExitPoint(plate);
|
||||
|
||||
foreach (var sp in sequenced)
|
||||
// Pass 1: assign lead-ins to establish pierce points
|
||||
var piercePoints = AssignPass(sequenced, parameters, exitPoint, nextPiercePoints: null);
|
||||
|
||||
// Pass 2: re-assign with knowledge of next part's start point
|
||||
AssignPass(sequenced, parameters, exitPoint, nextPiercePoints: piercePoints);
|
||||
}
|
||||
|
||||
private Vector[] AssignPass(List<SequencedPart> sequenced, CuttingParameters parameters,
|
||||
Vector exitPoint, Vector[] nextPiercePoints)
|
||||
{
|
||||
var part = sp.Part;
|
||||
var piercePoints = new Vector[sequenced.Count];
|
||||
var currentPoint = exitPoint;
|
||||
|
||||
for (var i = 0; i < sequenced.Count; i++)
|
||||
{
|
||||
var part = sequenced[i].Part;
|
||||
|
||||
if (part.LeadInsLocked)
|
||||
{
|
||||
piercePoints[i] = GetPiercePoint(part);
|
||||
currentPoint = part.Location;
|
||||
continue;
|
||||
}
|
||||
@@ -31,10 +47,33 @@ namespace OpenNest.Engine
|
||||
part.RemoveLeadIns();
|
||||
|
||||
var localApproach = currentPoint - part.Location;
|
||||
part.ApplyLeadIns(parameters, localApproach);
|
||||
|
||||
if (nextPiercePoints != null && i + 1 < sequenced.Count)
|
||||
{
|
||||
var nextStart = nextPiercePoints[i + 1] - part.Location;
|
||||
part.ApplyLeadIns(parameters, localApproach, nextStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
part.ApplyLeadIns(parameters, localApproach);
|
||||
}
|
||||
|
||||
piercePoints[i] = GetPiercePoint(part);
|
||||
currentPoint = part.Location;
|
||||
}
|
||||
|
||||
return piercePoints;
|
||||
}
|
||||
|
||||
private static Vector GetPiercePoint(Part part)
|
||||
{
|
||||
foreach (var code in part.Program.Codes)
|
||||
{
|
||||
if (code is CNC.Motion motion)
|
||||
return motion.EndPoint + part.Location;
|
||||
}
|
||||
|
||||
return part.Location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,15 +17,38 @@ namespace OpenNest.Engine
|
||||
public PlateProcessingResult Process(Plate plate)
|
||||
{
|
||||
var sequenced = Sequencer.Sequence(plate.Parts.ToList(), plate);
|
||||
var exitPoint = PlateHelper.GetExitPoint(plate);
|
||||
|
||||
// Pass 1: process each part to collect pierce points
|
||||
var piercePoints = new Vector[sequenced.Count];
|
||||
var currentPoint = exitPoint;
|
||||
|
||||
for (var i = 0; i < sequenced.Count; i++)
|
||||
{
|
||||
var part = sequenced[i].Part;
|
||||
|
||||
if (!part.HasManualLeadIns && CuttingStrategy != null)
|
||||
{
|
||||
var localApproach = ToPartLocal(currentPoint, part);
|
||||
var result = CuttingStrategy.Apply(part.Program, localApproach);
|
||||
piercePoints[i] = ToPlateSpace(GetProgramStartPoint(result.Program), part);
|
||||
currentPoint = ToPlateSpace(result.LastCutPoint, part);
|
||||
}
|
||||
else
|
||||
{
|
||||
piercePoints[i] = ToPlateSpace(GetProgramStartPoint(part.Program), part);
|
||||
currentPoint = ToPlateSpace(GetProgramEndPoint(part.Program), part);
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: re-process with next part's start point for perimeter lead-in refinement
|
||||
var results = new List<ProcessedPart>(sequenced.Count);
|
||||
var cutAreas = new List<Shape>();
|
||||
var currentPoint = PlateHelper.GetExitPoint(plate);
|
||||
currentPoint = exitPoint;
|
||||
|
||||
foreach (var sp in sequenced)
|
||||
for (var i = 0; i < sequenced.Count; i++)
|
||||
{
|
||||
var part = sp.Part;
|
||||
|
||||
// Compute approach point in part-local space
|
||||
var part = sequenced[i].Part;
|
||||
var localApproach = ToPartLocal(currentPoint, part);
|
||||
|
||||
Program processedProgram;
|
||||
@@ -33,7 +56,18 @@ namespace OpenNest.Engine
|
||||
|
||||
if (!part.HasManualLeadIns && CuttingStrategy != null)
|
||||
{
|
||||
var cuttingResult = CuttingStrategy.Apply(part.Program, localApproach);
|
||||
CuttingResult cuttingResult;
|
||||
|
||||
if (i + 1 < sequenced.Count)
|
||||
{
|
||||
var nextStart = ToPartLocal(piercePoints[i + 1], part);
|
||||
cuttingResult = CuttingStrategy.Apply(part.Program, localApproach, nextStart);
|
||||
}
|
||||
else
|
||||
{
|
||||
cuttingResult = CuttingStrategy.Apply(part.Program, localApproach);
|
||||
}
|
||||
|
||||
processedProgram = cuttingResult.Program;
|
||||
lastCutLocal = cuttingResult.LastCutPoint;
|
||||
}
|
||||
@@ -43,11 +77,9 @@ namespace OpenNest.Engine
|
||||
lastCutLocal = GetProgramEndPoint(part.Program);
|
||||
}
|
||||
|
||||
// Pierce point: program start point in plate space
|
||||
var pierceLocal = GetProgramStartPoint(processedProgram);
|
||||
var piercePoint = ToPlateSpace(pierceLocal, part);
|
||||
|
||||
// Plan rapid from currentPoint to pierce point
|
||||
var rapidPath = RapidPlanner.Plan(currentPoint, piercePoint, cutAreas);
|
||||
|
||||
results.Add(new ProcessedPart
|
||||
@@ -57,12 +89,10 @@ namespace OpenNest.Engine
|
||||
RapidPath = rapidPath
|
||||
});
|
||||
|
||||
// Update cut areas with part perimeter
|
||||
var perimeter = GetPartPerimeter(part);
|
||||
if (perimeter != null)
|
||||
cutAreas.Add(perimeter);
|
||||
|
||||
// Update current point to last cut point in plate space
|
||||
currentPoint = ToPlateSpace(lastCutLocal, part);
|
||||
}
|
||||
|
||||
|
||||
@@ -621,15 +621,21 @@ namespace OpenNest.Controls
|
||||
|
||||
private void redrawTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
Invalidate();
|
||||
if (IsDisposed || !IsHandleCreated) return;
|
||||
BeginInvoke(new System.Action(Invalidate));
|
||||
}
|
||||
|
||||
private void hoverTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
||||
{
|
||||
if (IsDisposed || !IsHandleCreated) return;
|
||||
BeginInvoke(new System.Action(HoverCheck));
|
||||
}
|
||||
|
||||
private void HoverCheck()
|
||||
{
|
||||
var graphPt = PointControlToGraph(hoverPoint);
|
||||
LayoutPart hitPart = null;
|
||||
try
|
||||
{
|
||||
|
||||
for (var i = parts.Count - 1; i >= 0; --i)
|
||||
{
|
||||
if (parts[i].Path.GetBounds().Contains(graphPt) &&
|
||||
@@ -639,12 +645,6 @@ namespace OpenNest.Controls
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
// GraphicsPath in use by paint thread — skip this hover tick
|
||||
return;
|
||||
}
|
||||
|
||||
hoveredPart = hitPart;
|
||||
showTooltip = hitPart != null;
|
||||
|
||||
Reference in New Issue
Block a user