using System.Collections.Generic;
using System.Linq;
using OpenNest.CNC;
using OpenNest.Converters;
using OpenNest.Geometry;
namespace OpenNest.Desktop.Rendering;
///
/// What the plate view draws for one part, in its program frame before placement, chosen as the
/// WinForms LayoutPart 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
/// says the part's program, rotation or lead-ins changed.
///
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 Cut { get; private init; }
/// Lead strokes; empty unless the part has manually placed lead-ins.
public IReadOnlyList Lead { get; private init; }
public IReadOnlyList Etch { get; private init; }
/// The filled material region: the cut outline unless break cuts, tabs or common lines make it differ.
public IReadOnlyList Material { get; private init; }
/// The drawing's display-only geometry, rotated as placed. Never material or cut.
public IReadOnlyList Display { get; private init; }
public bool HasOpenCuts { get; private init; }
///
/// 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.
///
public IReadOnlyList 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 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(),
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 DisplayFigures(Part part)
{
var entities = EntityRoles.DisplayGeometry(part.BaseDrawing);
foreach (var entity in entities)
entity.Rotate(part.Rotation);
return ProgramGeometry.EntityFigures(entities);
}
/// The bounds of every drawn stroke (cut, etch, leads and display), or null when none.
public Box StrokeBounds() => ProgramGeometry.Bounds(Cut.Concat(Etch).Concat(Lead).Concat(Display));
}