mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-11-07 07:35:44 -05:00
N26-295's PT42 pairs saved spacing in slice 1 but every shared edge was still cut twice, costing a pass per edge and risking a freed part tipping under the head on the second pass. Plan Cutting now cuts a plate's common lines once unless the plate is set to cut them twice. Each group is cut member by member, owner before partner (a captured dependency that search and replay enforce, so a kept order cutting the partner first is a constraint conflict): the member's holes, then the shared cuts it owns as open CommonLine centerline cuts entered through the notch on the span's axis, then its remainder pieces, each ramped in from the kerf the shared cut opened. Capture marks the shared spans as tabs on the owned clean copy, so replay's contour accounting proves each perimeter is cut exactly once; a lead that fits at neither end refuses as UnsupportedGeometry naming the span, never shortened. Material still comes from the drawing, so utilization, quantity and overlap results do not change. Members are never support-tabbed; locked or excluded members and broken groups refuse. The CI Fiber post writes shared cuts in the cut-off form with a lead-in and no G41/G42, and remainders in the contour form on their perimeter's compensation side, named by a remainder comment. Cincinnati CL (whose PreApplied mode offsets nothing) and Gravograph refuse such nests, as do the desktop and console for any post without ICommonLinePostProcessor; nothing falls back to a silent double cut. Plate > Arrange > Cut Common Lines Twice keeps slice 1's behavior, persists with the plate and makes an open plan stale. Pre-post review now checks each span is cut once, on its centerline, by the member cut first, and the rapid check treats a finished member's remainders as an obstacle. Compensation start in void and cancel in kerf are unconfirmed on the CI FIBER 4020; release waits on a 3/16 stainless coupon trial. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
160 lines
6.2 KiB
C#
160 lines
6.2 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));
|
|
|
|
CNC.CommonLineCuts.EnsurePostable(nest, this);
|
|
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);
|
|
CNC.CommonLineCuts.EnsurePostable(nest, this);
|
|
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);
|
|
}
|
|
}
|
|
}
|