using System; using System.Collections.Generic; using OpenNest.CNC; using OpenNest.Geometry; namespace OpenNest.Posts.CincinnatiCIFiber { /// /// One contour (pierce + lead-in + cut path) reduced to sheet-absolute /// moves, ready to emit as a TF5200 N-label block. /// public sealed class CIFiberContour { /// Pierce point (sheet-absolute) — rapid target. public Vector Pierce { get; set; } /// /// Lead-in move to the contour start (sheet-absolute endpoint), or /// null when the contour begins cutting immediately at the pierce. /// Per TF5200 §13.2.4.1 the first motion block after G41/G42 selection /// must be LINEAR, so callers must reject contours whose lead-in is an /// arc unless the config tolerates it. /// public Motion LeadIn { get; set; } /// /// Additional lead-in layer moves after the first (e.g. the arc that /// follows CleanHoleLeadIn's line). Emitted before the cut layer turns /// on, like the sample's lead block. /// public List LeadInExtra { get; } = new(); /// Cutting moves (sheet-absolute) executed after the cut layer turns on. public List Cuts { get; } = new(); /// True when the contour is an external perimeter (exterior). public bool IsExterior { get; set; } } /// /// Extracts cut contours for one part in sheet coordinates. /// Circular holes arrive as s in an incremental /// hole-local program; they are flattened here to sheet-absolute moves the /// same way walks them for HPGL output. /// Moves with are skipped. /// public static class CIFiberContourExtractor { public static List Extract(Part part) { if (part == null) throw new ArgumentNullException(nameof(part)); var contours = new List(); Flatten(part.Program, part.Location, Vector.Zero, contours); return contours; } /// /// Walk one program whose coordinates are relative to (partLocation + /// callOffset). Sub-program calls recurse with the extra offset. /// A contour starts at each rapid move; moves before the first rapid /// form a contour whose pierce is the last move's endpoint (closed-loop /// continuation), matching FeatureUtils' synthetic-rapid convention. /// private static void Flatten( Program program, Vector partLocation, Vector subOffset, List contours ) { if (program == null) return; var origin = partLocation + subOffset; // Work on a clone when conversion is needed so caller programs are // never mutated (the CI sample inlines absolute moves). var codes = program.Codes; if (program.Mode == Mode.Incremental) { var clone = (Program)program.Clone(); clone.Mode = Mode.Absolute; codes = clone.Codes; } CIFiberContour current = null; void Flush() { if (current != null && (current.LeadIn != null || current.Cuts.Count > 0)) contours.Add(current); current = null; } foreach (var code in codes) { if (code is Motion suppressed && suppressed.Suppressed) continue; switch (code) { case RapidMove rapid: Flush(); current = new CIFiberContour { Pierce = rapid.EndPoint + origin }; break; case SubProgramCall call: // A hole call is its own contour: flatten into a temp // list, then merge as one contour anchored at the call // offset. Flush(); var sub = new List(); Flatten(call.Program, partLocation, subOffset + call.Offset, sub); contours.AddRange(sub); break; case Motion motion: if (current == null) { // Contour with no explicit rapid (program starts // mid-contour): in absolute mode the implicit // start position is the frame origin, which is the // closure point of a closed contour = the pierce. current = new CIFiberContour { Pierce = origin }; } var absolute = (Motion)motion.Clone(); absolute.Offset(origin); var layer = LayerOf(motion); if (layer == LayerType.Leadin) { // First lead-in move opens the lead block; further // lead-in moves (CleanHole line+arc) extend it. if (current.LeadIn == null) current.LeadIn = absolute; else current.LeadInExtra.Add(absolute); } else { // Cut, Leadout and Display all cut: OpenNest tags // contour moves Display by default (see // ContourCuttingStrategy.ConvertShapeToMoves), // only Scribe is reliably a mark. Scribe contours // are dropped by the writer when SkipScribe is on. current.Cuts.Add(absolute); } break; } } Flush(); } private static LayerType LayerOf(Motion motion) { return motion switch { LinearMove l => l.Layer, ArcMove a => a.Layer, _ => LayerType.Cut, }; } } }