mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-13 23:55:08 -05:00
Port the WinForms GraphicsHelper program walker to a toolkit-neutral figure list (move/line/arc) with its layer routing: cuts without tabs, leads, etches, and a material fill without break cuts whose tab-gapped pieces are bridged. Suppressed moves are skipped and marks never bound the fill. PartDisplayGeometry picks the fill as LayoutPart does (common-line members, break cuts, open cuts) and keeps display-only entities; PartLabels ports the label points. ViewTransform carries DrawControl's world/screen mapping and zoom rules. PlateOverlays computes what the view draws around parts (sheet, edge spacing, irregular usable area, clamps, cut-off strokes, rapids, pierce points, cut direction, offset outline, support) through the existing Core code. PlateViewOptions reads and writes the View toggles under the WinForms PlateViewDrawRapid/PlateViewDrawBounds names. WinForms still draws with GraphicsHelper; nothing there changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
90 lines
3.4 KiB
C#
90 lines
3.4 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Converters;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Desktop.Rendering;
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// <c>LayoutPart</c> label rules.
|
|
/// </summary>
|
|
public static class PartLabels
|
|
{
|
|
/// <summary>The search budget that keeps any shape from stalling a paint.</summary>
|
|
public const int SearchCells = 20000;
|
|
|
|
/// <summary>The drawing's label point in its program frame.</summary>
|
|
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<Polygon> holes = null;
|
|
if (profile.Cutouts.Count > 0)
|
|
{
|
|
holes = new List<Polygon>();
|
|
foreach (var cutout in profile.Cutouts)
|
|
holes.Add(cutout.ToPolygonWithTolerance(0.1));
|
|
}
|
|
|
|
return PolyLabel.Find(outer, holes, PolyLabel.ThicknessPrecision(outer, holes), maxCells);
|
|
}
|
|
|
|
/// <summary>
|
|
/// A drawing label point (from <see cref="LabelPoint"/>) placed with <paramref name="part"/>. The
|
|
/// point already carries the drawing program's own rotation, so only the part's rotation past
|
|
/// it is applied.
|
|
/// </summary>
|
|
public static Vector Place(Part part, Vector drawingLabelPoint) =>
|
|
drawingLabelPoint.Rotate(part.Rotation - part.BaseDrawing.Program.Rotation) + part.Location;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|