using System; using System.Collections.Generic; using System.IO; using System.IO.Ports; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using OpenNest.Geometry; namespace OpenNest.Posts.GravographIS { /// /// IPostProcessor implementation for the Gravograph IS8000. /// writes the binary HPGL bytes. For serial streaming, use . /// /// Geometry is split by into an engrave pass /// (Scribe) and a cut pass (Cut), each with its own feed/depth from . /// The cut pass pauses by default so the operator can swap/adjust the tool. /// public sealed class GravographISPostProcessor : IConfigurablePostProcessor { private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true, Converters = { new JsonStringEnumConverter() } }; public string Name => "Gravograph IS8000"; public string Author => "OpenNest"; public string Description => "Gravograph IS8000 mechanical engraver (binary HPGL over serial)"; public GravographISWriterOptions WriterOptions { get; } = new GravographISWriterOptions(); public NestPolylineExtractor Extractor { get; } = new NestPolylineExtractor(); public double StitchTolerance { get; set; } = PolylinePrePass.DefaultStitchTolerance; public bool AllowReverse { get; set; } = true; public GravographISPostConfig Config { get; } object IConfigurablePostProcessor.Config => Config; public GravographISPostProcessor() { var configPath = GetConfigPath(); if (File.Exists(configPath)) { var json = File.ReadAllText(configPath); Config = JsonSerializer.Deserialize(json, JsonOptions) ?? new GravographISPostConfig(); } else { Config = new GravographISPostConfig(); SaveConfig(); } } public GravographISPostProcessor(GravographISPostConfig config) { Config = config ?? throw new ArgumentNullException(nameof(config)); } public void SaveConfig() { var configPath = GetConfigPath(); var json = JsonSerializer.Serialize(Config, JsonOptions); File.WriteAllText(configPath, json); } private static string GetConfigPath() { var assemblyPath = typeof(GravographISPostProcessor).Assembly.Location; var dir = Path.GetDirectoryName(assemblyPath); var name = Path.GetFileNameWithoutExtension(assemblyPath); return Path.Combine(dir, name + ".json"); } public void Post(Nest nest, Stream outputStream) { if (nest == null) throw new ArgumentNullException(nameof(nest)); if (outputStream == null) throw new ArgumentNullException(nameof(outputStream)); var passes = BuildPasses(Extractor.ExtractLayered(nest)); new GravographISWriter(WriterOptions).Write(passes, outputStream); } public void Post(Nest nest, string outputFile) { using var fs = new FileStream(outputFile, FileMode.Create, FileAccess.Write); Post(nest, fs); } /// /// Groups layer-tagged polylines into ordered tool passes: engrave (Scribe) /// first, then cut. Each group is stitch/reverse-optimized independently. /// Geometry whose layer maps to no config (Display) is skipped. When only one /// group is present, a single pass is returned (and so the writer emits no pause). /// public IReadOnlyList BuildPasses(IEnumerable polylines) { if (polylines == null) throw new ArgumentNullException(nameof(polylines)); var engrave = new List>(); var cut = new List>(); foreach (var poly in polylines) { if (poly == null) continue; var block = Config.ConfigFor(poly.Layer); if (block == null) continue; // non-cutting (Display) geometry if (ReferenceEquals(block, Config.Engrave)) engrave.Add(poly.Points); else cut.Add(poly.Points); } var passes = new List(); if (engrave.Count > 0) passes.Add(MakePass(Config.Engrave, engrave)); if (cut.Count > 0) passes.Add(MakePass(Config.Cut, cut)); return passes; } private GravographPass MakePass(LayerCutConfig block, List> polylines) { return new GravographPass { Polylines = PolylinePrePass.Prepare(polylines, StitchTolerance, AllowReverse), FeedMmPerSec = block.FeedMmPerSec, DepthInches = block.Depth, PauseBefore = block.PauseBefore, PauseMessage = block.PauseMessage ?? "", }; } /// /// Buffers the encoded job in memory, then streams it to the named COM port. /// public void Stream(Nest nest, string portName, Handshake handshake = Handshake.RequestToSend, CancellationToken cancellationToken = default) { byte[] bytes; using (var ms = new MemoryStream()) { Post(nest, ms); bytes = ms.ToArray(); } using var port = new GravographISPort(); port.Open(portName, handshake); port.StreamJob(bytes, cancellationToken); } } }