mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-04 18:11:35 -05:00
Ahead of the new laser schedule, three ways a post could silently cut with the wrong process, and one way its settings silently reset: - Cincinnati CL wrote no G89 when no cut library matched the nest's material, thickness and gas (or no etch library for etch features), so the controller cut with whatever library it loaded last. It now refuses, unless the new "Require libraries" setting is turned off. - Cincinnati CL declared G20/G21 from its own setting without comparing the nest's units, so a metric nest posted 25.4x too large. It now refuses a mismatch. - CI Fiber posted any material missing from its table as the default code (MSN), so stainless or aluminum ran with mild-steel technology. A named, unmapped material is now refused; the default code covers only nests with no material. - Posts saved their settings beside their DLL, inside the install folder a Velopack update replaces, so every update reset them to the shipped defaults. Saves now go to %APPDATA%\OpenNest\Posts and the shipped JSON only seeds them; constructing a post no longer writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
158 lines
6.1 KiB
C#
158 lines
6.1 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// IPostProcessor implementation for the Gravograph IS8000. <see cref="Post(Nest, Stream)"/>
|
|
/// writes the binary HPGL bytes. For serial streaming, use <see cref="Stream(Nest, string, Handshake, CancellationToken)"/>.
|
|
///
|
|
/// Geometry is split by <see cref="OpenNest.CNC.LayerType"/> into an engrave pass
|
|
/// (Scribe) and a cut pass (Cut), each with its own feed/depth from <see cref="Config"/>.
|
|
/// The cut pass pauses by default so the operator can swap/adjust the tool.
|
|
/// </summary>
|
|
public sealed class GravographISPostProcessor : IJsonConfigPostProcessor
|
|
{
|
|
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; private set; }
|
|
|
|
object IConfigurablePostProcessor.Config => Config;
|
|
|
|
/// <summary>Loads this PC's saved settings, else the shipped defaults (see <see cref="PostSettingsFile"/>).</summary>
|
|
public GravographISPostProcessor()
|
|
{
|
|
var json = PostSettingsFile.Read(typeof(GravographISPostProcessor).Assembly);
|
|
Config = json == null
|
|
? new GravographISPostConfig()
|
|
: JsonSerializer.Deserialize<GravographISPostConfig>(json, JsonOptions) ?? new GravographISPostConfig();
|
|
}
|
|
|
|
public GravographISPostProcessor(GravographISPostConfig config)
|
|
{
|
|
Config = config ?? throw new ArgumentNullException(nameof(config));
|
|
}
|
|
|
|
public void SaveConfig() => PostSettingsFile.Write(typeof(GravographISPostProcessor).Assembly, SerializeConfig());
|
|
|
|
public string SerializeConfig() => JsonSerializer.Serialize(Config, JsonOptions);
|
|
|
|
public void LoadConfig(string json) =>
|
|
Config =
|
|
JsonSerializer.Deserialize<GravographISPostConfig>(json, JsonOptions)
|
|
?? throw new JsonException("The post settings are empty.");
|
|
|
|
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)
|
|
{
|
|
// Refuse before creating the file, so a refusal never empties an existing program.
|
|
CNC.DisplayPerimeterTail.EnsurePostable(nest);
|
|
using var fs = new FileStream(outputFile, FileMode.Create, FileAccess.Write);
|
|
Post(nest, fs);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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).
|
|
/// </summary>
|
|
public IReadOnlyList<GravographPass> BuildPasses(IEnumerable<LayeredPolyline> polylines)
|
|
{
|
|
if (polylines == null)
|
|
throw new ArgumentNullException(nameof(polylines));
|
|
|
|
var engrave = new List<IReadOnlyList<Vector>>();
|
|
var cut = new List<IReadOnlyList<Vector>>();
|
|
|
|
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<GravographPass>();
|
|
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<IReadOnlyList<Vector>> polylines)
|
|
{
|
|
return new GravographPass
|
|
{
|
|
Polylines = PolylinePrePass.Prepare(polylines, StitchTolerance, AllowReverse),
|
|
FeedMmPerSec = block.FeedMmPerSec,
|
|
DepthInches = block.Depth,
|
|
PauseBefore = block.PauseBefore,
|
|
PauseMessage = block.PauseMessage ?? "",
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Buffers the encoded job in memory, then streams it to the named COM port.
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
}
|