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);
|
private record ContourEntry(Shape Shape, Vector Point, Entity Entity);
|
||||||
|
|
||||||
public CuttingResult Apply(Program partProgram, Vector approachPoint)
|
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();
|
var entities = partProgram.ToGeometry();
|
||||||
entities.RemoveAll(e => e.Layer == SpecialLayers.Rapid);
|
entities.RemoveAll(e => e.Layer == SpecialLayers.Rapid);
|
||||||
@@ -20,14 +25,43 @@ namespace OpenNest.CNC.CuttingStrategy
|
|||||||
|
|
||||||
var profile = new ShapeProfile(entities);
|
var profile = new ShapeProfile(entities);
|
||||||
|
|
||||||
// Forward pass: sequence cutouts nearest-neighbor from perimeter
|
// Start from the bounding box corner opposite the origin (max X, max Y)
|
||||||
var perimeterPoint = profile.Perimeter.ClosestPointTo(approachPoint, out _);
|
var bbox = entities.GetBoundingBox();
|
||||||
var orderedCutouts = SequenceCutouts(profile.Cutouts, perimeterPoint);
|
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();
|
orderedCutouts.Reverse();
|
||||||
|
|
||||||
// Backward pass: walk from perimeter back through cutting order
|
var perimeterSeed = profile.Perimeter.ClosestPointTo(seedPoint, out _);
|
||||||
// so each lead-in faces the next cutout to be cut, not the previous
|
var cutoutEntries = ResolveLeadInPoints(orderedCutouts, perimeterSeed);
|
||||||
var cutoutEntries = ResolveLeadInPoints(orderedCutouts, perimeterPoint);
|
|
||||||
|
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);
|
var result = new Program(Mode.Absolute);
|
||||||
|
|
||||||
@@ -36,9 +70,6 @@ namespace OpenNest.CNC.CuttingStrategy
|
|||||||
foreach (var entry in cutoutEntries)
|
foreach (var entry in cutoutEntries)
|
||||||
EmitContour(result, entry.Shape, entry.Point, entry.Entity);
|
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);
|
EmitContour(result, profile.Perimeter, perimeterPt, perimeterEntity, ContourType.External);
|
||||||
|
|
||||||
result.Mode = Mode.Incremental;
|
result.Mode = Mode.Incremental;
|
||||||
@@ -187,6 +218,33 @@ namespace OpenNest.CNC.CuttingStrategy
|
|||||||
return new List<ContourEntry>(entries);
|
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)
|
private void EmitContour(Program program, Shape shape, Vector point, Entity entity, ContourType? forceType = null)
|
||||||
{
|
{
|
||||||
var contourType = forceType ?? DetectContourType(shape);
|
var contourType = forceType ?? DetectContourType(shape);
|
||||||
|
|||||||
@@ -62,10 +62,15 @@ namespace OpenNest
|
|||||||
public CNC.CuttingStrategy.CuttingParameters CuttingParameters { get; set; }
|
public CNC.CuttingStrategy.CuttingParameters CuttingParameters { get; set; }
|
||||||
|
|
||||||
public void ApplyLeadIns(CNC.CuttingStrategy.CuttingParameters parameters, Vector approachPoint)
|
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;
|
preLeadInRotation = Rotation;
|
||||||
var strategy = new CNC.CuttingStrategy.ContourCuttingStrategy { Parameters = parameters };
|
var strategy = new CNC.CuttingStrategy.ContourCuttingStrategy { Parameters = parameters };
|
||||||
var result = strategy.Apply(Program, approachPoint);
|
var result = strategy.Apply(Program, approachPoint, nextPartStart);
|
||||||
Program = result.Program;
|
Program = result.Program;
|
||||||
CuttingParameters = parameters;
|
CuttingParameters = parameters;
|
||||||
HasManualLeadIns = true;
|
HasManualLeadIns = true;
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using OpenNest.CNC.CuttingStrategy;
|
||||||
using OpenNest.Engine.Sequencing;
|
using OpenNest.Engine.Sequencing;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace OpenNest.Engine
|
namespace OpenNest.Engine
|
||||||
@@ -15,14 +17,28 @@ namespace OpenNest.Engine
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var sequenced = Sequencer.Sequence(plate.Parts.ToList(), plate);
|
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 piercePoints = new Vector[sequenced.Count];
|
||||||
|
var currentPoint = exitPoint;
|
||||||
|
|
||||||
|
for (var i = 0; i < sequenced.Count; i++)
|
||||||
{
|
{
|
||||||
var part = sp.Part;
|
var part = sequenced[i].Part;
|
||||||
|
|
||||||
if (part.LeadInsLocked)
|
if (part.LeadInsLocked)
|
||||||
{
|
{
|
||||||
|
piercePoints[i] = GetPiercePoint(part);
|
||||||
currentPoint = part.Location;
|
currentPoint = part.Location;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -31,10 +47,33 @@ namespace OpenNest.Engine
|
|||||||
part.RemoveLeadIns();
|
part.RemoveLeadIns();
|
||||||
|
|
||||||
var localApproach = currentPoint - part.Location;
|
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;
|
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)
|
public PlateProcessingResult Process(Plate plate)
|
||||||
{
|
{
|
||||||
var sequenced = Sequencer.Sequence(plate.Parts.ToList(), 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 results = new List<ProcessedPart>(sequenced.Count);
|
||||||
var cutAreas = new List<Shape>();
|
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;
|
var part = sequenced[i].Part;
|
||||||
|
|
||||||
// Compute approach point in part-local space
|
|
||||||
var localApproach = ToPartLocal(currentPoint, part);
|
var localApproach = ToPartLocal(currentPoint, part);
|
||||||
|
|
||||||
Program processedProgram;
|
Program processedProgram;
|
||||||
@@ -33,7 +56,18 @@ namespace OpenNest.Engine
|
|||||||
|
|
||||||
if (!part.HasManualLeadIns && CuttingStrategy != null)
|
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;
|
processedProgram = cuttingResult.Program;
|
||||||
lastCutLocal = cuttingResult.LastCutPoint;
|
lastCutLocal = cuttingResult.LastCutPoint;
|
||||||
}
|
}
|
||||||
@@ -43,11 +77,9 @@ namespace OpenNest.Engine
|
|||||||
lastCutLocal = GetProgramEndPoint(part.Program);
|
lastCutLocal = GetProgramEndPoint(part.Program);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pierce point: program start point in plate space
|
|
||||||
var pierceLocal = GetProgramStartPoint(processedProgram);
|
var pierceLocal = GetProgramStartPoint(processedProgram);
|
||||||
var piercePoint = ToPlateSpace(pierceLocal, part);
|
var piercePoint = ToPlateSpace(pierceLocal, part);
|
||||||
|
|
||||||
// Plan rapid from currentPoint to pierce point
|
|
||||||
var rapidPath = RapidPlanner.Plan(currentPoint, piercePoint, cutAreas);
|
var rapidPath = RapidPlanner.Plan(currentPoint, piercePoint, cutAreas);
|
||||||
|
|
||||||
results.Add(new ProcessedPart
|
results.Add(new ProcessedPart
|
||||||
@@ -57,12 +89,10 @@ namespace OpenNest.Engine
|
|||||||
RapidPath = rapidPath
|
RapidPath = rapidPath
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update cut areas with part perimeter
|
|
||||||
var perimeter = GetPartPerimeter(part);
|
var perimeter = GetPartPerimeter(part);
|
||||||
if (perimeter != null)
|
if (perimeter != null)
|
||||||
cutAreas.Add(perimeter);
|
cutAreas.Add(perimeter);
|
||||||
|
|
||||||
// Update current point to last cut point in plate space
|
|
||||||
currentPoint = ToPlateSpace(lastCutLocal, part);
|
currentPoint = ToPlateSpace(lastCutLocal, part);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -621,30 +621,30 @@ namespace OpenNest.Controls
|
|||||||
|
|
||||||
private void redrawTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
|
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)
|
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);
|
var graphPt = PointControlToGraph(hoverPoint);
|
||||||
LayoutPart hitPart = null;
|
LayoutPart hitPart = null;
|
||||||
try
|
|
||||||
|
for (var i = parts.Count - 1; i >= 0; --i)
|
||||||
{
|
{
|
||||||
for (var i = parts.Count - 1; i >= 0; --i)
|
if (parts[i].Path.GetBounds().Contains(graphPt) &&
|
||||||
|
parts[i].Path.IsVisible(graphPt))
|
||||||
{
|
{
|
||||||
if (parts[i].Path.GetBounds().Contains(graphPt) &&
|
hitPart = parts[i];
|
||||||
parts[i].Path.IsVisible(graphPt))
|
break;
|
||||||
{
|
|
||||||
hitPart = parts[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
// GraphicsPath in use by paint thread — skip this hover tick
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
hoveredPart = hitPart;
|
hoveredPart = hitPart;
|
||||||
showTooltip = hitPart != null;
|
showTooltip = hitPart != null;
|
||||||
|
|||||||
Reference in New Issue
Block a user