feat: add material library resolution, assist gas support, and UI fixes

- 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>
This commit is contained in:
2026-03-23 22:00:57 -04:00
parent 072915abf2
commit b970629a59
15 changed files with 327 additions and 29 deletions

View File

@@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace OpenNest.Posts.Cincinnati
{
/// <summary>
@@ -153,16 +155,29 @@ namespace OpenNest.Posts.Cincinnati
public G89Mode ProcessParameterMode { get; set; } = G89Mode.LibraryFile;
/// <summary>
/// Gets or sets the default G89 library file path.
/// Default: empty string
/// Gets or sets the default assist gas when Nest.AssistGas is empty.
/// Default: "O2"
/// </summary>
public string DefaultLibraryFile { get; set; } = "";
public string DefaultAssistGas { get; set; } = "O2";
/// <summary>
/// Gets or sets whether to repeat G89 before each feature.
/// Default: true
/// Gets or sets the gas used for etch operations.
/// Independent of the cutting assist gas — etch typically requires a specific gas.
/// Default: "N2"
/// </summary>
public bool RepeatG89BeforeEachFeature { get; set; } = true;
public string DefaultEtchGas { get; set; } = "N2";
/// <summary>
/// Gets or sets the material-to-library mapping for cut operations.
/// Each entry maps (material, thickness, gas) to a G89 library file.
/// </summary>
public List<MaterialLibraryEntry> MaterialLibraries { get; set; } = new();
/// <summary>
/// Gets or sets the gas-to-library mapping for etch operations.
/// Each entry maps a gas type to a G89 etch library file.
/// </summary>
public List<EtchLibraryEntry> EtchLibraries { get; set; } = new();
/// <summary>
/// Gets or sets whether to use exact stop mode (G61).
@@ -272,4 +287,18 @@ namespace OpenNest.Posts.Cincinnati
/// </summary>
public int SheetLengthVariable { get; set; } = 111;
}
public class MaterialLibraryEntry
{
public string Material { get; set; } = "";
public double Thickness { get; set; }
public string Gas { get; set; } = "";
public string Library { get; set; } = "";
}
public class EtchLibraryEntry
{
public string Gas { get; set; } = "";
public string Library { get; set; } = "";
}
}

View File

@@ -68,7 +68,18 @@ namespace OpenNest.Posts.Cincinnati
.Where(p => p.Parts.Count > 0)
.ToList();
// 3. Build part sub-program registry (if enabled)
// 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;
@@ -100,21 +111,21 @@ namespace OpenNest.Posts.Cincinnati
}
}
// 4. Create writers
// 5. Create writers
var preamble = new CincinnatiPreambleWriter(Config);
var sheetWriter = new CincinnatiSheetWriter(Config, vars);
// 5. Build material description from first plate
var material = plates.FirstOrDefault()?.Material;
// 6. Build material description from first plate
var material = firstPlate?.Material;
var materialDesc = material != null
? $"{material.Name}{(string.IsNullOrEmpty(material.Grade) ? "" : $", {material.Grade}")}"
: "";
// 6. Write to stream
// 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);
preamble.WriteMainProgram(writer, nest.Name ?? "NEST", materialDesc, plates.Count, initialCutLibrary);
// Variable declaration subprogram
preamble.WriteVariableDeclaration(writer, vars);
@@ -122,17 +133,18 @@ namespace OpenNest.Posts.Cincinnati
// Sheet subprograms
for (var i = 0; i < plates.Count; i++)
{
var plate = plates[i];
var sheetIndex = i + 1;
var subNumber = Config.SheetSubprogramStart + i;
sheetWriter.Write(writer, plates[i], nest.Name ?? "NEST", sheetIndex, subNumber,
partSubprograms);
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 firstPlate = plates.FirstOrDefault();
var sheetDiagonal = firstPlate != null
? System.Math.Sqrt(firstPlate.Size.Width * firstPlate.Size.Width
+ firstPlate.Size.Length * firstPlate.Size.Length)
@@ -141,7 +153,7 @@ namespace OpenNest.Posts.Cincinnati
foreach (var (subNum, name, pgm) in subprogramEntries)
{
partSubWriter.Write(writer, pgm, name, subNum,
Config.DefaultLibraryFile ?? "", sheetDiagonal);
initialCutLibrary, etchLibrary, sheetDiagonal);
}
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace OpenNest.Posts.Cincinnati;
public sealed class MaterialLibraryResolver
{
private const double ThicknessTolerance = 0.001;
private readonly List<MaterialLibraryEntry> _materialLibraries;
private readonly List<EtchLibraryEntry> _etchLibraries;
public MaterialLibraryResolver(CincinnatiPostConfig config)
{
_materialLibraries = config.MaterialLibraries ?? new List<MaterialLibraryEntry>();
_etchLibraries = config.EtchLibraries ?? new List<EtchLibraryEntry>();
}
public string ResolveCutLibrary(string materialName, double thickness, string gas)
{
var entry = _materialLibraries.FirstOrDefault(e =>
string.Equals(e.Material, materialName, StringComparison.OrdinalIgnoreCase) &&
System.Math.Abs(e.Thickness - thickness) <= ThicknessTolerance &&
string.Equals(e.Gas, gas, StringComparison.OrdinalIgnoreCase));
return entry?.Library ?? "";
}
public string ResolveEtchLibrary(string gas)
{
var entry = _etchLibraries.FirstOrDefault(e =>
string.Equals(e.Gas, gas, StringComparison.OrdinalIgnoreCase));
return entry?.Library ?? "";
}
public static string ResolveGas(Nest nest, CincinnatiPostConfig config)
{
return !string.IsNullOrEmpty(nest.AssistGas) ? nest.AssistGas : config.DefaultAssistGas;
}
}