mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 16:11:07 -04:00
A library drawing can say how individual contours must be cut. A `ContourCutPin` holds the entry point in the drawing's source frame plus the lead-in, lead-out and optional tab used there; `Drawing.CutPins` lists them. Nothing plans with pins yet (Slice 5). - Pins accept only the exact built-in lead and tab types, copied through the same owned copiers the planner uses, so a pin cannot carry a type the emitter would not certify. Properties return copies: the pin is immutable. - A tab requires no lead-out, because the planner refuses a lead-out after an open contour. - The source frame is the frame of the source entities. It does not move when BuildDrawing re-origins the program at its first rapid, so pins survive converter edits that change Source.Offset. `CutPinResolver` maps pins to (contour ordinal, entity ordinal, local point) for a placement rotation, using the same `PreparedContours` ordinals Plan Cutting will use. It never guesses: a pin must lie within the contour closure tolerance of exactly one contour that no other pin claims, otherwise it is reported unresolved with a typed reason. A drawing program rotated in place (a canonical copy) is not the source frame and resolves nothing; canonical copies and split pieces carry no pins. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
214 lines
6.7 KiB
C#
214 lines
6.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using OpenNest.Bending;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Converters;
|
|
using OpenNest.Geometry;
|
|
|
|
namespace OpenNest
|
|
{
|
|
public class Drawing
|
|
{
|
|
private static int nextId;
|
|
private static int nextColorIndex;
|
|
private Program program;
|
|
|
|
public static Color[] PartColors = new Color[]
|
|
{
|
|
Color.FromArgb(205, 92, 92), // Indian Red
|
|
Color.FromArgb(148, 103, 189), // Medium Purple
|
|
Color.FromArgb(75, 180, 175), // Teal
|
|
Color.FromArgb(210, 190, 75), // Goldenrod
|
|
Color.FromArgb(190, 85, 175), // Orchid
|
|
Color.FromArgb(185, 115, 85), // Sienna
|
|
Color.FromArgb(120, 100, 190), // Slate Blue
|
|
Color.FromArgb(200, 100, 140), // Rose
|
|
Color.FromArgb(80, 175, 155), // Sea Green
|
|
Color.FromArgb(195, 160, 85), // Dark Khaki
|
|
Color.FromArgb(175, 95, 160), // Plum
|
|
Color.FromArgb(215, 130, 130), // Light Coral
|
|
};
|
|
|
|
public static bool UseGoldenAngleColors { get; set; }
|
|
|
|
public static Color GetPartColor(int index)
|
|
{
|
|
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
|
return UseGoldenAngleColors
|
|
? PartColorPalette.GoldenAngle(index)
|
|
: PartColors[index % PartColors.Length];
|
|
}
|
|
|
|
public static Color GetNextColor() => GetPartColor(unchecked(nextColorIndex++) & int.MaxValue);
|
|
|
|
public Drawing()
|
|
: this(string.Empty, new Program()) { }
|
|
|
|
public Drawing(string name)
|
|
: this(name, new Program()) { }
|
|
|
|
public Drawing(string name, Program pgm)
|
|
{
|
|
Id = Interlocked.Increment(ref nextId);
|
|
Name = name;
|
|
Material = new Material();
|
|
Constraints = new NestConstraints();
|
|
Source = new SourceInfo();
|
|
Program = pgm;
|
|
}
|
|
|
|
public int Id { get; }
|
|
|
|
public string Name { get; set; }
|
|
|
|
public string Customer { get; set; }
|
|
|
|
public int Priority { get; set; }
|
|
|
|
public DwgQty Quantity;
|
|
|
|
public Material Material { get; set; }
|
|
|
|
public Program Program
|
|
{
|
|
get { return program; }
|
|
set
|
|
{
|
|
program = value;
|
|
UpdateArea();
|
|
RecomputeCanonicalAngle();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Recomputes and stores the canonical angle from the current Program.
|
|
/// Callers that mutate Program in place (rather than reassigning it) must invoke this explicitly.
|
|
/// Cut-off drawings are left with Angle=0.
|
|
/// </summary>
|
|
public void RecomputeCanonicalAngle()
|
|
{
|
|
if (Source == null)
|
|
Source = new SourceInfo();
|
|
|
|
if (program == null || IsCutOff)
|
|
{
|
|
Source.Angle = 0.0;
|
|
return;
|
|
}
|
|
|
|
Source.Angle = CanonicalAngle.Compute(this);
|
|
}
|
|
|
|
public Color Color { get; set; }
|
|
|
|
public bool IsCutOff { get; set; }
|
|
|
|
public NestConstraints Constraints { get; set; }
|
|
|
|
public SourceInfo Source { get; set; }
|
|
|
|
public List<Bend> Bends { get; set; } = new List<Bend>();
|
|
|
|
/// <summary>
|
|
/// The library revision this drawing was copied from, or null when it did not come
|
|
/// from a drawing library. Editing the drawing in a nest keeps the link as provenance.
|
|
/// </summary>
|
|
public DrawingLibraryLink Library { get; set; }
|
|
|
|
/// <summary>
|
|
/// Contours this drawing says must be cut a fixed way, in the source frame. At most one
|
|
/// pin per contour is used; <see cref="CNC.CuttingPlanning.CutPinResolver"/> maps them to
|
|
/// a placement. Pins do not follow geometry into new drawings (split pieces).
|
|
/// </summary>
|
|
public List<CNC.CuttingStrategy.ContourCutPin> CutPins { get; set; } = new List<CNC.CuttingStrategy.ContourCutPin>();
|
|
|
|
/// <summary>
|
|
/// Complete set of source entities with stable GUIDs.
|
|
/// Null when the drawing was created from G-code or an older nest file.
|
|
/// </summary>
|
|
public List<Entity> SourceEntities { get; set; }
|
|
|
|
/// <summary>
|
|
/// IDs of entities in <see cref="SourceEntities"/> that are suppressed (hidden).
|
|
/// Suppressed entities are excluded from the active Program but preserved for re-enabling.
|
|
/// </summary>
|
|
public HashSet<Guid> SuppressedEntityIds { get; set; } = new HashSet<Guid>();
|
|
|
|
public double Area { get; protected set; }
|
|
|
|
public void UpdateArea()
|
|
{
|
|
var geometry = ConvertProgram
|
|
.ToGeometry(Program)
|
|
.Where(entity => SpecialLayers.IsMaterial(entity.Layer));
|
|
var shapes = ShapeBuilder.GetShapes(geometry);
|
|
|
|
if (shapes.Count == 0)
|
|
return;
|
|
|
|
var areas = new double[shapes.Count];
|
|
|
|
for (int i = 0; i < shapes.Count; i++)
|
|
{
|
|
var shape = shapes[i];
|
|
areas[i] = shape.Area();
|
|
}
|
|
|
|
int largestAreaIndex = 0;
|
|
|
|
for (int i = 1; i < areas.Length; i++)
|
|
{
|
|
var area = areas[i];
|
|
|
|
if (area > areas[largestAreaIndex])
|
|
largestAreaIndex = i;
|
|
}
|
|
|
|
var outerArea = areas[largestAreaIndex];
|
|
|
|
Area = outerArea - (areas.Sum() - outerArea);
|
|
}
|
|
|
|
public override bool Equals(object obj)
|
|
{
|
|
if (obj is Drawing == false)
|
|
return false;
|
|
|
|
var dwg = (Drawing)obj;
|
|
|
|
return Name == dwg.Name;
|
|
}
|
|
|
|
public override int GetHashCode()
|
|
{
|
|
return Name.GetHashCode();
|
|
}
|
|
}
|
|
|
|
public class SourceInfo
|
|
{
|
|
/// <summary>
|
|
/// Path to the source file.
|
|
/// </summary>
|
|
public string Path { get; set; }
|
|
|
|
/// <summary>
|
|
/// Offset distances to the original location.
|
|
/// </summary>
|
|
public Vector Offset { get; set; }
|
|
|
|
/// <summary>
|
|
/// Rotation (radians) that maps the source program geometry to its canonical
|
|
/// (MBR-axis-aligned) frame. Populated automatically by the <see cref="Drawing.Program"/>
|
|
/// setter via <see cref="CanonicalAngle.Compute"/>. A value of 0 means the drawing is
|
|
/// already canonical or <see cref="Drawing.IsCutOff"/> is true. Callers that mutate
|
|
/// <see cref="Drawing.Program"/> in place must invoke
|
|
/// <see cref="Drawing.RecomputeCanonicalAngle"/> to refresh.
|
|
/// </summary>
|
|
public double Angle { get; set; }
|
|
}
|
|
}
|