using System.Collections.Generic;
using System.Linq;
using OpenNest.CNC;
using OpenNest.Converters;
using OpenNest.Geometry;
namespace OpenNest.Desktop.Rendering;
///
/// Where the plate view writes a part's number: the pole of inaccessibility of the drawing's
/// material, or the middle of a cut-off segment's own cut. A port of the WinForms
/// LayoutPart label rules.
///
public static class PartLabels
{
/// The search budget that keeps any shape from stalling a paint.
public const int SearchCells = 20000;
/// The drawing's label point in its program frame.
public static Vector LabelPoint(Drawing drawing, int maxCells = SearchCells)
{
var entities = ConvertProgram.ToGeometry(drawing.Program);
var material = entities.Where(e => SpecialLayers.IsMaterial(e.Layer)).ToList();
var shapes = ShapeBuilder.GetShapes(material);
if (shapes.Count == 0)
{
var bbox = drawing.Program.BoundingBox();
return new Vector(bbox.Location.X + bbox.Length / 2, bbox.Location.Y + bbox.Width / 2);
}
var profile = ShapeProfile.FromShapes(shapes);
var outer = profile.Perimeter.ToPolygonWithTolerance(0.1);
List holes = null;
if (profile.Cutouts.Count > 0)
{
holes = new List();
foreach (var cutout in profile.Cutouts)
holes.Add(cutout.ToPolygonWithTolerance(0.1));
}
return PolyLabel.Find(outer, holes, PolyLabel.ThicknessPrecision(outer, holes), maxCells);
}
///
/// A drawing label point (from ) placed with . The
/// point already carries the drawing program's own rotation, so only the part's rotation past
/// it is applied.
///
public static Vector Place(Part part, Vector drawingLabelPoint) =>
drawingLabelPoint.Rotate(part.Rotation - part.BaseDrawing.Program.Rotation) + part.Location;
///
/// The middle of a cut-off segment part's own cutting moves, in plate coordinates; null when
/// the part is not an absolute cut-off segment. Every segment shares the cut-off drawing, so
/// labelling from the drawing would stack all the numbers on one point.
///
public static Vector? CutOffSegmentPoint(Part part)
{
var program = part.Program;
if (!part.BaseDrawing.IsCutOff || program?.Mode != Mode.Absolute)
return null;
double minX = double.MaxValue, minY = double.MaxValue;
double maxX = double.MinValue, maxY = double.MinValue;
Vector? position = null;
foreach (var code in program.Codes)
{
if (code is not Motion motion)
continue;
if (motion is not RapidMove && position is { } start)
{
foreach (var point in new[] { start, motion.EndPoint })
{
minX = System.Math.Min(minX, point.X);
minY = System.Math.Min(minY, point.Y);
maxX = System.Math.Max(maxX, point.X);
maxY = System.Math.Max(maxY, point.Y);
}
}
position = motion.EndPoint;
}
if (minX > maxX)
return null;
return new Vector((minX + maxX) / 2, (minY + maxY) / 2) + part.Location;
}
}