Cutoff features now substitute plate-edge coordinates with #SheetWidthVariable and #SheetLengthVariable references. Vertical cutoffs at Y=plate_width emit Y#110, horizontal cutoffs at X=plate_length emit X#111. Segmented cutoffs only substitute the edge coordinate, interior segment endpoints stay literal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
208 lines
7.4 KiB
C#
208 lines
7.4 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 { 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 { 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 { 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 { 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 { 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 { 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;
|
|
}
|
|
}
|