using System; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.IO.Ports; using System.Runtime.InteropServices; using System.Threading; using Microsoft.Win32.SafeHandles; if (args.Length < 2) { Console.Error.WriteLine("Usage:"); Console.Error.WriteLine(" StreamGravographJob [chunk=256] [flow=rtscts|xonxoff|none]"); Console.Error.WriteLine(" StreamGravographJob --gen # name: testA | testB | miniB | miniSquare"); Console.Error.WriteLine(" StreamGravographJob --inspect-nest "); Console.Error.WriteLine(" StreamGravographJob --from-nest "); return 2; } // Inspect a .nest file: extract polylines via the post-processor pipeline and // report dimensions / bounding box / pen-up travel — no bytes written. if (args[0] == "--inspect-nest") { if (args.Length < 2) { Console.Error.WriteLine("--inspect-nest requires "); return 2; } var nestPath = args[1]; if (!File.Exists(nestPath)) { Console.Error.WriteLine($"Not found: {nestPath}"); return 3; } using var fs = new FileStream(nestPath, FileMode.Open, FileAccess.Read); var reader = new OpenNest.IO.NestReader(fs); var nest = reader.Read(); Console.WriteLine($"Nest: {nest.Name}"); Console.WriteLine($"Units: {nest.Units}"); Console.WriteLine($"Plates: {nest.Plates.Count}"); var plateIdx = 0; foreach (var plate in nest.Plates) { plateIdx++; Console.WriteLine($" Plate {plateIdx}: size={plate.Size.Length} x {plate.Size.Width}, quadrant={plate.Quadrant}, parts={plate.Parts.Count}"); } var polylines = new OpenNest.Posts.GravographIS.NestPolylineExtractor().Extract(nest); if (polylines.Count == 0) { Console.WriteLine("No polylines extracted."); return 0; } double minX = double.PositiveInfinity, minY = double.PositiveInfinity; double maxX = double.NegativeInfinity, maxY = double.NegativeInfinity; int totalPts = 0; foreach (var p in polylines) { foreach (var v in p) { if (v.X < minX) minX = v.X; if (v.X > maxX) maxX = v.X; if (v.Y < minY) minY = v.Y; if (v.Y > maxY) maxY = v.Y; } totalPts += p.Count; } Console.WriteLine($"Polylines: {polylines.Count}, total points: {totalPts}"); Console.WriteLine($"Bounding box (inches): X ∈ [{minX:F3}, {maxX:F3}] Y ∈ [{minY:F3}, {maxY:F3}]"); Console.WriteLine($"Extents: {maxX - minX:F3}\" × {maxY - minY:F3}\""); // After running the pre-pass (stitch + reorder from origin) — what the writer will actually consume. var prepared = OpenNest.Posts.GravographIS.PolylinePrePass.Prepare(polylines); Console.WriteLine($"After stitch+reorder: {prepared.Count} polylines"); Console.WriteLine(); Console.WriteLine("--- Vertex dump (prepared, upper-left origin, with segment deltas) ---"); var pi = 0; foreach (var poly in prepared) { pi++; Console.WriteLine($"Polyline {pi}: {poly.Count} points"); var cumX = 0.0; var cumY = 0.0; for (var i = 0; i < poly.Count; i++) { var v = poly[i]; if (i == 0) { Console.WriteLine($" [{i}] ({v.X,7:F3}, {v.Y,7:F3}) first DR travel from upper-left origin=({v.X,+7:F3}, {v.Y,+7:F3})"); } else { var dx = v.X - poly[i - 1].X; var dy = v.Y - poly[i - 1].Y; cumX += dx; cumY += dy; Console.WriteLine($" [{i}] ({v.X,7:F3}, {v.Y,7:F3}) Δ=({dx,+7:F3}, {dy,+7:F3}) cum from origin=({cumX,+7:F3}, {cumY,+7:F3})"); } } } return 0; } // Convert a .nest file to a .prn job via the full post-processor pipeline. if (args[0] == "--from-nest") { if (args.Length < 3) { Console.Error.WriteLine("--from-nest requires "); return 2; } var nestPath = args[1]; var outFile = args[2]; if (!File.Exists(nestPath)) { Console.Error.WriteLine($"Not found: {nestPath}"); return 3; } using var fs = new FileStream(nestPath, FileMode.Open, FileAccess.Read); var nest = new OpenNest.IO.NestReader(fs).Read(); var post = new OpenNest.Posts.GravographIS.GravographISPostProcessor(); post.Post(nest, outFile); var size = new FileInfo(outFile).Length; Console.WriteLine($"Wrote {size} bytes → {outFile}"); return 0; } // Generator mode: run the live writer to produce a captured-test file on disk. if (args[0] == "--gen") { if (args.Length < 3) { Console.Error.WriteLine("--gen requires "); return 2; } var preset = args[1]; var outFile = args[2]; var polylines = preset.ToLowerInvariant() switch { "testa" => new System.Collections.Generic.List> { new[] { new OpenNest.Geometry.Vector(1, 1), new OpenNest.Geometry.Vector(1, 3) }, }, "testb" => new System.Collections.Generic.List> { new[] { new OpenNest.Geometry.Vector(1, 1), new OpenNest.Geometry.Vector(1, 3) }, new[] { new OpenNest.Geometry.Vector(4, 1), new OpenNest.Geometry.Vector(4, 3) }, new[] { new OpenNest.Geometry.Vector(4, 5), new OpenNest.Geometry.Vector(4, 7) }, new[] { new OpenNest.Geometry.Vector(1, 5), new OpenNest.Geometry.Vector(1, 7) }, }, // Same 4-polyline topology as testB (vertical lines + diagonal PU travels between them), // shrunk to a 0.5" × 1.5" footprint so it stays right near the operator-set work origin. "minib" => new System.Collections.Generic.List> { new[] { new OpenNest.Geometry.Vector(0, 0), new OpenNest.Geometry.Vector(0, 0.5) }, new[] { new OpenNest.Geometry.Vector(0.5, 0), new OpenNest.Geometry.Vector(0.5, 0.5) }, new[] { new OpenNest.Geometry.Vector(0.5, 1), new OpenNest.Geometry.Vector(0.5, 1.5) }, new[] { new OpenNest.Geometry.Vector(0, 1), new OpenNest.Geometry.Vector(0, 1.5) }, }, // Closed 0.5" square as a SINGLE polyline of 5 points → 4-segment PD packet. // Exercises multi-segment PD (one FF FD 50 44 00 00 followed by 4 records, // no intermediate lifts) and bi-directional motion (X+, Y+, X−, Y−). // Returns the head to its starting point so no manual jog needed after. "minisquare" => new System.Collections.Generic.List> { new[] { new OpenNest.Geometry.Vector(0, 0), new OpenNest.Geometry.Vector(0.5, 0), new OpenNest.Geometry.Vector(0.5, 0.5), new OpenNest.Geometry.Vector(0, 0.5), new OpenNest.Geometry.Vector(0, 0), }, }, _ => throw new ArgumentException($"Unknown preset '{preset}' (try testA, testB, miniB, or miniSquare)."), }; using var outFs = new FileStream(outFile, FileMode.Create, FileAccess.Write); new OpenNest.Posts.GravographIS.GravographISWriter().Write(polylines, outFs); Console.WriteLine($"Wrote {new FileInfo(outFile).Length} bytes via live writer → {outFile}"); return 0; } var file = args[0]; var portName = args[1]; var chunk = args.Length > 2 ? int.Parse(args[2]) : 256; var flowArg = args.Length > 3 ? args[3] : "rtscts"; var handshake = flowArg.ToLowerInvariant() switch { "rtscts" or "rts" or "cts" => Handshake.RequestToSend, "xonxoff" or "xon" or "xoff" => Handshake.XOnXOff, "none" => Handshake.None, _ => throw new ArgumentException($"Unknown flow control '{flowArg}'."), }; if (!File.Exists(file)) { Console.Error.WriteLine($"File not found: {file}"); return 3; } var bytes = File.ReadAllBytes(file); Console.WriteLine($"File: {file}"); Console.WriteLine($"Size: {bytes.Length} bytes"); Console.WriteLine($"Header: {BitConverter.ToString(bytes, 0, Math.Min(7, bytes.Length)).Replace('-', ' ')}"); var ports = SerialPort.GetPortNames(); Array.Sort(ports); Console.WriteLine($"Available COM ports: {string.Join(", ", ports)}"); if (Array.IndexOf(ports, portName) < 0) { Console.Error.WriteLine($"{portName} not in available ports."); return 4; } using var port = new SerialPort(portName, 9600, Parity.None, 8, StopBits.One) { Handshake = handshake, WriteTimeout = 30000, ReadTimeout = 30000, WriteBufferSize = 4096, DtrEnable = true, }; // Probe with the same CreateFile flags SerialStream uses, in this same process, // so we can tell SerialStream-specific failures apart from process-level access denials. { const uint GENERIC_RW = 0x80000000u | 0x40000000u; const uint OPEN_EXISTING = 3; const uint FILE_FLAG_OVERLAPPED = 0x40000000u; var devName = @"\\.\" + portName; var handle = NativeMethods.CreateFileW(devName, GENERIC_RW, 0, IntPtr.Zero, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, IntPtr.Zero); var err = Marshal.GetLastWin32Error(); if (handle.IsInvalid) { Console.WriteLine($"CreateFile(\"{devName}\", overlapped, exclusive) FAILED: win32={err} ({new Win32Exception(err).Message})"); } else { Console.WriteLine($"CreateFile(\"{devName}\", overlapped, exclusive) OK — closing."); handle.Close(); } } Console.WriteLine($"Opening {portName} 9600 8N1 handshake={handshake}..."); port.Open(); Console.WriteLine("Opened."); var sw = Stopwatch.StartNew(); try { for (var i = 0; i < bytes.Length; i += chunk) { var n = Math.Min(chunk, bytes.Length - i); port.Write(bytes, i, n); } try { port.BaseStream.Flush(); } catch { /* advisory */ } Thread.Sleep(500); } finally { sw.Stop(); port.Close(); } Console.WriteLine($"Sent {bytes.Length} bytes in {sw.ElapsedMilliseconds} ms. Port closed."); return 0; internal static class NativeMethods { [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CreateFileW")] internal static extern SafeFileHandle CreateFileW( string lpFileName, uint dwDesiredAccess, uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, IntPtr hTemplateFile); }