Files
OpenNest/OpenNest.Tests/Cincinnati/UserVariablePostTests.cs
T
ajandClaude Opus 5.5 e8c52a354d fix(posts): refuse wrong-process output and keep settings across updates
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>
2026-11-02 09:51:11 -05:00

214 lines
7.6 KiB
C#

using System.IO;
using System.Linq;
using System.Text;
using OpenNest.CNC;
using OpenNest.Geometry;
using OpenNest.IO;
using OpenNest.Posts.Cincinnati;
namespace OpenNest.Tests.Cincinnati;
public class UserVariablePostTests
{
[Fact]
public void UserVariables_EmittedInDeclarationSubprogram()
{
var output = PostNestWithVariables("width = 48.0\nG90\nG01X$widthY0");
Assert.Contains("#200=48", output);
Assert.Contains("WIDTH", output.ToUpper());
}
[Fact]
public void UserVariables_InlineVariable_NotEmittedAsNumbered()
{
var output = PostNestWithVariables("kerf = 0.06 inline\nG90\nG01X1Y0");
Assert.DoesNotContain("#200", output);
}
[Fact]
public void UserVariables_CoordinateUsesNumberedVariable()
{
var output = PostNestWithVariables("width = 48.0\nG90\nG01X$widthY0");
Assert.Contains("X#200", output);
}
[Fact]
public void UserVariables_InlineVariable_CoordinateUsesLiteral()
{
var output = PostNestWithVariables("kerf = 0.06 inline\nG90\nG01X$kerfY0");
Assert.Contains("X0.06", output);
// G1 coordinate lines should not use X#nnn variable references for inline vars
var g1Lines = output.Split('\n').Where(l => l.TrimStart().StartsWith("G1 ")).ToList();
Assert.All(g1Lines, line => Assert.DoesNotContain("X#", line));
}
[Fact]
public void UserVariables_GlobalVariables_SharedAcrossDrawings()
{
var pgm1 = ParseProgram("sheet_width = 48.0 global\nG90\nG01X$sheet_widthY0");
var pgm2 = ParseProgram("sheet_width = 48.0 global\nG90\nG01X$sheet_widthY0");
var drawing1 = new Drawing("Part1", pgm1);
var drawing2 = new Drawing("Part2", pgm2);
var nest = new Nest { Name = "Test" };
nest.Drawings.Add(drawing1);
nest.Drawings.Add(drawing2);
var plate = new Plate(new Size(100, 100));
plate.Parts.Add(new Part(drawing1, new Vector(0, 0)));
plate.Parts.Add(new Part(drawing2, new Vector(50, 0)));
nest.Plates.Add(plate);
var config = new CincinnatiPostConfig { RequireLibraries = false, UserVariableStart = 200 };
var post = new CincinnatiPostProcessor(config);
var output = PostToString(post, nest);
// Both should use the same #200 — only one declaration
var declarationCount = output
.Split('\n')
.Count(l => l.Contains("#200=") && l.ToUpper().Contains("SHEET WIDTH"));
Assert.Equal(1, declarationCount);
}
[Fact]
public void UserVariables_LocalVariables_GetSeparateNumbers()
{
var pgm1 = ParseProgram("diameter = 0.3\nG90\nG01X$diameterY0");
var pgm2 = ParseProgram("diameter = 0.5\nG90\nG01X$diameterY0");
var drawing1 = new Drawing("TubeA", pgm1);
var drawing2 = new Drawing("TubeB", pgm2);
var nest = new Nest { Name = "Test" };
nest.Drawings.Add(drawing1);
nest.Drawings.Add(drawing2);
var plate = new Plate(new Size(100, 100));
plate.Parts.Add(new Part(drawing1, new Vector(0, 0)));
plate.Parts.Add(new Part(drawing2, new Vector(50, 0)));
nest.Plates.Add(plate);
var config = new CincinnatiPostConfig { RequireLibraries = false, UserVariableStart = 200 };
var post = new CincinnatiPostProcessor(config);
var output = PostToString(post, nest);
// Two separate declarations with different numbers
Assert.Contains("#200=0.3", output);
Assert.Contains("#201=0.5", output);
Assert.Contains("TUBE A", output.ToUpper());
Assert.Contains("TUBE B", output.ToUpper());
}
[Fact]
public void UserVariables_StartNumberConfigurable()
{
var config = new CincinnatiPostConfig { RequireLibraries = false, UserVariableStart = 300 };
var output = PostNestWithVariables("width = 48.0\nG90\nG01X$widthY0", config);
Assert.Contains("#300=48", output);
}
[Fact]
public void CutOff_VerticalCut_UsesSheetWidthVariable()
{
// Create a plate with a vertical cutoff
var config = new CincinnatiPostConfig
{
RequireLibraries = false,
SheetWidthVariable = 110,
SheetLengthVariable = 111,
};
var nest = new Nest { Name = "Test" };
var plate = new Plate(new Size(48, 96));
// Add a simple part so the plate isn't empty
var partPgm = new Program();
partPgm.Codes.Add(new RapidMove(0, 0));
partPgm.Codes.Add(new LinearMove(10, 0));
partPgm.Codes.Add(new LinearMove(10, 10));
partPgm.Codes.Add(new LinearMove(0, 10));
partPgm.Codes.Add(new LinearMove(0, 0));
var drawing = new Drawing("Part1", partPgm);
nest.Drawings.Add(drawing);
plate.Parts.Add(new Part(drawing, new Vector(0, 0)));
// Add a vertical cutoff that goes full width (Y=0 to Y=48)
var cutoff = new CutOff(new Vector(20, 0), CutOffAxis.Vertical);
plate.CutOffs.Add(cutoff);
plate.RegenerateCutOffs(new CutOffSettings());
nest.Plates.Add(plate);
var post = new CincinnatiPostProcessor(config);
var output = PostToString(post, nest);
// The cutoff line end at Y=48 (sheet width) should use #110
Assert.Contains("Y#110", output);
}
[Fact]
public void CutOff_SegmentedCut_OnlyEdgeUsesVariable()
{
// Create a plate with a part in the middle and a vertical cutoff
var config = new CincinnatiPostConfig { RequireLibraries = false, SheetWidthVariable = 110 };
var nest = new Nest { Name = "Test" };
var plate = new Plate(new Size(48, 96));
// Part in the middle — cutoff will be segmented around it
var partPgm = new Program();
partPgm.Codes.Add(new RapidMove(0, 0));
partPgm.Codes.Add(new LinearMove(10, 0));
partPgm.Codes.Add(new LinearMove(10, 10));
partPgm.Codes.Add(new LinearMove(0, 10));
partPgm.Codes.Add(new LinearMove(0, 0));
var drawing = new Drawing("Part1", partPgm);
nest.Drawings.Add(drawing);
plate.Parts.Add(new Part(drawing, new Vector(15, 20))); // Part at Y=20-30, should create gap
var cutoff = new CutOff(new Vector(20, 0), CutOffAxis.Vertical);
plate.CutOffs.Add(cutoff);
plate.RegenerateCutOffs(new CutOffSettings());
nest.Plates.Add(plate);
var post = new CincinnatiPostProcessor(config);
var output = PostToString(post, nest);
// The last segment endpoint at Y=48 should use #110
Assert.Contains("Y#110", output);
}
private static string PostNestWithVariables(string gcode, CincinnatiPostConfig config = null)
{
var program = ParseProgram(gcode);
var drawing = new Drawing("TestPart", program);
var nest = new Nest { Name = "Test" };
nest.Drawings.Add(drawing);
var plate = new Plate(new Size(100, 100));
plate.Parts.Add(new Part(drawing, new Vector(0, 0)));
nest.Plates.Add(plate);
config ??= new CincinnatiPostConfig { RequireLibraries = false, UserVariableStart = 200 };
var post = new CincinnatiPostProcessor(config);
return PostToString(post, nest);
}
private static string PostToString(CincinnatiPostProcessor post, Nest nest)
{
var ms = new MemoryStream();
post.Post(nest, ms);
ms.Position = 0;
return new StreamReader(ms).ReadToEnd();
}
private static Program ParseProgram(string gcode)
{
var stream = new MemoryStream(Encoding.UTF8.GetBytes(gcode));
var reader = new ProgramReader(stream);
var program = reader.Read();
reader.Close();
return program;
}
}