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; /// The active palette's color for a drawing at . public static Color GetPartColor(int index) => PartColorPalette.Current.ColorAt(index); public static Color GetNextColor() => GetPartColor(unchecked(Interlocked.Increment(ref nextColorIndex) - 1) & 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; } /// Nesting order: higher numbers nest first. Zero is the default. public int Priority { get; set; } public DwgQty Quantity; public Material Material { get; set; } public Program Program { get { return program; } set { program = value; ProgramChanged(); } } /// /// Refreshes everything derived from : and the /// canonical angle in . Assigning /// calls it; callers that mutate the program in place (rather than reassigning it) must /// call it afterwards. /// public void ProgramChanged() { UpdateArea(); RecomputeCanonicalAngle(); } // Cut-off drawings are left with Angle=0. private 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; } /// /// True when someone chose on purpose. Otherwise the color comes /// from the active scheme's palette and is reassigned when the scheme changes or the /// nest is opened. /// public bool HasCustomColor { get; set; } public bool IsCutOff { get; set; } /// /// Cut this drawing's parts before every other part when automatic sequencing runs, /// e.g. a test coupon checked before the rest of the sheet is cut. /// public bool CutFirst { get; set; } /// /// Lets automatic nesting place this drawing's parts one kerf apart so they share a cut /// (see docs/common-line-cutting.md), when the nest's common-line usage allows it. Off by /// default: a shared edge is cut once from the centerline, which some customers' /// edge-quality requirements do not accept. /// public bool AllowCommonLine { get; set; } public NestConstraints Constraints { get; set; } public SourceInfo Source { get; set; } public List Bends { get; set; } = new List(); /// /// 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. /// public DrawingLibraryLink Library { get; set; } /// /// Contours this drawing says must be cut a fixed way, in the source frame. At most one /// pin per contour is used; maps them to /// a placement. Pins do not follow geometry into new drawings (split pieces). /// public List CutPins { get; set; } = new List(); /// /// Scrap pieces tabbed to the part, in the source frame. already has /// them applied; they are kept so an editor can change them from . /// public List Breakoffs { get; set; } = new List(); /// /// Holes whose slugs stay attached by tabs, in the source frame. already /// has them applied (cut pieces plus spans); they are kept so /// an editor can change them from . /// public List TabbedCutouts { get; set; } = new List(); /// /// Annotations shown in the CAD Converter. They store placement only, are measured from the /// geometry when shown and never affect . /// public List Dimensions { get; set; } = new List(); /// /// Complete set of source entities with stable GUIDs. /// Null when the drawing was created from G-code or an older nest file. /// public List SourceEntities { get; set; } /// /// IDs of entities in that are suppressed (hidden). /// Suppressed entities are excluded from the active Program but preserved for re-enabling. /// public HashSet SuppressedEntityIds { get; set; } = new HashSet(); /// /// Role overrides for entities in ; entities not listed keep the /// role their layer implies (see ). already has /// them applied; display-only entities are not in it and come from /// . /// public Dictionary EntityRoles { get; set; } = new Dictionary(); public double Area { get; protected set; } private void UpdateArea() { if (program == null) { Area = 0; return; } var geometry = ConvertProgram .ToGeometry(program) .Where(entity => SpecialLayers.IsMaterial(entity.Layer)); var shapes = ShapeBuilder.GetShapes(geometry); // No material left (e.g. only marks): a stale area would still count as material. if (shapes.Count == 0) { Area = 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 { /// /// Path to the source file. /// public string Path { get; set; } /// /// Offset distances to the original location. /// public Vector Offset { get; set; } /// /// Rotation (radians) that maps the source program geometry to its canonical /// (MBR-axis-aligned) frame. Populated automatically by the /// setter via . A value of 0 means the drawing is /// already canonical or is true. Callers that mutate /// in place must invoke /// to refresh. /// public double Angle { get; set; } } }