- Add MaterialLibraryResolver for Cincinnati post processor to resolve G89 library files from material/thickness/gas configuration - Add Nest.AssistGas property with serialization support in nest format - Add etch library support with separate gas configuration - Fix CutOff tests to match AwayFromOrigin default cut direction - Fix plate info label not updating after ResizePlateToFitParts - Add cutoff and remnants toolbar button icons Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
180 lines
6.9 KiB
C#
180 lines
6.9 KiB
C#
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Text.Json.Serialization;
|
|
using OpenNest.CNC;
|
|
|
|
namespace OpenNest.Posts.Cincinnati
|
|
{
|
|
public sealed class CincinnatiPostProcessor : IPostProcessor
|
|
{
|
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
|
{
|
|
WriteIndented = true,
|
|
Converters = { new JsonStringEnumConverter() }
|
|
};
|
|
|
|
public string Name => "Cincinnati CL-707";
|
|
public string Author => "OpenNest";
|
|
public string Description => "Cincinnati CL-707/CL-800/CL-900/CL-940/CLX family";
|
|
|
|
public CincinnatiPostConfig Config { get; }
|
|
|
|
public CincinnatiPostProcessor()
|
|
{
|
|
var configPath = GetConfigPath();
|
|
if (File.Exists(configPath))
|
|
{
|
|
var json = File.ReadAllText(configPath);
|
|
Config = JsonSerializer.Deserialize<CincinnatiPostConfig>(json, JsonOptions);
|
|
}
|
|
else
|
|
{
|
|
Config = new CincinnatiPostConfig();
|
|
SaveConfig();
|
|
}
|
|
}
|
|
|
|
public CincinnatiPostProcessor(CincinnatiPostConfig config)
|
|
{
|
|
Config = config;
|
|
}
|
|
|
|
public void SaveConfig()
|
|
{
|
|
var configPath = GetConfigPath();
|
|
var json = JsonSerializer.Serialize(Config, JsonOptions);
|
|
File.WriteAllText(configPath, json);
|
|
}
|
|
|
|
private static string GetConfigPath()
|
|
{
|
|
var assemblyPath = typeof(CincinnatiPostProcessor).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)
|
|
{
|
|
// 1. Create variable manager and register standard variables
|
|
var vars = CreateVariableManager();
|
|
|
|
// 2. Filter to non-empty plates
|
|
var plates = nest.Plates
|
|
.Where(p => p.Parts.Count > 0)
|
|
.ToList();
|
|
|
|
// 3. Resolve gas and library files
|
|
var resolver = new MaterialLibraryResolver(Config);
|
|
var gas = MaterialLibraryResolver.ResolveGas(nest, Config);
|
|
var etchLibrary = resolver.ResolveEtchLibrary(Config.DefaultEtchGas);
|
|
|
|
// Resolve cut library from first plate for preamble
|
|
var firstPlate = plates.FirstOrDefault();
|
|
var initialCutLibrary = firstPlate != null
|
|
? resolver.ResolveCutLibrary(firstPlate.Material?.Name ?? "", firstPlate.Thickness, gas)
|
|
: "";
|
|
|
|
// 4. Build part sub-program registry (if enabled)
|
|
Dictionary<(int, long), int> partSubprograms = null;
|
|
List<(int subNum, string name, Program program)> subprogramEntries = null;
|
|
|
|
if (Config.UsePartSubprograms)
|
|
{
|
|
partSubprograms = new Dictionary<(int, long), int>();
|
|
subprogramEntries = new List<(int, string, Program)>();
|
|
var nextSubNum = Config.PartSubprogramStart;
|
|
|
|
foreach (var plate in plates)
|
|
{
|
|
foreach (var part in plate.Parts)
|
|
{
|
|
if (part.BaseDrawing.IsCutOff) continue;
|
|
var key = CincinnatiPartSubprogramWriter.SubprogramKey(part);
|
|
if (!partSubprograms.ContainsKey(key))
|
|
{
|
|
var subNum = nextSubNum++;
|
|
partSubprograms[key] = subNum;
|
|
|
|
// Create normalized program at origin
|
|
var pgm = part.Program.Clone() as Program;
|
|
var bbox = pgm.BoundingBox();
|
|
pgm.Offset(-bbox.Location.X, -bbox.Location.Y);
|
|
|
|
subprogramEntries.Add((subNum, part.BaseDrawing.Name, pgm));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 5. Create writers
|
|
var preamble = new CincinnatiPreambleWriter(Config);
|
|
var sheetWriter = new CincinnatiSheetWriter(Config, vars);
|
|
|
|
// 6. Build material description from first plate
|
|
var material = firstPlate?.Material;
|
|
var materialDesc = material != null
|
|
? $"{material.Name}{(string.IsNullOrEmpty(material.Grade) ? "" : $", {material.Grade}")}"
|
|
: "";
|
|
|
|
// 7. Write to stream
|
|
using var writer = new StreamWriter(outputStream, Encoding.UTF8, 1024, leaveOpen: true);
|
|
|
|
// Main program
|
|
preamble.WriteMainProgram(writer, nest.Name ?? "NEST", materialDesc, plates.Count, initialCutLibrary);
|
|
|
|
// Variable declaration subprogram
|
|
preamble.WriteVariableDeclaration(writer, vars);
|
|
|
|
// Sheet subprograms
|
|
for (var i = 0; i < plates.Count; i++)
|
|
{
|
|
var plate = plates[i];
|
|
var sheetIndex = i + 1;
|
|
var subNumber = Config.SheetSubprogramStart + i;
|
|
var cutLibrary = resolver.ResolveCutLibrary(plate.Material?.Name ?? "", plate.Thickness, gas);
|
|
sheetWriter.Write(writer, plate, nest.Name ?? "NEST", sheetIndex, subNumber,
|
|
cutLibrary, etchLibrary, partSubprograms);
|
|
}
|
|
|
|
// Part sub-programs (if enabled)
|
|
if (subprogramEntries != null)
|
|
{
|
|
var partSubWriter = new CincinnatiPartSubprogramWriter(Config);
|
|
var sheetDiagonal = firstPlate != null
|
|
? System.Math.Sqrt(firstPlate.Size.Width * firstPlate.Size.Width
|
|
+ firstPlate.Size.Length * firstPlate.Size.Length)
|
|
: 100.0;
|
|
|
|
foreach (var (subNum, name, pgm) in subprogramEntries)
|
|
{
|
|
partSubWriter.Write(writer, pgm, name, subNum,
|
|
initialCutLibrary, etchLibrary, sheetDiagonal);
|
|
}
|
|
}
|
|
|
|
writer.Flush();
|
|
}
|
|
|
|
public void Post(Nest nest, string outputFile)
|
|
{
|
|
using var fs = new FileStream(outputFile, FileMode.Create, FileAccess.Write);
|
|
Post(nest, fs);
|
|
}
|
|
|
|
private ProgramVariableManager CreateVariableManager()
|
|
{
|
|
var vars = new ProgramVariableManager();
|
|
vars.GetOrCreate("ProcessFeedrate", 148); // Set by G89, no expression
|
|
vars.GetOrCreate("LeadInFeedrate", 126, $"[#148*{Config.LeadInFeedratePercent}]");
|
|
vars.GetOrCreate("LeadInArcLine2Feedrate", 127, $"[#148*{Config.LeadInArcLine2FeedratePercent}]");
|
|
vars.GetOrCreate("CircleFeedrate", 128, Config.CircleFeedrateMultiplier.ToString("0.#"));
|
|
return vars;
|
|
}
|
|
}
|
|
}
|