using System.Collections.Generic;
using OpenNest.CNC;
using OpenNest.Geometry;
namespace OpenNest
{
///
/// One placed cutting segment of a cut-off: the part that cuts it, where its cut runs along
/// the cut-off's axis, and the span of the nominal line it stands for, from the end of the
/// segment before it (or the line's start) to the start of the one after it (or the line's
/// end). The parts the nominal line passes through lie in the gaps between segments, so each
/// is inside the span of the segment on either side of it; a segment is cut before exactly
/// those parts.
///
public sealed record CutOffSegment(Part Part, double Start, double End, double SpanStart, double SpanEnd);
///
/// A cut-off's trimmed segments are materialized as one part each, so every segment has its
/// own place in the cut sequence. This finds those parts and the span each stands for.
///
public static class CutOffSegments
{
/// The definition's placed segment parts in ascending order along its axis.
public static IReadOnlyList Of(Plate plate, CutOff definition) =>
Of(definition, plate.Parts, plate.BoundingBox(includeParts: false));
///
/// The definition's segment parts among (those placed from its
/// drawing) in ascending order along its axis, with spans measured against the nominal
/// line's limits on . A part whose cut cannot be measured stands
/// for the whole line.
///
public static IReadOnlyList Of(CutOff definition, IEnumerable parts, Box sheet)
{
var vertical = definition.Axis == CutOffAxis.Vertical;
var lineStart = definition.StartLimit ?? (vertical ? sheet.Bottom : sheet.Left);
var lineEnd = definition.EndLimit ?? (vertical ? sheet.Top : sheet.Right);
var low = System.Math.Min(lineStart, lineEnd);
var high = System.Math.Max(lineStart, lineEnd);
var measured = new List<(Part Part, double Start, double End)>();
var whole = new List();
foreach (var part in parts)
{
if (!ReferenceEquals(part.BaseDrawing, definition.Drawing))
continue;
if (Extent(part, vertical) is { } extent)
measured.Add((part, extent.Start, extent.End));
else
whole.Add(new CutOffSegment(part, low, high, low, high));
}
measured.Sort((a, b) => a.Start != b.Start ? a.Start.CompareTo(b.Start) : a.End.CompareTo(b.End));
var result = new List(measured.Count + whole.Count);
for (var i = 0; i < measured.Count; i++)
{
var (part, start, end) = measured[i];
var spanStart = i == 0 ? System.Math.Min(low, start) : measured[i - 1].End;
var spanEnd = i == measured.Count - 1 ? System.Math.Max(high, end) : measured[i + 1].Start;
result.Add(new CutOffSegment(part, start, end, spanStart, spanEnd));
}
result.AddRange(whole);
return result;
}
///
/// The extent along the cut-off's axis of the part's cutting moves, or null when the
/// part cuts nothing or its program is not the absolute rapid-and-cut form cut-offs use.
///
public static (double Start, double End)? Extent(Part part, bool vertical)
{
if (part?.Program == null || part.Program.Mode != Mode.Absolute)
return null;
double? min = null, max = null;
Vector? position = null;
foreach (var code in part.Program.Codes)
{
switch (code)
{
case RapidMove rapid:
position = rapid.EndPoint + part.Location;
break;
case Motion motion when position != null:
var end = motion.EndPoint + part.Location;
foreach (var point in new[] { position.Value, end })
{
var along = vertical ? point.Y : point.X;
min = min == null ? along : System.Math.Min(min.Value, along);
max = max == null ? along : System.Math.Max(max.Value, along);
}
position = end;
break;
case Motion:
return null;
}
}
return min == null ? null : (min.Value, max.Value);
}
}
}