Files
OpenNest/tools/StreamGravographJob/Program.cs
T
2026-05-23 12:40:53 -04:00

267 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 <file.prn> <COMx> [chunk=256] [flow=rtscts|xonxoff|none]");
Console.Error.WriteLine(" StreamGravographJob --gen <name> <outfile.prn> # name: testA | testB | miniB | miniSquare");
Console.Error.WriteLine(" StreamGravographJob --inspect-nest <file.nest>");
Console.Error.WriteLine(" StreamGravographJob --from-nest <file.nest> <outfile.prn>");
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 <file.nest>"); 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 <file.nest> <outfile.prn>"); 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 <name> <outfile>"); return 2; }
var preset = args[1];
var outFile = args[2];
var polylines = preset.ToLowerInvariant() switch
{
"testa" => new System.Collections.Generic.List<System.Collections.Generic.IReadOnlyList<OpenNest.Geometry.Vector>>
{
new[] { new OpenNest.Geometry.Vector(1, 1), new OpenNest.Geometry.Vector(1, 3) },
},
"testb" => new System.Collections.Generic.List<System.Collections.Generic.IReadOnlyList<OpenNest.Geometry.Vector>>
{
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<System.Collections.Generic.IReadOnlyList<OpenNest.Geometry.Vector>>
{
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<System.Collections.Generic.IReadOnlyList<OpenNest.Geometry.Vector>>
{
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);
}