mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-14 09:15:06 -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>
97 lines
3.9 KiB
C#
97 lines
3.9 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Converters;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest.Desktop.Rendering;
|
|
|
|
/// <summary>
|
|
/// What the plate view draws for one part, in its program frame before placement, chosen as the
|
|
/// WinForms <c>LayoutPart</c> chooses it: cut, etch and display-only strokes, lead strokes only for
|
|
/// manually placed lead-ins, and the material fill. Immutable once built; rebuild it when
|
|
/// <see cref="IsCurrentFor"/> says the part's program, rotation or lead-ins changed.
|
|
/// </summary>
|
|
public sealed class PartDisplayGeometry
|
|
{
|
|
private PartDisplayGeometry(Program program, double rotation, bool manualLeadIns)
|
|
{
|
|
Program = program;
|
|
Rotation = rotation;
|
|
ManualLeadIns = manualLeadIns;
|
|
}
|
|
|
|
public Program Program { get; }
|
|
|
|
public double Rotation { get; }
|
|
|
|
public bool ManualLeadIns { get; }
|
|
|
|
public IReadOnlyList<PathFigure> Cut { get; private init; }
|
|
|
|
/// <summary>Lead strokes; empty unless the part has manually placed lead-ins.</summary>
|
|
public IReadOnlyList<PathFigure> Lead { get; private init; }
|
|
|
|
public IReadOnlyList<PathFigure> Etch { get; private init; }
|
|
|
|
/// <summary>The filled material region: the cut outline unless break cuts, tabs or common lines make it differ.</summary>
|
|
public IReadOnlyList<PathFigure> Material { get; private init; }
|
|
|
|
/// <summary>The drawing's display-only geometry, rotated as placed. Never material or cut.</summary>
|
|
public IReadOnlyList<PathFigure> Display { get; private init; }
|
|
|
|
public bool HasOpenCuts { get; private init; }
|
|
|
|
/// <summary>
|
|
/// What picks the part: its material, or for an etch-only part its closed marks, so it stays
|
|
/// selectable without treating scribe geometry as a cut or fill.
|
|
/// </summary>
|
|
public IReadOnlyList<PathFigure> Selection => Cut.Count > 0 ? Material : Etch;
|
|
|
|
public bool IsCurrentFor(Part part) =>
|
|
ReferenceEquals(Program, part.Program) && Rotation == part.Rotation
|
|
&& ManualLeadIns == part.HasManualLeadIns;
|
|
|
|
public static PartDisplayGeometry Build(Part part)
|
|
{
|
|
var program = part.Program;
|
|
var layers = ProgramGeometry.Layers(program);
|
|
IReadOnlyList<PathFigure> material;
|
|
// A common-line member cut once leaves its shared spans to the shared cut: its material is
|
|
// the drawing's, rotated as placed (docs/common-line-cutting.md).
|
|
if (CommonLineCuts.IsSinglePass(program))
|
|
{
|
|
var clean = (Program)part.BaseDrawing.Program.Clone();
|
|
clean.Rotate(part.Rotation - part.BaseDrawing.Program.Rotation);
|
|
material = ProgramGeometry.Fill(clean);
|
|
}
|
|
else if (layers.HasOpenCuts || ProgramGeometry.HasBreakCuts(program))
|
|
material = ProgramGeometry.Fill(program);
|
|
else
|
|
material = layers.Cut;
|
|
|
|
return new PartDisplayGeometry(program, part.Rotation, part.HasManualLeadIns)
|
|
{
|
|
Cut = layers.Cut,
|
|
Lead = part.HasManualLeadIns ? layers.Lead : new List<PathFigure>(),
|
|
Etch = layers.Etch,
|
|
Material = material,
|
|
Display = DisplayFigures(part),
|
|
HasOpenCuts = layers.HasOpenCuts,
|
|
};
|
|
}
|
|
|
|
// Display-only entities are kept in the drawing's program frame before rotation, so they
|
|
// follow the part by its cumulative rotation about the program origin, then its location.
|
|
private static List<PathFigure> DisplayFigures(Part part)
|
|
{
|
|
var entities = EntityRoles.DisplayGeometry(part.BaseDrawing);
|
|
foreach (var entity in entities)
|
|
entity.Rotate(part.Rotation);
|
|
return ProgramGeometry.EntityFigures(entities);
|
|
}
|
|
|
|
/// <summary>The bounds of every drawn stroke (cut, etch, leads and display), or null when none.</summary>
|
|
public Box StrokeBounds() => ProgramGeometry.Bounds(Cut.Concat(Etch).Concat(Lead).Concat(Display));
|
|
}
|