feat: add CutOff and CutOffSettings domain classes with segment generation
CutOff computes cut segments along a vertical or horizontal axis, excluding zones around existing parts with configurable clearance. CutOffSettings controls part clearance, overtravel, minimum segment length, and cut direction (toward/away from origin). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
168
OpenNest.Core/CutOff.cs
Normal file
168
OpenNest.Core/CutOff.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum CutOffAxis
|
||||
{
|
||||
Horizontal,
|
||||
Vertical
|
||||
}
|
||||
|
||||
public class CutOff
|
||||
{
|
||||
public Vector Position { get; set; }
|
||||
public CutOffAxis Axis { get; set; }
|
||||
public double? StartLimit { get; set; }
|
||||
public double? EndLimit { get; set; }
|
||||
public Drawing Drawing { get; private set; }
|
||||
|
||||
public CutOff(Vector position, CutOffAxis axis)
|
||||
{
|
||||
Position = position;
|
||||
Axis = axis;
|
||||
Drawing = new Drawing(GetName()) { IsCutOff = true };
|
||||
}
|
||||
|
||||
public void Regenerate(Plate plate, CutOffSettings settings)
|
||||
{
|
||||
var segments = ComputeSegments(plate, settings);
|
||||
var program = BuildProgram(segments, settings);
|
||||
Drawing.Program = program;
|
||||
}
|
||||
|
||||
private string GetName()
|
||||
{
|
||||
var axisChar = Axis == CutOffAxis.Vertical ? "V" : "H";
|
||||
var coord = Axis == CutOffAxis.Vertical ? Position.X : Position.Y;
|
||||
return $"CutOff-{axisChar}-{coord:F2}";
|
||||
}
|
||||
|
||||
private List<(double Start, double End)> ComputeSegments(Plate plate, CutOffSettings settings)
|
||||
{
|
||||
var bounds = plate.BoundingBox(includeParts: false);
|
||||
|
||||
double lineStart, lineEnd, cutPosition;
|
||||
|
||||
if (Axis == CutOffAxis.Vertical)
|
||||
{
|
||||
cutPosition = Position.X;
|
||||
lineStart = StartLimit ?? bounds.Y;
|
||||
lineEnd = EndLimit ?? (bounds.Y + bounds.Length + settings.Overtravel);
|
||||
}
|
||||
else
|
||||
{
|
||||
cutPosition = Position.Y;
|
||||
lineStart = StartLimit ?? bounds.X;
|
||||
lineEnd = EndLimit ?? (bounds.X + bounds.Width + settings.Overtravel);
|
||||
}
|
||||
|
||||
var exclusions = new List<(double Start, double End)>();
|
||||
|
||||
foreach (var part in plate.Parts)
|
||||
{
|
||||
if (part.BaseDrawing.IsCutOff)
|
||||
continue;
|
||||
|
||||
var bb = part.BoundingBox;
|
||||
double partStart, partEnd, partMin, partMax;
|
||||
|
||||
if (Axis == CutOffAxis.Vertical)
|
||||
{
|
||||
partMin = bb.X - settings.PartClearance;
|
||||
partMax = bb.X + bb.Width + settings.PartClearance;
|
||||
partStart = bb.Y - settings.PartClearance;
|
||||
partEnd = bb.Y + bb.Length + settings.PartClearance;
|
||||
}
|
||||
else
|
||||
{
|
||||
partMin = bb.Y - settings.PartClearance;
|
||||
partMax = bb.Y + bb.Length + settings.PartClearance;
|
||||
partStart = bb.X - settings.PartClearance;
|
||||
partEnd = bb.X + bb.Width + settings.PartClearance;
|
||||
}
|
||||
|
||||
if (cutPosition >= partMin && cutPosition <= partMax)
|
||||
exclusions.Add((partStart, partEnd));
|
||||
}
|
||||
|
||||
exclusions.Sort((a, b) => a.Start.CompareTo(b.Start));
|
||||
var merged = new List<(double Start, double End)>();
|
||||
foreach (var ex in exclusions)
|
||||
{
|
||||
if (merged.Count > 0 && ex.Start <= merged[^1].End)
|
||||
merged[^1] = (merged[^1].Start, System.Math.Max(merged[^1].End, ex.End));
|
||||
else
|
||||
merged.Add(ex);
|
||||
}
|
||||
|
||||
var segments = new List<(double Start, double End)>();
|
||||
var current = lineStart;
|
||||
|
||||
foreach (var ex in merged)
|
||||
{
|
||||
var clampedStart = System.Math.Max(ex.Start, lineStart);
|
||||
var clampedEnd = System.Math.Min(ex.End, lineEnd);
|
||||
|
||||
if (clampedStart > current)
|
||||
segments.Add((current, clampedStart));
|
||||
|
||||
current = System.Math.Max(current, clampedEnd);
|
||||
}
|
||||
|
||||
if (current < lineEnd)
|
||||
segments.Add((current, lineEnd));
|
||||
|
||||
segments = segments.Where(s => (s.End - s.Start) >= settings.MinSegmentLength).ToList();
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
private Program BuildProgram(List<(double Start, double End)> segments, CutOffSettings settings)
|
||||
{
|
||||
var program = new Program();
|
||||
|
||||
if (segments.Count == 0)
|
||||
return program;
|
||||
|
||||
if (settings.CutDirection == CutDirection.TowardOrigin)
|
||||
segments = segments.OrderByDescending(s => s.Start).ToList();
|
||||
else
|
||||
segments = segments.OrderBy(s => s.Start).ToList();
|
||||
|
||||
foreach (var seg in segments)
|
||||
{
|
||||
double startVal, endVal;
|
||||
if (settings.CutDirection == CutDirection.TowardOrigin)
|
||||
{
|
||||
startVal = seg.End;
|
||||
endVal = seg.Start;
|
||||
}
|
||||
else
|
||||
{
|
||||
startVal = seg.Start;
|
||||
endVal = seg.End;
|
||||
}
|
||||
|
||||
Vector startPt, endPt;
|
||||
if (Axis == CutOffAxis.Vertical)
|
||||
{
|
||||
startPt = new Vector(Position.X, startVal);
|
||||
endPt = new Vector(Position.X, endVal);
|
||||
}
|
||||
else
|
||||
{
|
||||
startPt = new Vector(startVal, Position.Y);
|
||||
endPt = new Vector(endVal, Position.Y);
|
||||
}
|
||||
|
||||
program.Codes.Add(new RapidMove(startPt));
|
||||
program.Codes.Add(new LinearMove(endPt));
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user