Compare commits
5 Commits
0246073b31
...
b970629a59
| Author | SHA1 | Date | |
|---|---|---|---|
| b970629a59 | |||
| 072915abf2 | |||
| aeeb2e4074 | |||
| a2f7219db3 | |||
| 7e4040ba08 |
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
||||||
return NestConsole.Run(args);
|
return NestConsole.Run(args);
|
||||||
@@ -20,6 +21,12 @@ static class NestConsole
|
|||||||
if (options == null)
|
if (options == null)
|
||||||
return 0; // --help was requested
|
return 0; // --help was requested
|
||||||
|
|
||||||
|
if (options.ListPosts)
|
||||||
|
{
|
||||||
|
ListPostProcessors(options);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (options.InputFiles.Count == 0)
|
if (options.InputFiles.Count == 0)
|
||||||
{
|
{
|
||||||
PrintUsage();
|
PrintUsage();
|
||||||
@@ -68,6 +75,7 @@ static class NestConsole
|
|||||||
|
|
||||||
PrintResults(success, plate, elapsed);
|
PrintResults(success, plate, elapsed);
|
||||||
Save(nest, options);
|
Save(nest, options);
|
||||||
|
PostProcess(nest, options);
|
||||||
|
|
||||||
return options.CheckOverlaps && overlapCount > 0 ? 1 : 0;
|
return options.CheckOverlaps && overlapCount > 0 ? 1 : 0;
|
||||||
}
|
}
|
||||||
@@ -120,6 +128,18 @@ static class NestConsole
|
|||||||
case "--engine" when i + 1 < args.Length:
|
case "--engine" when i + 1 < args.Length:
|
||||||
NestEngineRegistry.ActiveEngineName = args[++i];
|
NestEngineRegistry.ActiveEngineName = args[++i];
|
||||||
break;
|
break;
|
||||||
|
case "--post" when i + 1 < args.Length:
|
||||||
|
o.PostName = args[++i];
|
||||||
|
break;
|
||||||
|
case "--post-output" when i + 1 < args.Length:
|
||||||
|
o.PostOutput = args[++i];
|
||||||
|
break;
|
||||||
|
case "--posts-dir" when i + 1 < args.Length:
|
||||||
|
o.PostsDir = args[++i];
|
||||||
|
break;
|
||||||
|
case "--list-posts":
|
||||||
|
o.ListPosts = true;
|
||||||
|
break;
|
||||||
case "--help":
|
case "--help":
|
||||||
case "-h":
|
case "-h":
|
||||||
PrintUsage();
|
PrintUsage();
|
||||||
@@ -382,6 +402,100 @@ static class NestConsole
|
|||||||
Console.WriteLine($"Saved: {outputFile}");
|
Console.WriteLine($"Saved: {outputFile}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static string ResolvePostsDir(Options options)
|
||||||
|
{
|
||||||
|
if (options.PostsDir != null)
|
||||||
|
return options.PostsDir;
|
||||||
|
|
||||||
|
var exePath = Assembly.GetEntryAssembly()?.Location
|
||||||
|
?? typeof(NestConsole).Assembly.Location;
|
||||||
|
return Path.Combine(Path.GetDirectoryName(exePath), "Posts");
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<IPostProcessor> LoadPostProcessors(string postsDir)
|
||||||
|
{
|
||||||
|
var processors = new List<IPostProcessor>();
|
||||||
|
|
||||||
|
if (!Directory.Exists(postsDir))
|
||||||
|
return processors;
|
||||||
|
|
||||||
|
foreach (var file in Directory.GetFiles(postsDir, "*.dll"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assembly = Assembly.LoadFrom(file);
|
||||||
|
|
||||||
|
foreach (var type in assembly.GetTypes())
|
||||||
|
{
|
||||||
|
if (!typeof(IPostProcessor).IsAssignableFrom(type) || type.IsInterface || type.IsAbstract)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (Activator.CreateInstance(type) is IPostProcessor processor)
|
||||||
|
processors.Add(processor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Warning: failed to load post processor from {Path.GetFileName(file)}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processors;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ListPostProcessors(Options options)
|
||||||
|
{
|
||||||
|
var postsDir = ResolvePostsDir(options);
|
||||||
|
var processors = LoadPostProcessors(postsDir);
|
||||||
|
|
||||||
|
if (processors.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"No post processors found in: {postsDir}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"Post processors ({postsDir}):");
|
||||||
|
|
||||||
|
foreach (var p in processors)
|
||||||
|
Console.WriteLine($" {p.Name,-30} {p.Description}");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void PostProcess(Nest nest, Options options)
|
||||||
|
{
|
||||||
|
if (options.PostName == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var postsDir = ResolvePostsDir(options);
|
||||||
|
var processors = LoadPostProcessors(postsDir);
|
||||||
|
var post = processors.FirstOrDefault(p =>
|
||||||
|
p.Name.Equals(options.PostName, StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (post == null)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Error: post processor '{options.PostName}' not found");
|
||||||
|
|
||||||
|
if (processors.Count > 0)
|
||||||
|
Console.Error.WriteLine($"Available: {string.Join(", ", processors.Select(p => p.Name))}");
|
||||||
|
else
|
||||||
|
Console.Error.WriteLine($"No post processors found in: {postsDir}");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputFile = options.PostOutput;
|
||||||
|
|
||||||
|
if (outputFile == null)
|
||||||
|
{
|
||||||
|
var firstInput = options.InputFiles[0];
|
||||||
|
outputFile = Path.Combine(
|
||||||
|
Path.GetDirectoryName(firstInput),
|
||||||
|
$"{Path.GetFileNameWithoutExtension(firstInput)}.cnc");
|
||||||
|
}
|
||||||
|
|
||||||
|
post.Post(nest, outputFile);
|
||||||
|
Console.WriteLine($"Post: {post.Name} -> {outputFile}");
|
||||||
|
}
|
||||||
|
|
||||||
static void PrintUsage()
|
static void PrintUsage()
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine("Usage: OpenNest.Console <input-files...> [options]");
|
Console.Error.WriteLine("Usage: OpenNest.Console <input-files...> [options]");
|
||||||
@@ -407,6 +521,10 @@ static class NestConsole
|
|||||||
Console.Error.WriteLine(" --check-overlaps Run overlap detection after fill (exit code 1 if found)");
|
Console.Error.WriteLine(" --check-overlaps Run overlap detection after fill (exit code 1 if found)");
|
||||||
Console.Error.WriteLine(" --no-save Skip saving output file");
|
Console.Error.WriteLine(" --no-save Skip saving output file");
|
||||||
Console.Error.WriteLine(" --no-log Skip writing debug log file");
|
Console.Error.WriteLine(" --no-log Skip writing debug log file");
|
||||||
|
Console.Error.WriteLine(" --post <name> Run a post processor after nesting");
|
||||||
|
Console.Error.WriteLine(" --post-output <path> Output file for post processor (default: <input>.cnc)");
|
||||||
|
Console.Error.WriteLine(" --posts-dir <path> Directory containing post processor DLLs (default: Posts/)");
|
||||||
|
Console.Error.WriteLine(" --list-posts List available post processors and exit");
|
||||||
Console.Error.WriteLine(" -h, --help Show this help");
|
Console.Error.WriteLine(" -h, --help Show this help");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -425,5 +543,9 @@ static class NestConsole
|
|||||||
public bool KeepParts;
|
public bool KeepParts;
|
||||||
public bool AutoNest;
|
public bool AutoNest;
|
||||||
public string TemplateFile;
|
public string TemplateFile;
|
||||||
|
public string PostName;
|
||||||
|
public string PostOutput;
|
||||||
|
public string PostsDir;
|
||||||
|
public bool ListPosts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ namespace OpenNest
|
|||||||
|
|
||||||
public string Notes { get; set; }
|
public string Notes { get; set; }
|
||||||
|
|
||||||
|
public string AssistGas { get; set; } = "";
|
||||||
|
|
||||||
public Units Units { get; set; }
|
public Units Units { get; set; }
|
||||||
|
|
||||||
public DateTime DateCreated { get; set; }
|
public DateTime DateCreated { get; set; }
|
||||||
|
|||||||
@@ -23,9 +23,17 @@ namespace OpenNest.Engine.BestFit
|
|||||||
return new PolygonExtractionResult(null, Vector.Zero);
|
return new PolygonExtractionResult(null, Vector.Zero);
|
||||||
|
|
||||||
// Inflate by half-spacing if spacing is non-zero.
|
// Inflate by half-spacing if spacing is non-zero.
|
||||||
// OffsetSide.Right = outward for CCW perimeters (standard for outer contours).
|
// Detect winding direction to choose the correct outward offset side.
|
||||||
|
var outwardSide = OffsetSide.Right;
|
||||||
|
if (halfSpacing > 0)
|
||||||
|
{
|
||||||
|
var testPoly = perimeter.ToPolygon();
|
||||||
|
if (testPoly.Vertices.Count >= 3 && testPoly.RotationDirection() == RotationType.CW)
|
||||||
|
outwardSide = OffsetSide.Left;
|
||||||
|
}
|
||||||
|
|
||||||
var inflated = halfSpacing > 0
|
var inflated = halfSpacing > 0
|
||||||
? (perimeter.OffsetEntity(halfSpacing, OffsetSide.Right) as Shape ?? perimeter)
|
? (perimeter.OffsetEntity(halfSpacing, outwardSide) as Shape ?? perimeter)
|
||||||
: perimeter;
|
: perimeter;
|
||||||
|
|
||||||
// Convert to polygon with circumscribed arcs for tight nesting.
|
// Convert to polygon with circumscribed arcs for tight nesting.
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ namespace OpenNest.IO
|
|||||||
public string DateCreated { get; init; } = "";
|
public string DateCreated { get; init; } = "";
|
||||||
public string DateLastModified { get; init; } = "";
|
public string DateLastModified { get; init; } = "";
|
||||||
public string Notes { get; init; } = "";
|
public string Notes { get; init; } = "";
|
||||||
|
public string AssistGas { get; init; } = "";
|
||||||
public PlateDefaultsDto PlateDefaults { get; init; } = new();
|
public PlateDefaultsDto PlateDefaults { get; init; } = new();
|
||||||
public List<DrawingDto> Drawings { get; init; } = new();
|
public List<DrawingDto> Drawings { get; init; } = new();
|
||||||
public List<PlateDto> Plates { get; init; } = new();
|
public List<PlateDto> Plates { get; init; } = new();
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ namespace OpenNest.IO
|
|||||||
nest.DateCreated = DateTime.Parse(dto.DateCreated);
|
nest.DateCreated = DateTime.Parse(dto.DateCreated);
|
||||||
nest.DateLastModified = DateTime.Parse(dto.DateLastModified);
|
nest.DateLastModified = DateTime.Parse(dto.DateLastModified);
|
||||||
nest.Notes = dto.Notes;
|
nest.Notes = dto.Notes;
|
||||||
|
nest.AssistGas = dto.AssistGas ?? "";
|
||||||
|
|
||||||
// Plate defaults
|
// Plate defaults
|
||||||
var pd = dto.PlateDefaults;
|
var pd = dto.PlateDefaults;
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ namespace OpenNest.IO
|
|||||||
DateCreated = nest.DateCreated.ToString("o"),
|
DateCreated = nest.DateCreated.ToString("o"),
|
||||||
DateLastModified = nest.DateLastModified.ToString("o"),
|
DateLastModified = nest.DateLastModified.ToString("o"),
|
||||||
Notes = nest.Notes ?? "",
|
Notes = nest.Notes ?? "",
|
||||||
|
AssistGas = nest.AssistGas ?? "",
|
||||||
PlateDefaults = BuildPlateDefaultsDto(),
|
PlateDefaults = BuildPlateDefaultsDto(),
|
||||||
Drawings = BuildDrawingDtos(),
|
Drawings = BuildDrawingDtos(),
|
||||||
Plates = BuildPlateDtos()
|
Plates = BuildPlateDtos()
|
||||||
|
|||||||
@@ -57,6 +57,35 @@ namespace OpenNest.Mcp.Tools
|
|||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[McpServerTool(Name = "save_nest")]
|
||||||
|
[Description("Save the current session (all drawings and plates) to a .nest file.")]
|
||||||
|
public string SaveNest(
|
||||||
|
[Description("Absolute path for the output .nest file")] string path,
|
||||||
|
[Description("Name for the nest (optional)")] string name = null)
|
||||||
|
{
|
||||||
|
var nest = new Nest();
|
||||||
|
nest.Name = name ?? Path.GetFileNameWithoutExtension(path);
|
||||||
|
|
||||||
|
foreach (var drawing in _session.AllDrawings())
|
||||||
|
nest.Drawings.Add(drawing);
|
||||||
|
|
||||||
|
foreach (var plate in _session.AllPlates())
|
||||||
|
nest.Plates.Add(plate);
|
||||||
|
|
||||||
|
if (nest.Drawings.Count == 0)
|
||||||
|
return "Error: no drawings in session to save";
|
||||||
|
|
||||||
|
var dir = Path.GetDirectoryName(path);
|
||||||
|
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
var writer = new NestWriter(nest);
|
||||||
|
if (!writer.Write(path))
|
||||||
|
return "Error: failed to write nest file";
|
||||||
|
|
||||||
|
return $"Saved nest to {path}\n Drawings: {nest.Drawings.Count}\n Plates: {nest.Plates.Count}";
|
||||||
|
}
|
||||||
|
|
||||||
[McpServerTool(Name = "import_dxf")]
|
[McpServerTool(Name = "import_dxf")]
|
||||||
[Description("Import a DXF file as a new drawing. Returns drawing name and bounding box.")]
|
[Description("Import a DXF file as a new drawing. Returns drawing name and bounding box.")]
|
||||||
public string ImportDxf(
|
public string ImportDxf(
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public sealed class FeatureContext
|
|||||||
public bool IsLastFeatureOnSheet { get; set; }
|
public bool IsLastFeatureOnSheet { get; set; }
|
||||||
public bool IsSafetyHeadraise { get; set; }
|
public bool IsSafetyHeadraise { get; set; }
|
||||||
public bool IsExteriorFeature { get; set; }
|
public bool IsExteriorFeature { get; set; }
|
||||||
|
public bool IsEtch { get; set; }
|
||||||
public string LibraryFile { get; set; } = "";
|
public string LibraryFile { get; set; } = "";
|
||||||
public double CutDistance { get; set; }
|
public double CutDistance { get; set; }
|
||||||
public double SheetDiagonal { get; set; }
|
public double SheetDiagonal { get; set; }
|
||||||
@@ -61,17 +62,24 @@ public sealed class CincinnatiFeatureWriter
|
|||||||
if (ctx.IsFirstFeatureOfPart && !string.IsNullOrEmpty(ctx.PartName))
|
if (ctx.IsFirstFeatureOfPart && !string.IsNullOrEmpty(ctx.PartName))
|
||||||
writer.WriteLine(CoordinateFormatter.Comment($"PART: {ctx.PartName}"));
|
writer.WriteLine(CoordinateFormatter.Comment($"PART: {ctx.PartName}"));
|
||||||
|
|
||||||
// 3. G89 process params (if RepeatG89BeforeEachFeature)
|
// 3. G89 process params
|
||||||
if (_config.RepeatG89BeforeEachFeature && _config.ProcessParameterMode == G89Mode.LibraryFile)
|
if (_config.ProcessParameterMode == G89Mode.LibraryFile)
|
||||||
|
{
|
||||||
|
var lib = ctx.LibraryFile;
|
||||||
|
if (!string.IsNullOrEmpty(lib))
|
||||||
{
|
{
|
||||||
var lib = !string.IsNullOrEmpty(ctx.LibraryFile) ? ctx.LibraryFile : _config.DefaultLibraryFile;
|
|
||||||
var speedClass = _speedClassifier.Classify(ctx.CutDistance, ctx.SheetDiagonal);
|
var speedClass = _speedClassifier.Classify(ctx.CutDistance, ctx.SheetDiagonal);
|
||||||
var cutDist = _speedClassifier.FormatCutDist(ctx.CutDistance, ctx.SheetDiagonal);
|
var cutDist = _speedClassifier.FormatCutDist(ctx.CutDistance, ctx.SheetDiagonal);
|
||||||
writer.WriteLine($"G89 P {lib} ({speedClass} {cutDist})");
|
writer.WriteLine($"G89 P {lib} ({speedClass} {cutDist})");
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteLine("(WARNING: No library found)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Pierce and start cut
|
// 4. Pierce/beam on — G85 for etch (no pierce), G84 for cut
|
||||||
writer.WriteLine("G84");
|
writer.WriteLine(ctx.IsEtch ? "G85" : "G84");
|
||||||
|
|
||||||
// 5. Anti-dive off
|
// 5. Anti-dive off
|
||||||
if (_config.UseAntiDive)
|
if (_config.UseAntiDive)
|
||||||
@@ -90,20 +98,20 @@ public sealed class CincinnatiFeatureWriter
|
|||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
// Kerf compensation on first cutting move
|
// Kerf compensation on first cutting move (skip for etch)
|
||||||
if (!kerfEmitted && _config.KerfCompensation == KerfMode.ControllerSide)
|
if (!ctx.IsEtch && !kerfEmitted && _config.KerfCompensation == KerfMode.ControllerSide)
|
||||||
{
|
{
|
||||||
sb.Append(_config.DefaultKerfSide == KerfSide.Left ? "G41" : "G42");
|
sb.Append(_config.DefaultKerfSide == KerfSide.Left ? "G41 " : "G42 ");
|
||||||
kerfEmitted = true;
|
kerfEmitted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.Append($"G1X{_fmt.FormatCoord(linear.EndPoint.X)}Y{_fmt.FormatCoord(linear.EndPoint.Y)}");
|
sb.Append($"G1 X{_fmt.FormatCoord(linear.EndPoint.X)} Y{_fmt.FormatCoord(linear.EndPoint.Y)}");
|
||||||
|
|
||||||
// Feedrate
|
// Feedrate — etch always uses process feedrate
|
||||||
var feedVar = GetFeedVariable(linear.Layer);
|
var feedVar = ctx.IsEtch ? "#148" : GetFeedVariable(linear.Layer);
|
||||||
if (feedVar != lastFeedVar)
|
if (feedVar != lastFeedVar)
|
||||||
{
|
{
|
||||||
sb.Append($"F{feedVar}");
|
sb.Append($" F{feedVar}");
|
||||||
lastFeedVar = feedVar;
|
lastFeedVar = feedVar;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,28 +122,30 @@ public sealed class CincinnatiFeatureWriter
|
|||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
// Kerf compensation on first cutting move
|
// Kerf compensation on first cutting move (skip for etch)
|
||||||
if (!kerfEmitted && _config.KerfCompensation == KerfMode.ControllerSide)
|
if (!ctx.IsEtch && !kerfEmitted && _config.KerfCompensation == KerfMode.ControllerSide)
|
||||||
{
|
{
|
||||||
sb.Append(_config.DefaultKerfSide == KerfSide.Left ? "G41" : "G42");
|
sb.Append(_config.DefaultKerfSide == KerfSide.Left ? "G41 " : "G42 ");
|
||||||
kerfEmitted = true;
|
kerfEmitted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// G2 = CW, G3 = CCW
|
// G2 = CW, G3 = CCW
|
||||||
var gCode = arc.Rotation == RotationType.CW ? "G2" : "G3";
|
var gCode = arc.Rotation == RotationType.CW ? "G2" : "G3";
|
||||||
sb.Append($"{gCode}X{_fmt.FormatCoord(arc.EndPoint.X)}Y{_fmt.FormatCoord(arc.EndPoint.Y)}");
|
sb.Append($"{gCode} X{_fmt.FormatCoord(arc.EndPoint.X)} Y{_fmt.FormatCoord(arc.EndPoint.Y)}");
|
||||||
|
|
||||||
// Convert absolute center to incremental I/J
|
// Convert absolute center to incremental I/J
|
||||||
var i = arc.CenterPoint.X - currentPos.X;
|
var i = arc.CenterPoint.X - currentPos.X;
|
||||||
var j = arc.CenterPoint.Y - currentPos.Y;
|
var j = arc.CenterPoint.Y - currentPos.Y;
|
||||||
sb.Append($"I{_fmt.FormatCoord(i)}J{_fmt.FormatCoord(j)}");
|
sb.Append($" I{_fmt.FormatCoord(i)} J{_fmt.FormatCoord(j)}");
|
||||||
|
|
||||||
// Feedrate — full circles use multiplied feedrate
|
// Feedrate — etch always uses process feedrate, cut uses layer-based
|
||||||
var isFullCircle = IsFullCircle(currentPos, arc.EndPoint);
|
var isFullCircle = IsFullCircle(currentPos, arc.EndPoint);
|
||||||
var feedVar = isFullCircle ? "[#148*#128]" : GetFeedVariable(arc.Layer);
|
var feedVar = ctx.IsEtch ? "#148"
|
||||||
|
: isFullCircle ? "[#148*#128]"
|
||||||
|
: GetFeedVariable(arc.Layer);
|
||||||
if (feedVar != lastFeedVar)
|
if (feedVar != lastFeedVar)
|
||||||
{
|
{
|
||||||
sb.Append($"F{feedVar}");
|
sb.Append($" F{feedVar}");
|
||||||
lastFeedVar = feedVar;
|
lastFeedVar = feedVar;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,9 +193,9 @@ public sealed class CincinnatiFeatureWriter
|
|||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
if (_config.UseLineNumbers)
|
if (_config.UseLineNumbers)
|
||||||
sb.Append($"N{featureNumber}");
|
sb.Append($"N{featureNumber} ");
|
||||||
|
|
||||||
sb.Append($"G0X{_fmt.FormatCoord(piercePoint.X)}Y{_fmt.FormatCoord(piercePoint.Y)}");
|
sb.Append($"G0 X{_fmt.FormatCoord(piercePoint.X)} Y{_fmt.FormatCoord(piercePoint.Y)}");
|
||||||
|
|
||||||
writer.WriteLine(sb.ToString());
|
writer.WriteLine(sb.ToString());
|
||||||
}
|
}
|
||||||
@@ -194,7 +204,7 @@ public sealed class CincinnatiFeatureWriter
|
|||||||
{
|
{
|
||||||
if (ctx.IsSafetyHeadraise && _config.SafetyHeadraiseDistance.HasValue)
|
if (ctx.IsSafetyHeadraise && _config.SafetyHeadraiseDistance.HasValue)
|
||||||
{
|
{
|
||||||
writer.WriteLine($"M47 P{_config.SafetyHeadraiseDistance.Value}(Safety Headraise)");
|
writer.WriteLine($"M47 P{_config.SafetyHeadraiseDistance.Value} (Safety Headraise)");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,19 +27,22 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
/// The program coordinates must already be normalized to origin (0,0).
|
/// The program coordinates must already be normalized to origin (0,0).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Write(TextWriter w, Program normalizedProgram, string drawingName,
|
public void Write(TextWriter w, Program normalizedProgram, string drawingName,
|
||||||
int subNumber, string libraryFile, double sheetDiagonal)
|
int subNumber, string cutLibrary, string etchLibrary, double sheetDiagonal)
|
||||||
{
|
{
|
||||||
var features = SplitFeatures(normalizedProgram.Codes);
|
var allFeatures = SplitFeatures(normalizedProgram.Codes);
|
||||||
if (features.Count == 0)
|
if (allFeatures.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Classify and order: etch features first, then cut features
|
||||||
|
var ordered = OrderFeatures(allFeatures);
|
||||||
|
|
||||||
w.WriteLine("(*****************************************************)");
|
w.WriteLine("(*****************************************************)");
|
||||||
w.WriteLine($":{subNumber}");
|
w.WriteLine($":{subNumber}");
|
||||||
w.WriteLine(CoordinateFormatter.Comment($"PART: {drawingName}"));
|
w.WriteLine(CoordinateFormatter.Comment($"PART: {drawingName}"));
|
||||||
|
|
||||||
for (var i = 0; i < features.Count; i++)
|
for (var i = 0; i < ordered.Count; i++)
|
||||||
{
|
{
|
||||||
var codes = features[i];
|
var (codes, isEtch) = ordered[i];
|
||||||
var featureNumber = i == 0
|
var featureNumber = i == 0
|
||||||
? _config.FeatureLineNumberStart
|
? _config.FeatureLineNumberStart
|
||||||
: 1000 + i + 1;
|
: 1000 + i + 1;
|
||||||
@@ -51,10 +54,11 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
FeatureNumber = featureNumber,
|
FeatureNumber = featureNumber,
|
||||||
PartName = drawingName,
|
PartName = drawingName,
|
||||||
IsFirstFeatureOfPart = false,
|
IsFirstFeatureOfPart = false,
|
||||||
IsLastFeatureOnSheet = i == features.Count - 1,
|
IsLastFeatureOnSheet = i == ordered.Count - 1,
|
||||||
IsSafetyHeadraise = false,
|
IsSafetyHeadraise = false,
|
||||||
IsExteriorFeature = false,
|
IsExteriorFeature = false,
|
||||||
LibraryFile = libraryFile,
|
IsEtch = isEtch,
|
||||||
|
LibraryFile = isEtch ? etchLibrary : cutLibrary,
|
||||||
CutDistance = cutDistance,
|
CutDistance = cutDistance,
|
||||||
SheetDiagonal = sheetDiagonal
|
SheetDiagonal = sheetDiagonal
|
||||||
};
|
};
|
||||||
@@ -62,8 +66,30 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
_featureWriter.Write(w, ctx);
|
_featureWriter.Write(w, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteLine("G0X0Y0");
|
w.WriteLine("G0 X0 Y0");
|
||||||
w.WriteLine($"M99(END OF {drawingName})");
|
w.WriteLine($"M99 (END OF {drawingName})");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static List<(List<ICode> codes, bool isEtch)> OrderFeatures(List<List<ICode>> features)
|
||||||
|
{
|
||||||
|
var result = new List<(List<ICode>, bool)>();
|
||||||
|
var etch = new List<List<ICode>>();
|
||||||
|
var cut = new List<List<ICode>>();
|
||||||
|
|
||||||
|
foreach (var f in features)
|
||||||
|
{
|
||||||
|
if (CincinnatiSheetWriter.IsFeatureEtch(f))
|
||||||
|
etch.Add(f);
|
||||||
|
else
|
||||||
|
cut.Add(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var f in etch)
|
||||||
|
result.Add((f, true));
|
||||||
|
foreach (var f in cut)
|
||||||
|
result.Add((f, false));
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace OpenNest.Posts.Cincinnati
|
namespace OpenNest.Posts.Cincinnati
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -153,16 +155,29 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
public G89Mode ProcessParameterMode { get; set; } = G89Mode.LibraryFile;
|
public G89Mode ProcessParameterMode { get; set; } = G89Mode.LibraryFile;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the default G89 library file path.
|
/// Gets or sets the default assist gas when Nest.AssistGas is empty.
|
||||||
/// Default: empty string
|
/// Default: "O2"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string DefaultLibraryFile { get; set; } = "";
|
public string DefaultAssistGas { get; set; } = "O2";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets whether to repeat G89 before each feature.
|
/// Gets or sets the gas used for etch operations.
|
||||||
/// Default: true
|
/// Independent of the cutting assist gas — etch typically requires a specific gas.
|
||||||
|
/// Default: "N2"
|
||||||
/// </summary>
|
/// </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>
|
/// <summary>
|
||||||
/// Gets or sets whether to use exact stop mode (G61).
|
/// Gets or sets whether to use exact stop mode (G61).
|
||||||
@@ -272,4 +287,18 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int SheetLengthVariable { get; set; } = 111;
|
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; } = "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,18 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
.Where(p => p.Parts.Count > 0)
|
.Where(p => p.Parts.Count > 0)
|
||||||
.ToList();
|
.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;
|
Dictionary<(int, long), int> partSubprograms = null;
|
||||||
List<(int subNum, string name, Program program)> subprogramEntries = 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 preamble = new CincinnatiPreambleWriter(Config);
|
||||||
var sheetWriter = new CincinnatiSheetWriter(Config, vars);
|
var sheetWriter = new CincinnatiSheetWriter(Config, vars);
|
||||||
|
|
||||||
// 5. Build material description from first plate
|
// 6. Build material description from first plate
|
||||||
var material = plates.FirstOrDefault()?.Material;
|
var material = firstPlate?.Material;
|
||||||
var materialDesc = material != null
|
var materialDesc = material != null
|
||||||
? $"{material.Name}{(string.IsNullOrEmpty(material.Grade) ? "" : $", {material.Grade}")}"
|
? $"{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);
|
using var writer = new StreamWriter(outputStream, Encoding.UTF8, 1024, leaveOpen: true);
|
||||||
|
|
||||||
// Main program
|
// Main program
|
||||||
preamble.WriteMainProgram(writer, nest.Name ?? "NEST", materialDesc, plates.Count);
|
preamble.WriteMainProgram(writer, nest.Name ?? "NEST", materialDesc, plates.Count, initialCutLibrary);
|
||||||
|
|
||||||
// Variable declaration subprogram
|
// Variable declaration subprogram
|
||||||
preamble.WriteVariableDeclaration(writer, vars);
|
preamble.WriteVariableDeclaration(writer, vars);
|
||||||
@@ -122,17 +133,18 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
// Sheet subprograms
|
// Sheet subprograms
|
||||||
for (var i = 0; i < plates.Count; i++)
|
for (var i = 0; i < plates.Count; i++)
|
||||||
{
|
{
|
||||||
|
var plate = plates[i];
|
||||||
var sheetIndex = i + 1;
|
var sheetIndex = i + 1;
|
||||||
var subNumber = Config.SheetSubprogramStart + i;
|
var subNumber = Config.SheetSubprogramStart + i;
|
||||||
sheetWriter.Write(writer, plates[i], nest.Name ?? "NEST", sheetIndex, subNumber,
|
var cutLibrary = resolver.ResolveCutLibrary(plate.Material?.Name ?? "", plate.Thickness, gas);
|
||||||
partSubprograms);
|
sheetWriter.Write(writer, plate, nest.Name ?? "NEST", sheetIndex, subNumber,
|
||||||
|
cutLibrary, etchLibrary, partSubprograms);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Part sub-programs (if enabled)
|
// Part sub-programs (if enabled)
|
||||||
if (subprogramEntries != null)
|
if (subprogramEntries != null)
|
||||||
{
|
{
|
||||||
var partSubWriter = new CincinnatiPartSubprogramWriter(Config);
|
var partSubWriter = new CincinnatiPartSubprogramWriter(Config);
|
||||||
var firstPlate = plates.FirstOrDefault();
|
|
||||||
var sheetDiagonal = firstPlate != null
|
var sheetDiagonal = firstPlate != null
|
||||||
? System.Math.Sqrt(firstPlate.Size.Width * firstPlate.Size.Width
|
? System.Math.Sqrt(firstPlate.Size.Width * firstPlate.Size.Width
|
||||||
+ firstPlate.Size.Length * firstPlate.Size.Length)
|
+ firstPlate.Size.Length * firstPlate.Size.Length)
|
||||||
@@ -141,7 +153,7 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
foreach (var (subNum, name, pgm) in subprogramEntries)
|
foreach (var (subNum, name, pgm) in subprogramEntries)
|
||||||
{
|
{
|
||||||
partSubWriter.Write(writer, pgm, name, subNum,
|
partSubWriter.Write(writer, pgm, name, subNum,
|
||||||
Config.DefaultLibraryFile ?? "", sheetDiagonal);
|
initialCutLibrary, etchLibrary, sheetDiagonal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ public sealed class CincinnatiPreambleWriter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes the main program header block.
|
/// Writes the main program header block.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void WriteMainProgram(TextWriter w, string nestName, string materialDescription, int sheetCount)
|
/// <param name="initialLibrary">Resolved G89 library file for the initial process setup.</param>
|
||||||
|
public void WriteMainProgram(TextWriter w, string nestName, string materialDescription,
|
||||||
|
int sheetCount, string initialLibrary)
|
||||||
{
|
{
|
||||||
w.WriteLine(CoordinateFormatter.Comment($"NEST {nestName}"));
|
w.WriteLine(CoordinateFormatter.Comment($"NEST {nestName}"));
|
||||||
w.WriteLine(CoordinateFormatter.Comment($"CONFIGURATION - {_config.ConfigurationName}"));
|
w.WriteLine(CoordinateFormatter.Comment($"CONFIGURATION - {_config.ConfigurationName}"));
|
||||||
@@ -39,8 +41,8 @@ public sealed class CincinnatiPreambleWriter
|
|||||||
|
|
||||||
w.WriteLine("M42");
|
w.WriteLine("M42");
|
||||||
|
|
||||||
if (_config.ProcessParameterMode == G89Mode.LibraryFile && !string.IsNullOrEmpty(_config.DefaultLibraryFile))
|
if (_config.ProcessParameterMode == G89Mode.LibraryFile && !string.IsNullOrEmpty(initialLibrary))
|
||||||
w.WriteLine($"G89 P {_config.DefaultLibraryFile}");
|
w.WriteLine($"G89 P {initialLibrary}");
|
||||||
|
|
||||||
w.WriteLine($"M98 P{_config.VariableDeclarationSubprogram} (Variable Declaration)");
|
w.WriteLine($"M98 P{_config.VariableDeclarationSubprogram} (Variable Declaration)");
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ public sealed class CincinnatiPreambleWriter
|
|||||||
for (var i = 1; i <= sheetCount; i++)
|
for (var i = 1; i <= sheetCount; i++)
|
||||||
{
|
{
|
||||||
var subNum = _config.SheetSubprogramStart + (i - 1);
|
var subNum = _config.SheetSubprogramStart + (i - 1);
|
||||||
w.WriteLine($"N{i}M98 P{subNum} (SHEET {i})");
|
w.WriteLine($"N{i} M98 P{subNum} (SHEET {i})");
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteLine("M42");
|
w.WriteLine("M42");
|
||||||
|
|||||||
@@ -30,11 +30,14 @@ public sealed class CincinnatiSheetWriter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes a complete sheet subprogram for the given plate.
|
/// Writes a complete sheet subprogram for the given plate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="cutLibrary">Resolved G89 library file for cut operations.</param>
|
||||||
|
/// <param name="etchLibrary">Resolved G89 library file for etch operations.</param>
|
||||||
/// <param name="partSubprograms">
|
/// <param name="partSubprograms">
|
||||||
/// Optional mapping of (drawingId, rotationKey) to sub-program number.
|
/// Optional mapping of (drawingId, rotationKey) to sub-program number.
|
||||||
/// When provided, non-cutoff parts are emitted as M98 calls instead of inline features.
|
/// When provided, non-cutoff parts are emitted as M98 calls instead of inline features.
|
||||||
/// </param>
|
/// </param>
|
||||||
public void Write(TextWriter w, Plate plate, string nestName, int sheetIndex, int subNumber,
|
public void Write(TextWriter w, Plate plate, string nestName, int sheetIndex, int subNumber,
|
||||||
|
string cutLibrary, string etchLibrary,
|
||||||
Dictionary<(int, long), int> partSubprograms = null)
|
Dictionary<(int, long), int> partSubprograms = null)
|
||||||
{
|
{
|
||||||
if (plate.Parts.Count == 0)
|
if (plate.Parts.Count == 0)
|
||||||
@@ -43,7 +46,6 @@ public sealed class CincinnatiSheetWriter
|
|||||||
var width = plate.Size.Width;
|
var width = plate.Size.Width;
|
||||||
var length = plate.Size.Length;
|
var length = plate.Size.Length;
|
||||||
var sheetDiagonal = System.Math.Sqrt(width * width + length * length);
|
var sheetDiagonal = System.Math.Sqrt(width * width + length * length);
|
||||||
var libraryFile = _config.DefaultLibraryFile ?? "";
|
|
||||||
var varDeclSub = _config.VariableDeclarationSubprogram;
|
var varDeclSub = _config.VariableDeclarationSubprogram;
|
||||||
var partCount = plate.Parts.Count(p => !p.BaseDrawing.IsCutOff);
|
var partCount = plate.Parts.Count(p => !p.BaseDrawing.IsCutOff);
|
||||||
|
|
||||||
@@ -55,20 +57,20 @@ public sealed class CincinnatiSheetWriter
|
|||||||
w.WriteLine($"( Layout {sheetIndex} )");
|
w.WriteLine($"( Layout {sheetIndex} )");
|
||||||
w.WriteLine($"( SHEET NAME = {_fmt.FormatCoord(length)} X {_fmt.FormatCoord(width)} )");
|
w.WriteLine($"( SHEET NAME = {_fmt.FormatCoord(length)} X {_fmt.FormatCoord(width)} )");
|
||||||
w.WriteLine($"( Total parts on sheet = {partCount} )");
|
w.WriteLine($"( Total parts on sheet = {partCount} )");
|
||||||
w.WriteLine($"#{_config.SheetWidthVariable}={_fmt.FormatCoord(width)}(SHEET WIDTH FOR CUTOFFS)");
|
w.WriteLine($"#{_config.SheetWidthVariable}={_fmt.FormatCoord(width)} (SHEET WIDTH FOR CUTOFFS)");
|
||||||
w.WriteLine($"#{_config.SheetLengthVariable}={_fmt.FormatCoord(length)}(SHEET LENGTH FOR CUTOFFS)");
|
w.WriteLine($"#{_config.SheetLengthVariable}={_fmt.FormatCoord(length)} (SHEET LENGTH FOR CUTOFFS)");
|
||||||
|
|
||||||
// 2. Coordinate setup
|
// 2. Coordinate setup
|
||||||
w.WriteLine("M42");
|
w.WriteLine("M42");
|
||||||
w.WriteLine("N10000");
|
w.WriteLine("N10000");
|
||||||
w.WriteLine("G92X#5021Y#5022");
|
w.WriteLine("G92 X#5021 Y#5022");
|
||||||
if (!string.IsNullOrEmpty(libraryFile))
|
if (!string.IsNullOrEmpty(cutLibrary))
|
||||||
w.WriteLine($"G89 P {libraryFile}");
|
w.WriteLine($"G89 P {cutLibrary}");
|
||||||
w.WriteLine($"M98 P{varDeclSub} (Variable Declaration)");
|
w.WriteLine($"M98 P{varDeclSub} (Variable Declaration)");
|
||||||
w.WriteLine("G90");
|
w.WriteLine("G90");
|
||||||
w.WriteLine("M47(CPT)");
|
w.WriteLine("M47");
|
||||||
if (!string.IsNullOrEmpty(libraryFile))
|
if (!string.IsNullOrEmpty(cutLibrary))
|
||||||
w.WriteLine($"G89 P {libraryFile}");
|
w.WriteLine($"G89 P {cutLibrary}");
|
||||||
w.WriteLine("GOTO1( Goto Feature )");
|
w.WriteLine("GOTO1( Goto Feature )");
|
||||||
|
|
||||||
// 3. Order parts: non-cutoff sorted by Bottom then Left, cutoffs last
|
// 3. Order parts: non-cutoff sorted by Bottom then Left, cutoffs last
|
||||||
@@ -86,20 +88,20 @@ public sealed class CincinnatiSheetWriter
|
|||||||
|
|
||||||
// 4. Emit parts
|
// 4. Emit parts
|
||||||
if (partSubprograms != null)
|
if (partSubprograms != null)
|
||||||
WritePartsWithSubprograms(w, allParts, libraryFile, sheetDiagonal, partSubprograms);
|
WritePartsWithSubprograms(w, allParts, cutLibrary, etchLibrary, sheetDiagonal, partSubprograms);
|
||||||
else
|
else
|
||||||
WritePartsInline(w, allParts, libraryFile, sheetDiagonal);
|
WritePartsInline(w, allParts, cutLibrary, etchLibrary, sheetDiagonal);
|
||||||
|
|
||||||
// 5. Footer
|
// 5. Footer
|
||||||
w.WriteLine("M42");
|
w.WriteLine("M42");
|
||||||
w.WriteLine("G0X0Y0");
|
w.WriteLine("G0 X0 Y0");
|
||||||
if (_config.PalletExchange != PalletMode.None)
|
if (_config.PalletExchange != PalletMode.None)
|
||||||
w.WriteLine($"N{sheetIndex + 1}M50");
|
w.WriteLine($"N{sheetIndex + 1} M50");
|
||||||
w.WriteLine($"M99(END OF {nestName}.{sheetIndex:D3})");
|
w.WriteLine($"M99 (END OF {nestName}.{sheetIndex:D3})");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WritePartsWithSubprograms(TextWriter w, List<Part> allParts,
|
private void WritePartsWithSubprograms(TextWriter w, List<Part> allParts,
|
||||||
string libraryFile, double sheetDiagonal,
|
string cutLibrary, string etchLibrary, double sheetDiagonal,
|
||||||
Dictionary<(int, long), int> partSubprograms)
|
Dictionary<(int, long), int> partSubprograms)
|
||||||
{
|
{
|
||||||
var lastPartName = "";
|
var lastPartName = "";
|
||||||
@@ -126,26 +128,28 @@ public sealed class CincinnatiSheetWriter
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Inline features for cutoffs or parts without sub-programs
|
// Inline features for cutoffs or parts without sub-programs
|
||||||
var features = SplitPartFeatures(part);
|
var features = SplitAndOrderFeatures(part);
|
||||||
for (var f = 0; f < features.Count; f++)
|
for (var f = 0; f < features.Count; f++)
|
||||||
{
|
{
|
||||||
|
var (codes, isEtch) = features[f];
|
||||||
var featureNumber = featureIndex == 0
|
var featureNumber = featureIndex == 0
|
||||||
? _config.FeatureLineNumberStart
|
? _config.FeatureLineNumberStart
|
||||||
: 1000 + featureIndex + 1;
|
: 1000 + featureIndex + 1;
|
||||||
|
|
||||||
var isLastFeature = isLastPart && f == features.Count - 1;
|
var isLastFeature = isLastPart && f == features.Count - 1;
|
||||||
var cutDistance = ComputeCutDistance(features[f]);
|
var cutDistance = ComputeCutDistance(codes);
|
||||||
|
|
||||||
var ctx = new FeatureContext
|
var ctx = new FeatureContext
|
||||||
{
|
{
|
||||||
Codes = features[f],
|
Codes = codes,
|
||||||
FeatureNumber = featureNumber,
|
FeatureNumber = featureNumber,
|
||||||
PartName = partName,
|
PartName = partName,
|
||||||
IsFirstFeatureOfPart = isNewPart && f == 0,
|
IsFirstFeatureOfPart = isNewPart && f == 0,
|
||||||
IsLastFeatureOnSheet = isLastFeature,
|
IsLastFeatureOnSheet = isLastFeature,
|
||||||
IsSafetyHeadraise = isSafetyHeadraise && f == 0,
|
IsSafetyHeadraise = isSafetyHeadraise && f == 0,
|
||||||
IsExteriorFeature = false,
|
IsExteriorFeature = false,
|
||||||
LibraryFile = libraryFile,
|
IsEtch = isEtch,
|
||||||
|
LibraryFile = isEtch ? etchLibrary : cutLibrary,
|
||||||
CutDistance = cutDistance,
|
CutDistance = cutDistance,
|
||||||
SheetDiagonal = sheetDiagonal
|
SheetDiagonal = sheetDiagonal
|
||||||
};
|
};
|
||||||
@@ -164,7 +168,7 @@ public sealed class CincinnatiSheetWriter
|
|||||||
{
|
{
|
||||||
// Safety headraise before rapid to new part
|
// Safety headraise before rapid to new part
|
||||||
if (isSafetyHeadraise && _config.SafetyHeadraiseDistance.HasValue)
|
if (isSafetyHeadraise && _config.SafetyHeadraiseDistance.HasValue)
|
||||||
w.WriteLine($"M47 P{_config.SafetyHeadraiseDistance.Value}(Safety Headraise)");
|
w.WriteLine($"M47 P{_config.SafetyHeadraiseDistance.Value} (Safety Headraise)");
|
||||||
|
|
||||||
// Rapid to part position (bounding box lower-left)
|
// Rapid to part position (bounding box lower-left)
|
||||||
var featureNumber = featureIndex == 0
|
var featureNumber = featureIndex == 0
|
||||||
@@ -173,21 +177,21 @@ public sealed class CincinnatiSheetWriter
|
|||||||
|
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
if (_config.UseLineNumbers)
|
if (_config.UseLineNumbers)
|
||||||
sb.Append($"N{featureNumber}");
|
sb.Append($"N{featureNumber} ");
|
||||||
sb.Append($"G0X{_fmt.FormatCoord(part.Left)}Y{_fmt.FormatCoord(part.Bottom)}");
|
sb.Append($"G0 X{_fmt.FormatCoord(part.Left)} Y{_fmt.FormatCoord(part.Bottom)}");
|
||||||
w.WriteLine(sb.ToString());
|
w.WriteLine(sb.ToString());
|
||||||
|
|
||||||
// Part name comment
|
// Part name comment
|
||||||
w.WriteLine(CoordinateFormatter.Comment($"PART: {partName}"));
|
w.WriteLine(CoordinateFormatter.Comment($"PART: {partName}"));
|
||||||
|
|
||||||
// Set local coordinate system at part position
|
// Set local coordinate system at part position
|
||||||
w.WriteLine("G92X0Y0");
|
w.WriteLine("G92 X0 Y0");
|
||||||
|
|
||||||
// Call part sub-program
|
// Call part sub-program
|
||||||
w.WriteLine($"M98P{subNum}({partName})");
|
w.WriteLine($"M98 P{subNum} ({partName})");
|
||||||
|
|
||||||
// Restore sheet coordinate system
|
// Restore sheet coordinate system
|
||||||
w.WriteLine($"G92X{_fmt.FormatCoord(part.Left)}Y{_fmt.FormatCoord(part.Bottom)}");
|
w.WriteLine($"G92 X{_fmt.FormatCoord(part.Left)} Y{_fmt.FormatCoord(part.Bottom)}");
|
||||||
|
|
||||||
// Head raise (unless last part on sheet)
|
// Head raise (unless last part on sheet)
|
||||||
if (!isLastPart)
|
if (!isLastPart)
|
||||||
@@ -195,36 +199,22 @@ public sealed class CincinnatiSheetWriter
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void WritePartsInline(TextWriter w, List<Part> allParts,
|
private void WritePartsInline(TextWriter w, List<Part> allParts,
|
||||||
string libraryFile, double sheetDiagonal)
|
string cutLibrary, string etchLibrary, double sheetDiagonal)
|
||||||
{
|
{
|
||||||
// Multi-contour splitting
|
// Split and classify features, ordering etch before cut per part
|
||||||
var features = new List<(Part part, List<ICode> codes)>();
|
var features = new List<(Part part, List<ICode> codes, bool isEtch)>();
|
||||||
foreach (var part in allParts)
|
foreach (var part in allParts)
|
||||||
{
|
{
|
||||||
List<ICode> current = null;
|
var partFeatures = SplitAndOrderFeatures(part);
|
||||||
foreach (var code in part.Program.Codes)
|
foreach (var (codes, isEtch) in partFeatures)
|
||||||
{
|
features.Add((part, codes, isEtch));
|
||||||
if (code is RapidMove)
|
|
||||||
{
|
|
||||||
if (current != null)
|
|
||||||
features.Add((part, current));
|
|
||||||
current = new List<ICode> { code };
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
current ??= new List<ICode>();
|
|
||||||
current.Add(code);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (current != null && current.Count > 0)
|
|
||||||
features.Add((part, current));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit features
|
// Emit features
|
||||||
var lastPartName = "";
|
var lastPartName = "";
|
||||||
for (var i = 0; i < features.Count; i++)
|
for (var i = 0; i < features.Count; i++)
|
||||||
{
|
{
|
||||||
var (part, codes) = features[i];
|
var (part, codes, isEtch) = features[i];
|
||||||
var partName = part.BaseDrawing.Name;
|
var partName = part.BaseDrawing.Name;
|
||||||
var isFirstFeatureOfPart = partName != lastPartName;
|
var isFirstFeatureOfPart = partName != lastPartName;
|
||||||
var isSafetyHeadraise = partName != lastPartName && lastPartName != "";
|
var isSafetyHeadraise = partName != lastPartName && lastPartName != "";
|
||||||
@@ -245,7 +235,8 @@ public sealed class CincinnatiSheetWriter
|
|||||||
IsLastFeatureOnSheet = isLastFeature,
|
IsLastFeatureOnSheet = isLastFeature,
|
||||||
IsSafetyHeadraise = isSafetyHeadraise,
|
IsSafetyHeadraise = isSafetyHeadraise,
|
||||||
IsExteriorFeature = false,
|
IsExteriorFeature = false,
|
||||||
LibraryFile = libraryFile,
|
IsEtch = isEtch,
|
||||||
|
LibraryFile = isEtch ? etchLibrary : cutLibrary,
|
||||||
CutDistance = cutDistance,
|
CutDistance = cutDistance,
|
||||||
SheetDiagonal = sheetDiagonal
|
SheetDiagonal = sheetDiagonal
|
||||||
};
|
};
|
||||||
@@ -255,9 +246,14 @@ public sealed class CincinnatiSheetWriter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<List<ICode>> SplitPartFeatures(Part part)
|
/// <summary>
|
||||||
|
/// Splits a part's program into features (by rapids), classifies each as etch or cut,
|
||||||
|
/// and orders etch features before cut features.
|
||||||
|
/// </summary>
|
||||||
|
public static List<(List<ICode> codes, bool isEtch)> SplitAndOrderFeatures(Part part)
|
||||||
{
|
{
|
||||||
var features = new List<List<ICode>>();
|
var etchFeatures = new List<List<ICode>>();
|
||||||
|
var cutFeatures = new List<List<ICode>>();
|
||||||
List<ICode> current = null;
|
List<ICode> current = null;
|
||||||
|
|
||||||
foreach (var code in part.Program.Codes)
|
foreach (var code in part.Program.Codes)
|
||||||
@@ -265,7 +261,7 @@ public sealed class CincinnatiSheetWriter
|
|||||||
if (code is RapidMove)
|
if (code is RapidMove)
|
||||||
{
|
{
|
||||||
if (current != null)
|
if (current != null)
|
||||||
features.Add(current);
|
ClassifyAndAdd(current, etchFeatures, cutFeatures);
|
||||||
current = new List<ICode> { code };
|
current = new List<ICode> { code };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -276,9 +272,40 @@ public sealed class CincinnatiSheetWriter
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (current != null && current.Count > 0)
|
if (current != null && current.Count > 0)
|
||||||
features.Add(current);
|
ClassifyAndAdd(current, etchFeatures, cutFeatures);
|
||||||
|
|
||||||
return features;
|
// Etch features first, then cut features
|
||||||
|
var result = new List<(List<ICode>, bool)>();
|
||||||
|
foreach (var f in etchFeatures)
|
||||||
|
result.Add((f, true));
|
||||||
|
foreach (var f in cutFeatures)
|
||||||
|
result.Add((f, false));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ClassifyAndAdd(List<ICode> codes,
|
||||||
|
List<List<ICode>> etchFeatures, List<List<ICode>> cutFeatures)
|
||||||
|
{
|
||||||
|
if (IsFeatureEtch(codes))
|
||||||
|
etchFeatures.Add(codes);
|
||||||
|
else
|
||||||
|
cutFeatures.Add(codes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A feature is etch if any non-rapid move has LayerType.Scribe.
|
||||||
|
/// </summary>
|
||||||
|
public static bool IsFeatureEtch(List<ICode> codes)
|
||||||
|
{
|
||||||
|
foreach (var code in codes)
|
||||||
|
{
|
||||||
|
if (code is LinearMove linear && linear.Layer == LayerType.Scribe)
|
||||||
|
return true;
|
||||||
|
if (code is ArcMove arc && arc.Layer == LayerType.Scribe)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double ComputeCutDistance(List<ICode> codes)
|
private static double ComputeCutDistance(List<ICode> codes)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,9 +13,7 @@ public class CincinnatiFeatureWriterTests
|
|||||||
UseAntiDive = true,
|
UseAntiDive = true,
|
||||||
KerfCompensation = KerfMode.ControllerSide,
|
KerfCompensation = KerfMode.ControllerSide,
|
||||||
DefaultKerfSide = KerfSide.Left,
|
DefaultKerfSide = KerfSide.Left,
|
||||||
RepeatG89BeforeEachFeature = true,
|
|
||||||
ProcessParameterMode = G89Mode.LibraryFile,
|
ProcessParameterMode = G89Mode.LibraryFile,
|
||||||
DefaultLibraryFile = "MILD10",
|
|
||||||
InteriorM47 = M47Mode.Always,
|
InteriorM47 = M47Mode.Always,
|
||||||
ExteriorM47 = M47Mode.Always,
|
ExteriorM47 = M47Mode.Always,
|
||||||
UseSpeedGas = false,
|
UseSpeedGas = false,
|
||||||
@@ -58,7 +56,7 @@ public class CincinnatiFeatureWriterTests
|
|||||||
var output = WriteFeature(config, ctx);
|
var output = WriteFeature(config, ctx);
|
||||||
var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
|
var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
Assert.StartsWith("N1G0X13.401Y57.4895", lines[0]);
|
Assert.StartsWith("N1 G0 X13.401 Y57.4895", lines[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -70,7 +68,7 @@ public class CincinnatiFeatureWriterTests
|
|||||||
var output = WriteFeature(config, ctx);
|
var output = WriteFeature(config, ctx);
|
||||||
var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
|
var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
Assert.StartsWith("G0X13.401Y57.4895", lines[0]);
|
Assert.StartsWith("G0 X13.401 Y57.4895", lines[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -260,8 +258,8 @@ public class CincinnatiFeatureWriterTests
|
|||||||
var cwOutput = WriteFeature(config, SimpleContext(cwCodes));
|
var cwOutput = WriteFeature(config, SimpleContext(cwCodes));
|
||||||
var ccwOutput = WriteFeature(config, SimpleContext(ccwCodes));
|
var ccwOutput = WriteFeature(config, SimpleContext(ccwCodes));
|
||||||
|
|
||||||
Assert.Contains("G2X", cwOutput);
|
Assert.Contains("G2 X", cwOutput);
|
||||||
Assert.Contains("G3X", ccwOutput);
|
Assert.Contains("G3 X", ccwOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -289,12 +287,10 @@ public class CincinnatiFeatureWriterTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void G89_EmittedWhenRepeatEnabled()
|
public void G89_EmittedWithLibraryFile()
|
||||||
{
|
{
|
||||||
var config = DefaultConfig();
|
var config = DefaultConfig();
|
||||||
config.RepeatG89BeforeEachFeature = true;
|
|
||||||
config.ProcessParameterMode = G89Mode.LibraryFile;
|
config.ProcessParameterMode = G89Mode.LibraryFile;
|
||||||
config.DefaultLibraryFile = "MILD10";
|
|
||||||
var ctx = SimpleContext();
|
var ctx = SimpleContext();
|
||||||
ctx.LibraryFile = "MILD10";
|
ctx.LibraryFile = "MILD10";
|
||||||
ctx.CutDistance = 18.0;
|
ctx.CutDistance = 18.0;
|
||||||
@@ -305,14 +301,65 @@ public class CincinnatiFeatureWriterTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void G89_NotEmittedWhenRepeatDisabled()
|
public void G89_WarningEmittedWhenNoLibrary()
|
||||||
{
|
{
|
||||||
var config = DefaultConfig();
|
var config = DefaultConfig();
|
||||||
config.RepeatG89BeforeEachFeature = false;
|
config.ProcessParameterMode = G89Mode.LibraryFile;
|
||||||
var ctx = SimpleContext();
|
var ctx = SimpleContext();
|
||||||
|
ctx.LibraryFile = "";
|
||||||
var output = WriteFeature(config, ctx);
|
var output = WriteFeature(config, ctx);
|
||||||
|
|
||||||
Assert.DoesNotContain("G89", output);
|
Assert.Contains("WARNING: No library found", output);
|
||||||
|
Assert.DoesNotContain("G89 P", output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Etch_UsesG85InsteadOfG84()
|
||||||
|
{
|
||||||
|
var config = DefaultConfig();
|
||||||
|
var ctx = SimpleContext();
|
||||||
|
ctx.IsEtch = true;
|
||||||
|
ctx.LibraryFile = "EtchN2.lib";
|
||||||
|
var output = WriteFeature(config, ctx);
|
||||||
|
|
||||||
|
Assert.Contains("G85", output);
|
||||||
|
Assert.DoesNotContain("G84", output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Etch_SkipsKerfCompensation()
|
||||||
|
{
|
||||||
|
var config = DefaultConfig();
|
||||||
|
config.KerfCompensation = KerfMode.ControllerSide;
|
||||||
|
var ctx = SimpleContext();
|
||||||
|
ctx.IsEtch = true;
|
||||||
|
ctx.LibraryFile = "EtchN2.lib";
|
||||||
|
var output = WriteFeature(config, ctx);
|
||||||
|
|
||||||
|
Assert.DoesNotContain("G41", output);
|
||||||
|
Assert.DoesNotContain("G42", output);
|
||||||
|
Assert.DoesNotContain("G40", output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Etch_AllMovesUseProcessFeedrate()
|
||||||
|
{
|
||||||
|
var config = DefaultConfig();
|
||||||
|
config.KerfCompensation = KerfMode.PreApplied;
|
||||||
|
var codes = new List<ICode>
|
||||||
|
{
|
||||||
|
new RapidMove(1.0, 1.0),
|
||||||
|
new LinearMove(2.0, 1.0) { Layer = LayerType.Leadin },
|
||||||
|
new LinearMove(3.0, 1.0) { Layer = LayerType.Cut }
|
||||||
|
};
|
||||||
|
var ctx = SimpleContext(codes);
|
||||||
|
ctx.IsEtch = true;
|
||||||
|
ctx.LibraryFile = "EtchN2.lib";
|
||||||
|
var output = WriteFeature(config, ctx);
|
||||||
|
|
||||||
|
// Should use #148 for all moves, not #126 for lead-in
|
||||||
|
Assert.DoesNotContain("F#126", output);
|
||||||
|
Assert.Contains("F#148", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -378,7 +425,7 @@ public class CincinnatiFeatureWriterTests
|
|||||||
ctx.IsLastFeatureOnSheet = false;
|
ctx.IsLastFeatureOnSheet = false;
|
||||||
var output = WriteFeature(config, ctx);
|
var output = WriteFeature(config, ctx);
|
||||||
|
|
||||||
Assert.Contains("M47 P2000(Safety Headraise)", output);
|
Assert.Contains("M47 P2000 (Safety Headraise)", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -404,7 +451,7 @@ public class CincinnatiFeatureWriterTests
|
|||||||
var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
|
var lines = output.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
// Find indices of key lines
|
// Find indices of key lines
|
||||||
var rapidIdx = Array.FindIndex(lines, l => l.Contains("G0X"));
|
var rapidIdx = Array.FindIndex(lines, l => l.Contains("G0 X"));
|
||||||
var partIdx = Array.FindIndex(lines, l => l.Contains("PART:"));
|
var partIdx = Array.FindIndex(lines, l => l.Contains("PART:"));
|
||||||
var g89Idx = Array.FindIndex(lines, l => l.Contains("G89"));
|
var g89Idx = Array.FindIndex(lines, l => l.Contains("G89"));
|
||||||
var g84Idx = Array.FindIndex(lines, l => l.Contains("G84"));
|
var g84Idx = Array.FindIndex(lines, l => l.Contains("G84"));
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ public class CincinnatiPostProcessorTests
|
|||||||
var config = new CincinnatiPostConfig
|
var config = new CincinnatiPostConfig
|
||||||
{
|
{
|
||||||
ConfigurationName = "CL940",
|
ConfigurationName = "CL940",
|
||||||
DefaultLibraryFile = "MS135N2PANEL.lib",
|
|
||||||
PostedAccuracy = 4
|
PostedAccuracy = 4
|
||||||
};
|
};
|
||||||
var post = new CincinnatiPostProcessor(config);
|
var post = new CincinnatiPostProcessor(config);
|
||||||
@@ -72,7 +71,7 @@ public class CincinnatiPostProcessorTests
|
|||||||
var output = Encoding.UTF8.GetString(ms.ToArray());
|
var output = Encoding.UTF8.GetString(ms.ToArray());
|
||||||
|
|
||||||
// Should only have one sheet subprogram call in main
|
// Should only have one sheet subprogram call in main
|
||||||
Assert.Contains("N1M98 P101 (SHEET 1)", output);
|
Assert.Contains("N1 M98 P101 (SHEET 1)", output);
|
||||||
Assert.DoesNotContain("SHEET 2", output);
|
Assert.DoesNotContain("SHEET 2", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,10 +103,19 @@ public class CincinnatiPostProcessorTests
|
|||||||
var config = new CincinnatiPostConfig
|
var config = new CincinnatiPostConfig
|
||||||
{
|
{
|
||||||
ConfigurationName = "CL940_CORONA",
|
ConfigurationName = "CL940_CORONA",
|
||||||
DefaultLibraryFile = "MS135N2PANEL.lib",
|
DefaultAssistGas = "N2",
|
||||||
|
DefaultEtchGas = "N2",
|
||||||
PostedUnits = Units.Inches,
|
PostedUnits = Units.Inches,
|
||||||
KerfCompensation = KerfMode.ControllerSide,
|
KerfCompensation = KerfMode.ControllerSide,
|
||||||
UseAntiDive = true
|
UseAntiDive = true,
|
||||||
|
MaterialLibraries = new()
|
||||||
|
{
|
||||||
|
new MaterialLibraryEntry { Material = "Mild Steel", Thickness = 0.135, Gas = "N2", Library = "MS135N2PANEL.lib" }
|
||||||
|
},
|
||||||
|
EtchLibraries = new()
|
||||||
|
{
|
||||||
|
new EtchLibraryEntry { Gas = "N2", Library = "EtchN2.lib" }
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var opts = new JsonSerializerOptions
|
var opts = new JsonSerializerOptions
|
||||||
@@ -119,10 +127,15 @@ public class CincinnatiPostProcessorTests
|
|||||||
var deserialized = JsonSerializer.Deserialize<CincinnatiPostConfig>(json, opts);
|
var deserialized = JsonSerializer.Deserialize<CincinnatiPostConfig>(json, opts);
|
||||||
|
|
||||||
Assert.Equal("CL940_CORONA", deserialized.ConfigurationName);
|
Assert.Equal("CL940_CORONA", deserialized.ConfigurationName);
|
||||||
Assert.Equal("MS135N2PANEL.lib", deserialized.DefaultLibraryFile);
|
Assert.Equal("N2", deserialized.DefaultAssistGas);
|
||||||
|
Assert.Equal("N2", deserialized.DefaultEtchGas);
|
||||||
Assert.Equal(Units.Inches, deserialized.PostedUnits);
|
Assert.Equal(Units.Inches, deserialized.PostedUnits);
|
||||||
Assert.Equal(KerfMode.ControllerSide, deserialized.KerfCompensation);
|
Assert.Equal(KerfMode.ControllerSide, deserialized.KerfCompensation);
|
||||||
Assert.True(deserialized.UseAntiDive);
|
Assert.True(deserialized.UseAntiDive);
|
||||||
|
Assert.Single(deserialized.MaterialLibraries);
|
||||||
|
Assert.Equal("MS135N2PANEL.lib", deserialized.MaterialLibraries[0].Library);
|
||||||
|
Assert.Single(deserialized.EtchLibraries);
|
||||||
|
Assert.Equal("EtchN2.lib", deserialized.EtchLibraries[0].Library);
|
||||||
|
|
||||||
// Enums serialize as strings
|
// Enums serialize as strings
|
||||||
Assert.Contains("\"Inches\"", json);
|
Assert.Contains("\"Inches\"", json);
|
||||||
@@ -157,21 +170,21 @@ public class CincinnatiPostProcessorTests
|
|||||||
var output = Encoding.UTF8.GetString(ms.ToArray());
|
var output = Encoding.UTF8.GetString(ms.ToArray());
|
||||||
|
|
||||||
// Sheet should contain M98 call to part sub-program
|
// Sheet should contain M98 call to part sub-program
|
||||||
Assert.Contains("M98P200", output);
|
Assert.Contains("M98 P200", output);
|
||||||
|
|
||||||
// Should have G92 for local coordinate positioning
|
// Should have G92 for local coordinate positioning
|
||||||
Assert.Contains("G92X0Y0", output);
|
Assert.Contains("G92 X0 Y0", output);
|
||||||
|
|
||||||
// Part sub-program definition
|
// Part sub-program definition
|
||||||
Assert.Contains(":200", output);
|
Assert.Contains(":200", output);
|
||||||
Assert.Contains("G84", output);
|
Assert.Contains("G84", output);
|
||||||
|
|
||||||
// Sub-program ends with G0X0Y0 and M99
|
// Sub-program ends with G0 X0 Y0 and M99
|
||||||
Assert.Contains("G0X0Y0", output);
|
Assert.Contains("G0 X0 Y0", output);
|
||||||
Assert.Contains("M99(END OF Square)", output);
|
Assert.Contains("M99 (END OF Square)", output);
|
||||||
|
|
||||||
// G92 restore after M98 call
|
// G92 restore after M98 call
|
||||||
Assert.Contains("G92X", output);
|
Assert.Contains("G92 X", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -198,7 +211,7 @@ public class CincinnatiPostProcessorTests
|
|||||||
var output = Encoding.UTF8.GetString(ms.ToArray());
|
var output = Encoding.UTF8.GetString(ms.ToArray());
|
||||||
|
|
||||||
// Both parts should call the same sub-program
|
// Both parts should call the same sub-program
|
||||||
var m98Count = System.Text.RegularExpressions.Regex.Matches(output, "M98P200").Count;
|
var m98Count = System.Text.RegularExpressions.Regex.Matches(output, @"M98 P200\b").Count;
|
||||||
Assert.Equal(2, m98Count);
|
Assert.Equal(2, m98Count);
|
||||||
|
|
||||||
// Only one sub-program definition
|
// Only one sub-program definition
|
||||||
@@ -238,8 +251,8 @@ public class CincinnatiPostProcessorTests
|
|||||||
// Should have two different sub-programs
|
// Should have two different sub-programs
|
||||||
Assert.Contains(":200", output);
|
Assert.Contains(":200", output);
|
||||||
Assert.Contains(":201", output);
|
Assert.Contains(":201", output);
|
||||||
Assert.Contains("M98P200", output);
|
Assert.Contains("M98 P200", output);
|
||||||
Assert.Contains("M98P201", output);
|
Assert.Contains("M98 P201", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -268,7 +281,7 @@ public class CincinnatiPostProcessorTests
|
|||||||
var output = Encoding.UTF8.GetString(ms.ToArray());
|
var output = Encoding.UTF8.GetString(ms.ToArray());
|
||||||
|
|
||||||
// Regular part uses sub-program
|
// Regular part uses sub-program
|
||||||
Assert.Contains("M98P200", output);
|
Assert.Contains("M98 P200", output);
|
||||||
Assert.Contains(":200", output);
|
Assert.Contains(":200", output);
|
||||||
|
|
||||||
// Cutoff should NOT have its own sub-program
|
// Cutoff should NOT have its own sub-program
|
||||||
|
|||||||
@@ -13,14 +13,13 @@ public class CincinnatiPreambleWriterTests
|
|||||||
var config = new CincinnatiPostConfig
|
var config = new CincinnatiPostConfig
|
||||||
{
|
{
|
||||||
ConfigurationName = "CL940",
|
ConfigurationName = "CL940",
|
||||||
PostedUnits = Units.Inches,
|
PostedUnits = Units.Inches
|
||||||
DefaultLibraryFile = "MS135N2PANEL.lib"
|
|
||||||
};
|
};
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
using var sw = new StringWriter(sb);
|
using var sw = new StringWriter(sb);
|
||||||
var writer = new CincinnatiPreambleWriter(config);
|
var writer = new CincinnatiPreambleWriter(config);
|
||||||
|
|
||||||
writer.WriteMainProgram(sw, "TestNest", "Mild Steel, 10GA", 2);
|
writer.WriteMainProgram(sw, "TestNest", "Mild Steel, 10GA", 2, "MS135N2PANEL.lib");
|
||||||
|
|
||||||
var output = sb.ToString();
|
var output = sb.ToString();
|
||||||
Assert.Contains("( NEST TestNest )", output);
|
Assert.Contains("( NEST TestNest )", output);
|
||||||
@@ -30,8 +29,8 @@ public class CincinnatiPreambleWriterTests
|
|||||||
Assert.Contains("G89 P MS135N2PANEL.lib", output);
|
Assert.Contains("G89 P MS135N2PANEL.lib", output);
|
||||||
Assert.Contains("M98 P100 (Variable Declaration)", output);
|
Assert.Contains("M98 P100 (Variable Declaration)", output);
|
||||||
Assert.Contains("GOTO1 (GOTO SHEET NUMBER)", output);
|
Assert.Contains("GOTO1 (GOTO SHEET NUMBER)", output);
|
||||||
Assert.Contains("N1M98 P101 (SHEET 1)", output);
|
Assert.Contains("N1 M98 P101 (SHEET 1)", output);
|
||||||
Assert.Contains("N2M98 P102 (SHEET 2)", output);
|
Assert.Contains("N2 M98 P102 (SHEET 2)", output);
|
||||||
Assert.Contains("M30 (END OF MAIN)", output);
|
Assert.Contains("M30 (END OF MAIN)", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +42,7 @@ public class CincinnatiPreambleWriterTests
|
|||||||
using var sw = new StringWriter(sb);
|
using var sw = new StringWriter(sb);
|
||||||
var writer = new CincinnatiPreambleWriter(config);
|
var writer = new CincinnatiPreambleWriter(config);
|
||||||
|
|
||||||
writer.WriteMainProgram(sw, "Test", "", 1);
|
writer.WriteMainProgram(sw, "Test", "", 1, "");
|
||||||
|
|
||||||
Assert.Contains("G21", sb.ToString());
|
Assert.Contains("G21", sb.ToString());
|
||||||
}
|
}
|
||||||
@@ -56,7 +55,7 @@ public class CincinnatiPreambleWriterTests
|
|||||||
using var sw = new StringWriter(sb);
|
using var sw = new StringWriter(sb);
|
||||||
var writer = new CincinnatiPreambleWriter(config);
|
var writer = new CincinnatiPreambleWriter(config);
|
||||||
|
|
||||||
writer.WriteMainProgram(sw, "Test", "", 1);
|
writer.WriteMainProgram(sw, "Test", "", 1, "");
|
||||||
|
|
||||||
Assert.Contains("G61", sb.ToString());
|
Assert.Contains("G61", sb.ToString());
|
||||||
}
|
}
|
||||||
@@ -69,7 +68,7 @@ public class CincinnatiPreambleWriterTests
|
|||||||
using var sw = new StringWriter(sb);
|
using var sw = new StringWriter(sb);
|
||||||
var writer = new CincinnatiPreambleWriter(config);
|
var writer = new CincinnatiPreambleWriter(config);
|
||||||
|
|
||||||
writer.WriteMainProgram(sw, "Test", "", 1);
|
writer.WriteMainProgram(sw, "Test", "", 1, "");
|
||||||
|
|
||||||
Assert.DoesNotContain("G61", sb.ToString());
|
Assert.DoesNotContain("G61", sb.ToString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -14,7 +15,6 @@ public class CincinnatiSheetWriterTests
|
|||||||
{
|
{
|
||||||
var config = new CincinnatiPostConfig
|
var config = new CincinnatiPostConfig
|
||||||
{
|
{
|
||||||
DefaultLibraryFile = "MS135N2PANEL.lib",
|
|
||||||
PostedAccuracy = 4
|
PostedAccuracy = 4
|
||||||
};
|
};
|
||||||
var plate = new Plate(48.0, 96.0);
|
var plate = new Plate(48.0, 96.0);
|
||||||
@@ -24,14 +24,15 @@ public class CincinnatiSheetWriterTests
|
|||||||
using var sw = new StringWriter(sb);
|
using var sw = new StringWriter(sb);
|
||||||
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
||||||
|
|
||||||
sheetWriter.Write(sw, plate, "TestNest", 1, 101);
|
sheetWriter.Write(sw, plate, "TestNest", 1, 101, "MS135N2PANEL.lib", "EtchN2.lib");
|
||||||
|
|
||||||
var output = sb.ToString();
|
var output = sb.ToString();
|
||||||
Assert.Contains(":101", output);
|
Assert.Contains(":101", output);
|
||||||
Assert.Contains("( Sheet 1 )", output);
|
Assert.Contains("( Sheet 1 )", output);
|
||||||
Assert.Contains("#110=", output);
|
Assert.Contains("#110=", output);
|
||||||
Assert.Contains("#111=", output);
|
Assert.Contains("#111=", output);
|
||||||
Assert.Contains("G92X#5021Y#5022", output);
|
Assert.Contains("G92 X#5021 Y#5022", output);
|
||||||
|
Assert.Contains("G89 P MS135N2PANEL.lib", output);
|
||||||
Assert.Contains("M99", output);
|
Assert.Contains("M99", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,11 +51,11 @@ public class CincinnatiSheetWriterTests
|
|||||||
using var sw = new StringWriter(sb);
|
using var sw = new StringWriter(sb);
|
||||||
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
||||||
|
|
||||||
sheetWriter.Write(sw, plate, "TestNest", 1, 101);
|
sheetWriter.Write(sw, plate, "TestNest", 1, 101, "", "");
|
||||||
|
|
||||||
var output = sb.ToString();
|
var output = sb.ToString();
|
||||||
Assert.Contains("M42", output);
|
Assert.Contains("M42", output);
|
||||||
Assert.Contains("G0X0Y0", output);
|
Assert.Contains("G0 X0 Y0", output);
|
||||||
Assert.Contains("M50", output);
|
Assert.Contains("M50", output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +69,7 @@ public class CincinnatiSheetWriterTests
|
|||||||
using var sw = new StringWriter(sb);
|
using var sw = new StringWriter(sb);
|
||||||
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
||||||
|
|
||||||
sheetWriter.Write(sw, plate, "TestNest", 1, 101);
|
sheetWriter.Write(sw, plate, "TestNest", 1, 101, "", "");
|
||||||
|
|
||||||
Assert.Equal("", sb.ToString());
|
Assert.Equal("", sb.ToString());
|
||||||
}
|
}
|
||||||
@@ -96,7 +97,7 @@ public class CincinnatiSheetWriterTests
|
|||||||
using var sw = new StringWriter(sb);
|
using var sw = new StringWriter(sb);
|
||||||
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
||||||
|
|
||||||
sheetWriter.Write(sw, plate, "TestNest", 1, 101);
|
sheetWriter.Write(sw, plate, "TestNest", 1, 101, "", "");
|
||||||
|
|
||||||
var output = sb.ToString();
|
var output = sb.ToString();
|
||||||
// Should have two G84 pierce commands (one per contour)
|
// Should have two G84 pierce commands (one per contour)
|
||||||
@@ -104,6 +105,80 @@ public class CincinnatiSheetWriterTests
|
|||||||
Assert.Equal(2, g84Count);
|
Assert.Equal(2, g84Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void WriteSheet_EtchFeaturesOrderedBeforeCut()
|
||||||
|
{
|
||||||
|
var config = new CincinnatiPostConfig { PostedAccuracy = 4 };
|
||||||
|
var pgm = new Program();
|
||||||
|
// Cut contour first in program
|
||||||
|
pgm.Codes.Add(new RapidMove(0, 0));
|
||||||
|
pgm.Codes.Add(new LinearMove(5, 0) { Layer = LayerType.Cut });
|
||||||
|
pgm.Codes.Add(new LinearMove(5, 5) { Layer = LayerType.Cut });
|
||||||
|
// Etch contour second in program
|
||||||
|
pgm.Codes.Add(new RapidMove(1, 1));
|
||||||
|
pgm.Codes.Add(new LinearMove(2, 1) { Layer = LayerType.Scribe });
|
||||||
|
pgm.Codes.Add(new LinearMove(2, 2) { Layer = LayerType.Scribe });
|
||||||
|
|
||||||
|
var plate = new Plate(48.0, 96.0);
|
||||||
|
plate.Parts.Add(new Part(new Drawing("MixedPart", pgm)));
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
using var sw = new StringWriter(sb);
|
||||||
|
var sheetWriter = new CincinnatiSheetWriter(config, new ProgramVariableManager());
|
||||||
|
|
||||||
|
sheetWriter.Write(sw, plate, "TestNest", 1, 101, "MS250O2.lib", "EtchN2.lib");
|
||||||
|
|
||||||
|
var output = sb.ToString();
|
||||||
|
// Etch (G85) should appear before cut (G84)
|
||||||
|
var g85Idx = output.IndexOf("G85");
|
||||||
|
var g84Idx = output.IndexOf("G84");
|
||||||
|
Assert.True(g85Idx >= 0, "G85 should be present for etch");
|
||||||
|
Assert.True(g84Idx >= 0, "G84 should be present for cut");
|
||||||
|
Assert.True(g85Idx < g84Idx, "G85 (etch) should come before G84 (cut)");
|
||||||
|
|
||||||
|
// Etch uses etch library
|
||||||
|
Assert.Contains("G89 P EtchN2.lib", output);
|
||||||
|
// Cut uses cut library
|
||||||
|
Assert.Contains("G89 P MS250O2.lib", output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsFeatureEtch_ReturnsTrueForScribeLayer()
|
||||||
|
{
|
||||||
|
var codes = new List<ICode>
|
||||||
|
{
|
||||||
|
new RapidMove(0, 0),
|
||||||
|
new LinearMove(1, 0) { Layer = LayerType.Scribe },
|
||||||
|
new LinearMove(1, 1) { Layer = LayerType.Scribe }
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.True(CincinnatiSheetWriter.IsFeatureEtch(codes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsFeatureEtch_ReturnsFalseForCutLayer()
|
||||||
|
{
|
||||||
|
var codes = new List<ICode>
|
||||||
|
{
|
||||||
|
new RapidMove(0, 0),
|
||||||
|
new LinearMove(1, 0) { Layer = LayerType.Cut },
|
||||||
|
new LinearMove(1, 1) { Layer = LayerType.Cut }
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.False(CincinnatiSheetWriter.IsFeatureEtch(codes));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void IsFeatureEtch_ReturnsFalseForRapidsOnly()
|
||||||
|
{
|
||||||
|
var codes = new List<ICode>
|
||||||
|
{
|
||||||
|
new RapidMove(0, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
Assert.False(CincinnatiSheetWriter.IsFeatureEtch(codes));
|
||||||
|
}
|
||||||
|
|
||||||
private static Program CreateSimpleProgram()
|
private static Program CreateSimpleProgram()
|
||||||
{
|
{
|
||||||
var pgm = new Program();
|
var pgm = new Program();
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
using OpenNest.Posts.Cincinnati;
|
||||||
|
|
||||||
|
namespace OpenNest.Tests.Cincinnati;
|
||||||
|
|
||||||
|
public class MaterialLibraryResolverTests
|
||||||
|
{
|
||||||
|
private static CincinnatiPostConfig ConfigWithLibraries() => new()
|
||||||
|
{
|
||||||
|
DefaultAssistGas = "O2",
|
||||||
|
DefaultEtchGas = "N2",
|
||||||
|
MaterialLibraries = new()
|
||||||
|
{
|
||||||
|
new MaterialLibraryEntry { Material = "Mild Steel", Thickness = 0.250, Gas = "O2", Library = "MS250O2.lib" },
|
||||||
|
new MaterialLibraryEntry { Material = "Mild Steel", Thickness = 0.250, Gas = "N2", Library = "MS250N2.lib" },
|
||||||
|
new MaterialLibraryEntry { Material = "Aluminum", Thickness = 0.125, Gas = "N2", Library = "AL125N2.lib" },
|
||||||
|
new MaterialLibraryEntry { Material = "Stainless Steel", Thickness = 0.375, Gas = "AIR", Library = "SS375AIR.lib" }
|
||||||
|
},
|
||||||
|
EtchLibraries = new()
|
||||||
|
{
|
||||||
|
new EtchLibraryEntry { Gas = "N2", Library = "EtchN2.lib" },
|
||||||
|
new EtchLibraryEntry { Gas = "O2", Library = "EtchO2.lib" },
|
||||||
|
new EtchLibraryEntry { Gas = "AIR", Library = "EtchAIR.lib" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveCutLibrary_ExactMatch()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveCutLibrary("Mild Steel", 0.250, "O2");
|
||||||
|
Assert.Equal("MS250O2.lib", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveCutLibrary_CaseInsensitiveMaterial()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveCutLibrary("mild steel", 0.250, "O2");
|
||||||
|
Assert.Equal("MS250O2.lib", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveCutLibrary_CaseInsensitiveGas()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveCutLibrary("Mild Steel", 0.250, "o2");
|
||||||
|
Assert.Equal("MS250O2.lib", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveCutLibrary_ThicknessWithinTolerance()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveCutLibrary("Mild Steel", 0.2505, "O2");
|
||||||
|
Assert.Equal("MS250O2.lib", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveCutLibrary_ThicknessOutsideTolerance_ReturnsEmpty()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveCutLibrary("Mild Steel", 0.260, "O2");
|
||||||
|
Assert.Equal("", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveCutLibrary_NoMatch_ReturnsEmpty()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveCutLibrary("Titanium", 0.250, "O2");
|
||||||
|
Assert.Equal("", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveCutLibrary_WrongGas_ReturnsEmpty()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveCutLibrary("Mild Steel", 0.250, "AIR");
|
||||||
|
Assert.Equal("", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveCutLibrary_DifferentGasSameMaterial()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var o2 = resolver.ResolveCutLibrary("Mild Steel", 0.250, "O2");
|
||||||
|
var n2 = resolver.ResolveCutLibrary("Mild Steel", 0.250, "N2");
|
||||||
|
Assert.Equal("MS250O2.lib", o2);
|
||||||
|
Assert.Equal("MS250N2.lib", n2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveCutLibrary_EmptyList_ReturnsEmpty()
|
||||||
|
{
|
||||||
|
var config = new CincinnatiPostConfig { MaterialLibraries = new() };
|
||||||
|
var resolver = new MaterialLibraryResolver(config);
|
||||||
|
var result = resolver.ResolveCutLibrary("Mild Steel", 0.250, "O2");
|
||||||
|
Assert.Equal("", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveEtchLibrary_ExactMatch()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveEtchLibrary("N2");
|
||||||
|
Assert.Equal("EtchN2.lib", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveEtchLibrary_CaseInsensitive()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveEtchLibrary("n2");
|
||||||
|
Assert.Equal("EtchN2.lib", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveEtchLibrary_NoMatch_ReturnsEmpty()
|
||||||
|
{
|
||||||
|
var resolver = new MaterialLibraryResolver(ConfigWithLibraries());
|
||||||
|
var result = resolver.ResolveEtchLibrary("Argon");
|
||||||
|
Assert.Equal("", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveEtchLibrary_EmptyList_ReturnsEmpty()
|
||||||
|
{
|
||||||
|
var config = new CincinnatiPostConfig { EtchLibraries = new() };
|
||||||
|
var resolver = new MaterialLibraryResolver(config);
|
||||||
|
var result = resolver.ResolveEtchLibrary("N2");
|
||||||
|
Assert.Equal("", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveGas_UsesNestAssistGas_WhenSet()
|
||||||
|
{
|
||||||
|
var nest = new Nest("Test") { AssistGas = "N2" };
|
||||||
|
var config = new CincinnatiPostConfig { DefaultAssistGas = "O2" };
|
||||||
|
var result = MaterialLibraryResolver.ResolveGas(nest, config);
|
||||||
|
Assert.Equal("N2", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveGas_FallsBackToConfig_WhenNestEmpty()
|
||||||
|
{
|
||||||
|
var nest = new Nest("Test") { AssistGas = "" };
|
||||||
|
var config = new CincinnatiPostConfig { DefaultAssistGas = "O2" };
|
||||||
|
var result = MaterialLibraryResolver.ResolveGas(nest, config);
|
||||||
|
Assert.Equal("O2", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ResolveGas_FallsBackToConfig_WhenNestNull()
|
||||||
|
{
|
||||||
|
var nest = new Nest("Test");
|
||||||
|
var config = new CincinnatiPostConfig { DefaultAssistGas = "AIR" };
|
||||||
|
var result = MaterialLibraryResolver.ResolveGas(nest, config);
|
||||||
|
Assert.Equal("AIR", result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -154,10 +154,10 @@ public class CutOffTests
|
|||||||
|
|
||||||
// Plate(100, 50) = Width=100, Length=50. Vertical cut runs along Y (Width axis).
|
// Plate(100, 50) = Width=100, Length=50. Vertical cut runs along Y (Width axis).
|
||||||
// BoundingBox Y extent = Size.Width = 100. With 2" overtravel = 102.
|
// BoundingBox Y extent = Size.Width = 100. With 2" overtravel = 102.
|
||||||
// Default TowardOrigin: RapidMove to far end (102), LinearMove to near end (0).
|
// Default AwayFromOrigin: RapidMove to near end (0), LinearMove to far end (102).
|
||||||
var rapidMoves = cutoff.Drawing.Program.Codes.OfType<RapidMove>().ToList();
|
var linearMoves = cutoff.Drawing.Program.Codes.OfType<LinearMove>().ToList();
|
||||||
Assert.Single(rapidMoves);
|
Assert.Single(linearMoves);
|
||||||
Assert.Equal(102.0, rapidMoves[0].EndPoint.Y, 5);
|
Assert.Equal(102.0, linearMoves[0].EndPoint.Y, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -171,11 +171,10 @@ public class CutOffTests
|
|||||||
};
|
};
|
||||||
cutoff.Regenerate(plate, settings);
|
cutoff.Regenerate(plate, settings);
|
||||||
|
|
||||||
|
// AwayFromOrigin: RapidMove to near end (StartLimit=20), LinearMove to far end (100).
|
||||||
var rapidMoves = cutoff.Drawing.Program.Codes.OfType<RapidMove>().ToList();
|
var rapidMoves = cutoff.Drawing.Program.Codes.OfType<RapidMove>().ToList();
|
||||||
Assert.Single(rapidMoves);
|
Assert.Single(rapidMoves);
|
||||||
var linearMoves = cutoff.Drawing.Program.Codes.OfType<LinearMove>().ToList();
|
Assert.Equal(20.0, rapidMoves[0].EndPoint.Y, 5);
|
||||||
Assert.Single(linearMoves);
|
|
||||||
Assert.Equal(20.0, linearMoves[0].EndPoint.Y, 5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -189,9 +188,10 @@ public class CutOffTests
|
|||||||
};
|
};
|
||||||
cutoff.Regenerate(plate, settings);
|
cutoff.Regenerate(plate, settings);
|
||||||
|
|
||||||
var rapidMoves = cutoff.Drawing.Program.Codes.OfType<RapidMove>().ToList();
|
// AwayFromOrigin: RapidMove to near end (0), LinearMove to far end (EndLimit=80).
|
||||||
Assert.Single(rapidMoves);
|
var linearMoves = cutoff.Drawing.Program.Codes.OfType<LinearMove>().ToList();
|
||||||
Assert.Equal(80.0, rapidMoves[0].EndPoint.Y, 5);
|
Assert.Single(linearMoves);
|
||||||
|
Assert.Equal(80.0, linearMoves[0].EndPoint.Y, 5);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -36,6 +36,47 @@ public class PolygonHelperTests
|
|||||||
$"With-spacing width: {withSpacing.Polygon.BoundingBox.Width:F3}");
|
$"With-spacing width: {withSpacing.Polygon.BoundingBox.Width:F3}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExtractPerimeterPolygon_InflatedPolygonIsLarger_ForCWWinding()
|
||||||
|
{
|
||||||
|
// CW winding (standard CNC convention): (0,0)→(0,10)→(10,10)→(10,0)→(0,0)
|
||||||
|
var drawing = TestHelpers.MakeSquareDrawing(10);
|
||||||
|
var noSpacing = PolygonHelper.ExtractPerimeterPolygon(drawing, 0);
|
||||||
|
var withSpacing = PolygonHelper.ExtractPerimeterPolygon(drawing, 1);
|
||||||
|
|
||||||
|
noSpacing.Polygon.UpdateBounds();
|
||||||
|
withSpacing.Polygon.UpdateBounds();
|
||||||
|
|
||||||
|
Assert.True(withSpacing.Polygon.BoundingBox.Width > noSpacing.Polygon.BoundingBox.Width,
|
||||||
|
$"Inflated width {withSpacing.Polygon.BoundingBox.Width:F3} should be > original {noSpacing.Polygon.BoundingBox.Width:F3}");
|
||||||
|
Assert.True(withSpacing.Polygon.BoundingBox.Length > noSpacing.Polygon.BoundingBox.Length,
|
||||||
|
$"Inflated length {withSpacing.Polygon.BoundingBox.Length:F3} should be > original {noSpacing.Polygon.BoundingBox.Length:F3}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ExtractPerimeterPolygon_InflatedPolygonIsLarger_ForCCWWinding()
|
||||||
|
{
|
||||||
|
// CCW winding: (0,0)→(10,0)→(10,10)→(0,10)→(0,0)
|
||||||
|
var pgm = new CNC.Program();
|
||||||
|
pgm.Codes.Add(new CNC.RapidMove(new Vector(0, 0)));
|
||||||
|
pgm.Codes.Add(new CNC.LinearMove(new Vector(10, 0)));
|
||||||
|
pgm.Codes.Add(new CNC.LinearMove(new Vector(10, 10)));
|
||||||
|
pgm.Codes.Add(new CNC.LinearMove(new Vector(0, 10)));
|
||||||
|
pgm.Codes.Add(new CNC.LinearMove(new Vector(0, 0)));
|
||||||
|
var drawing = new Drawing("ccw-square", pgm);
|
||||||
|
|
||||||
|
var noSpacing = PolygonHelper.ExtractPerimeterPolygon(drawing, 0);
|
||||||
|
var withSpacing = PolygonHelper.ExtractPerimeterPolygon(drawing, 1);
|
||||||
|
|
||||||
|
noSpacing.Polygon.UpdateBounds();
|
||||||
|
withSpacing.Polygon.UpdateBounds();
|
||||||
|
|
||||||
|
Assert.True(withSpacing.Polygon.BoundingBox.Width > noSpacing.Polygon.BoundingBox.Width,
|
||||||
|
$"Inflated width {withSpacing.Polygon.BoundingBox.Width:F3} should be > original {noSpacing.Polygon.BoundingBox.Width:F3}");
|
||||||
|
Assert.True(withSpacing.Polygon.BoundingBox.Length > noSpacing.Polygon.BoundingBox.Length,
|
||||||
|
$"Inflated length {withSpacing.Polygon.BoundingBox.Length:F3} should be > original {noSpacing.Polygon.BoundingBox.Length:F3}");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ExtractPerimeterPolygon_ReturnsNull_ForEmptyDrawing()
|
public void ExtractPerimeterPolygon_ReturnsNull_ForEmptyDrawing()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -187,7 +187,25 @@ namespace OpenNest.Actions
|
|||||||
|
|
||||||
var boxes = new List<Box>();
|
var boxes = new List<Box>();
|
||||||
foreach (var part in plate.Parts)
|
foreach (var part in plate.Parts)
|
||||||
|
{
|
||||||
|
if (part.BaseDrawing.IsCutOff)
|
||||||
|
continue;
|
||||||
|
|
||||||
boxes.Add(part.BoundingBox.Offset(plate.PartSpacing));
|
boxes.Add(part.BoundingBox.Offset(plate.PartSpacing));
|
||||||
|
}
|
||||||
|
|
||||||
|
var plateBounds = plate.BoundingBox(includeParts: false);
|
||||||
|
foreach (var cutoff in plate.CutOffs)
|
||||||
|
{
|
||||||
|
Box cutoffBox;
|
||||||
|
|
||||||
|
if (cutoff.Axis == CutOffAxis.Vertical)
|
||||||
|
cutoffBox = new Box(cutoff.Position.X, plateBounds.Y, 0, plateBounds.Length);
|
||||||
|
else
|
||||||
|
cutoffBox = new Box(plateBounds.X, cutoff.Position.Y, plateBounds.Width, 0);
|
||||||
|
|
||||||
|
boxes.Add(cutoffBox.Offset(plate.PartSpacing));
|
||||||
|
}
|
||||||
|
|
||||||
var pt = plateView.CurrentPoint;
|
var pt = plateView.CurrentPoint;
|
||||||
var vertical = SpatialQuery.GetLargestBoxVertically(pt, bounds, boxes);
|
var vertical = SpatialQuery.GetLargestBoxVertically(pt, bounds, boxes);
|
||||||
|
|||||||
@@ -157,7 +157,31 @@ namespace OpenNest.Actions
|
|||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
foreach (var part in plateView.Plate.Parts)
|
foreach (var part in plateView.Plate.Parts)
|
||||||
|
{
|
||||||
|
if (part.BaseDrawing.IsCutOff)
|
||||||
|
continue;
|
||||||
|
|
||||||
boxes.Add(part.BoundingBox.Offset(plateView.Plate.PartSpacing));
|
boxes.Add(part.BoundingBox.Offset(plateView.Plate.PartSpacing));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add thin obstacle boxes from cutoff definitions so that
|
||||||
|
// the area selection correctly treats cutoffs as boundaries.
|
||||||
|
// Cutoff Parts have inflated bounding boxes (their programs use
|
||||||
|
// absolute coordinates, causing BoundingBox to span from origin)
|
||||||
|
// so we derive the position directly from the CutOff definition.
|
||||||
|
var plateBounds = plateView.Plate.BoundingBox(includeParts: false);
|
||||||
|
|
||||||
|
foreach (var cutoff in plateView.Plate.CutOffs)
|
||||||
|
{
|
||||||
|
Box cutoffBox;
|
||||||
|
|
||||||
|
if (cutoff.Axis == CutOffAxis.Vertical)
|
||||||
|
cutoffBox = new Box(cutoff.Position.X, plateBounds.Y, 0, plateBounds.Length);
|
||||||
|
else
|
||||||
|
cutoffBox = new Box(plateBounds.X, cutoff.Position.Y, plateBounds.Width, 0);
|
||||||
|
|
||||||
|
boxes.Add(cutoffBox.Offset(plateView.Plate.PartSpacing));
|
||||||
|
}
|
||||||
|
|
||||||
Bounds = plateView.Plate.WorkArea();
|
Bounds = plateView.Plate.WorkArea();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -458,6 +458,7 @@ namespace OpenNest.Forms
|
|||||||
PlateView.ZoomToPlate();
|
PlateView.ZoomToPlate();
|
||||||
PlateView.Refresh();
|
PlateView.Refresh();
|
||||||
UpdatePlateList();
|
UpdatePlateList();
|
||||||
|
UpdatePlateHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectAllParts()
|
public void SelectAllParts()
|
||||||
|
|||||||
Generated
+26
-3
@@ -149,6 +149,8 @@
|
|||||||
engineComboBox = new System.Windows.Forms.ToolStripComboBox();
|
engineComboBox = new System.Windows.Forms.ToolStripComboBox();
|
||||||
btnAutoNest = new System.Windows.Forms.ToolStripButton();
|
btnAutoNest = new System.Windows.Forms.ToolStripButton();
|
||||||
btnShowRemnants = new System.Windows.Forms.ToolStripButton();
|
btnShowRemnants = new System.Windows.Forms.ToolStripButton();
|
||||||
|
toolStripSeparator5 = new System.Windows.Forms.ToolStripSeparator();
|
||||||
|
btnCutOff = new System.Windows.Forms.ToolStripButton();
|
||||||
pEPToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
pEPToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
openNestToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
openNestToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
|
||||||
menuStrip1.SuspendLayout();
|
menuStrip1.SuspendLayout();
|
||||||
@@ -917,7 +919,7 @@
|
|||||||
// toolStrip1
|
// toolStrip1
|
||||||
//
|
//
|
||||||
toolStrip1.AutoSize = false;
|
toolStrip1.AutoSize = false;
|
||||||
toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { btnNew, btnOpen, btnSave, btnSaveAs, toolStripSeparator1, btnZoomOut, btnZoomIn, btnZoomToFit, toolStripSeparator4, engineLabel, engineComboBox, btnAutoNest, btnShowRemnants });
|
toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { btnNew, btnOpen, btnSave, btnSaveAs, toolStripSeparator1, btnZoomOut, btnZoomIn, btnZoomToFit, toolStripSeparator4, engineLabel, engineComboBox, btnAutoNest, btnShowRemnants, toolStripSeparator5, btnCutOff });
|
||||||
toolStrip1.Location = new System.Drawing.Point(0, 24);
|
toolStrip1.Location = new System.Drawing.Point(0, 24);
|
||||||
toolStrip1.Name = "toolStrip1";
|
toolStrip1.Name = "toolStrip1";
|
||||||
toolStrip1.Size = new System.Drawing.Size(1281, 40);
|
toolStrip1.Size = new System.Drawing.Size(1281, 40);
|
||||||
@@ -1044,12 +1046,31 @@
|
|||||||
//
|
//
|
||||||
// btnShowRemnants
|
// btnShowRemnants
|
||||||
//
|
//
|
||||||
btnShowRemnants.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
btnShowRemnants.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
|
||||||
|
btnShowRemnants.Image = Properties.Resources.remnants;
|
||||||
|
btnShowRemnants.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
|
||||||
btnShowRemnants.Name = "btnShowRemnants";
|
btnShowRemnants.Name = "btnShowRemnants";
|
||||||
btnShowRemnants.Size = new System.Drawing.Size(64, 37);
|
btnShowRemnants.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0);
|
||||||
|
btnShowRemnants.Size = new System.Drawing.Size(38, 37);
|
||||||
btnShowRemnants.Text = "Remnants";
|
btnShowRemnants.Text = "Remnants";
|
||||||
btnShowRemnants.Click += ShowRemnants_Click;
|
btnShowRemnants.Click += ShowRemnants_Click;
|
||||||
//
|
//
|
||||||
|
// toolStripSeparator5
|
||||||
|
//
|
||||||
|
toolStripSeparator5.Name = "toolStripSeparator5";
|
||||||
|
toolStripSeparator5.Size = new System.Drawing.Size(6, 40);
|
||||||
|
//
|
||||||
|
// btnCutOff
|
||||||
|
//
|
||||||
|
btnCutOff.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
|
||||||
|
btnCutOff.Image = Properties.Resources.cutoff;
|
||||||
|
btnCutOff.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
|
||||||
|
btnCutOff.Name = "btnCutOff";
|
||||||
|
btnCutOff.Padding = new System.Windows.Forms.Padding(5, 0, 5, 0);
|
||||||
|
btnCutOff.Size = new System.Drawing.Size(38, 37);
|
||||||
|
btnCutOff.Text = "Sheet Cut-Off";
|
||||||
|
btnCutOff.Click += CutOff_Click;
|
||||||
|
//
|
||||||
// pEPToolStripMenuItem
|
// pEPToolStripMenuItem
|
||||||
//
|
//
|
||||||
pEPToolStripMenuItem.Name = "pEPToolStripMenuItem";
|
pEPToolStripMenuItem.Name = "pEPToolStripMenuItem";
|
||||||
@@ -1213,6 +1234,8 @@
|
|||||||
private System.Windows.Forms.ToolStripComboBox engineComboBox;
|
private System.Windows.Forms.ToolStripComboBox engineComboBox;
|
||||||
private System.Windows.Forms.ToolStripButton btnAutoNest;
|
private System.Windows.Forms.ToolStripButton btnAutoNest;
|
||||||
private System.Windows.Forms.ToolStripButton btnShowRemnants;
|
private System.Windows.Forms.ToolStripButton btnShowRemnants;
|
||||||
|
private System.Windows.Forms.ToolStripSeparator toolStripSeparator5;
|
||||||
|
private System.Windows.Forms.ToolStripButton btnCutOff;
|
||||||
private System.Windows.Forms.ToolStripMenuItem mnuPlateCutOff;
|
private System.Windows.Forms.ToolStripMenuItem mnuPlateCutOff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+20
@@ -80,6 +80,16 @@ namespace OpenNest.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||||
|
/// </summary>
|
||||||
|
internal static System.Drawing.Bitmap cutoff {
|
||||||
|
get {
|
||||||
|
object obj = ResourceManager.GetObject("cutoff", resourceCulture);
|
||||||
|
return ((System.Drawing.Bitmap)(obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -160,6 +170,16 @@ namespace OpenNest.Properties {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||||
|
/// </summary>
|
||||||
|
internal static System.Drawing.Bitmap remnants {
|
||||||
|
get {
|
||||||
|
object obj = ResourceManager.GetObject("remnants", resourceCulture);
|
||||||
|
return ((System.Drawing.Bitmap)(obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -175,4 +175,10 @@
|
|||||||
<data name="zoom_out" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<data name="zoom_out" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
<value>..\Resources\zoom_out.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
<value>..\Resources\zoom_out.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="cutoff" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
|
<value>..\Resources\cutoff.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
|
</data>
|
||||||
|
<data name="remnants" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
|
<value>..\Resources\remnants.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 994 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\OpenNest.Core\OpenNest.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\..\OpenNest.IO\OpenNest.IO.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
using System;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenNest;
|
||||||
|
using OpenNest.Converters;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
using Size = OpenNest.Geometry.Size;
|
||||||
|
using OpenNest.IO;
|
||||||
|
|
||||||
|
var partColors = new Color[]
|
||||||
|
{
|
||||||
|
Color.FromArgb(205, 92, 92), // Indian Red
|
||||||
|
Color.FromArgb(148, 103, 189), // Medium Purple
|
||||||
|
Color.FromArgb(75, 180, 175), // Teal
|
||||||
|
Color.FromArgb(210, 190, 75), // Goldenrod
|
||||||
|
Color.FromArgb(190, 85, 175), // Orchid
|
||||||
|
Color.FromArgb(185, 115, 85), // Sienna
|
||||||
|
Color.FromArgb(120, 100, 190), // Slate Blue
|
||||||
|
Color.FromArgb(200, 100, 140), // Rose
|
||||||
|
Color.FromArgb(80, 175, 155), // Sea Green
|
||||||
|
Color.FromArgb(195, 160, 85), // Dark Khaki
|
||||||
|
Color.FromArgb(175, 95, 160), // Plum
|
||||||
|
Color.FromArgb(215, 130, 130), // Light Coral
|
||||||
|
};
|
||||||
|
|
||||||
|
var templateDir = @"C:\Users\AJ\Desktop\Projects\OpenNest\docs\Templates";
|
||||||
|
var outputDir = @"C:\Users\AJ\Desktop\Projects\OpenNest\docs\Templates\Nests";
|
||||||
|
Directory.CreateDirectory(outputDir);
|
||||||
|
|
||||||
|
// BOM: (fileName, qty, thickness, material)
|
||||||
|
var bom = new (string File, int Qty, double Thickness, string Material)[]
|
||||||
|
{
|
||||||
|
("PT01", 2, 0.250, "304SS"),
|
||||||
|
("PT02", 5, 0.625, "304SS"),
|
||||||
|
("PT03", 4, 0.250, "304SS"),
|
||||||
|
("PT04", 4, 0.250, "304SS"),
|
||||||
|
("PT05", 1, 0.250, "304SS"),
|
||||||
|
("PT06", 21, 0.375, "304SS"),
|
||||||
|
("PT07", 2, 0.250, "304SS"),
|
||||||
|
("PT08", 22, 0.250, "304SS"),
|
||||||
|
("PT11", 2, 0.1875, "304SS"),
|
||||||
|
("PT12", 2, 0.1875, "304SS"),
|
||||||
|
("PT13", 6, 0.1875, "304SS"),
|
||||||
|
("PT15", 6, 0.1875, "304SS"),
|
||||||
|
("PT16", 6, 0.1875, "304SS"),
|
||||||
|
("PT18", 3, 0.250, "304SS"),
|
||||||
|
("PT19", 3, 0.1875, "304SS"),
|
||||||
|
("PT20", 2, 0.1196, "304SS"),
|
||||||
|
("PT21", 6, 0.1196, "304SS"),
|
||||||
|
("PT22", 2, 0.1196, "304SS"),
|
||||||
|
("PT23", 1, 0.0598, "304SS"),
|
||||||
|
("PT24", 1, 0.0598, "304SS"),
|
||||||
|
("PT26", 4, 0.250, "304SS"),
|
||||||
|
("PT27", 2, 0.250, "304SS"),
|
||||||
|
("PT28", 4, 0.250, "304SS"),
|
||||||
|
("PT29", 6, 0.250, "304SS"),
|
||||||
|
("PT33", 2, 0.250, "304SS"),
|
||||||
|
("PT34", 4, 0.250, "304SS"),
|
||||||
|
("PT35", 3, 0.1875, "304SS"),
|
||||||
|
("PT36", 4, 0.1875, "304SS"),
|
||||||
|
("PT37", 4, 0.1875, "304SS"),
|
||||||
|
("PT38", 4, 0.1875, "304SS"),
|
||||||
|
("PT39", 2, 0.0598, "304SS"),
|
||||||
|
("PT40", 4, 0.0598, "304SS"),
|
||||||
|
("PT41", 1, 0.1875, "304SS"),
|
||||||
|
("PT43", 1, 0.0598, "304SS"),
|
||||||
|
("PT44", 1, 0.0598, "304SS"),
|
||||||
|
("PT45", 1, 0.250, "304SS"),
|
||||||
|
("PT46", 2, 0.250, "304SS"),
|
||||||
|
("PT47", 4, 0.250, "304SS"),
|
||||||
|
("PT48", 1, 0.250, "304SS"),
|
||||||
|
("PT49", 1, 0.750, "PCS"),
|
||||||
|
("PT50", 2, 0.375, "PCS"),
|
||||||
|
("PT51", 1, 0.250, "304SS"),
|
||||||
|
("PT52", 1, 0.1875, "304SS"),
|
||||||
|
("PT53", 1, 0.1875, "304SS"),
|
||||||
|
("PT54", 2, 0.250, "304SS"),
|
||||||
|
("PT55", 1, 0.1196, "304SS"),
|
||||||
|
("PT56", 1, 0.1196, "304SS"),
|
||||||
|
("PT57", 1, 0.0598, "304SS"),
|
||||||
|
("PT58", 1, 0.0598, "304SS"),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Group by material + thickness
|
||||||
|
var groups = bom.GroupBy(b => (b.Material, b.Thickness)).OrderBy(g => g.Key.Material).ThenBy(g => g.Key.Thickness);
|
||||||
|
|
||||||
|
foreach (var group in groups)
|
||||||
|
{
|
||||||
|
var material = group.Key.Material;
|
||||||
|
var thickness = group.Key.Thickness;
|
||||||
|
var thicknessLabel = thickness switch
|
||||||
|
{
|
||||||
|
0.0598 => "16GA",
|
||||||
|
0.1196 => "11GA",
|
||||||
|
0.1875 => "3-16",
|
||||||
|
0.250 => "1-4",
|
||||||
|
0.375 => "3-8",
|
||||||
|
0.625 => "5-8",
|
||||||
|
0.750 => "3-4",
|
||||||
|
_ => thickness.ToString("F4")
|
||||||
|
};
|
||||||
|
|
||||||
|
var nestName = $"4526 A14 - {material} {thicknessLabel}";
|
||||||
|
Console.WriteLine($"\n=== {nestName} ===");
|
||||||
|
|
||||||
|
var nest = new Nest();
|
||||||
|
nest.Name = nestName;
|
||||||
|
nest.PlateDefaults.Thickness = thickness;
|
||||||
|
nest.PlateDefaults.Material = new Material { Name = material };
|
||||||
|
nest.PlateDefaults.PartSpacing = 0.125;
|
||||||
|
nest.PlateDefaults.EdgeSpacing = new Spacing(0.25, 0.25, 0.25, 0.25);
|
||||||
|
|
||||||
|
// Import DXFs for this group
|
||||||
|
var importer = new DxfImporter();
|
||||||
|
var colorIndex = 0;
|
||||||
|
double maxMinDim = 0;
|
||||||
|
double maxMaxDim = 0;
|
||||||
|
|
||||||
|
foreach (var item in group)
|
||||||
|
{
|
||||||
|
var dxfPath = Path.Combine(templateDir, $"4526 A14 {item.File}.dxf");
|
||||||
|
if (!File.Exists(dxfPath))
|
||||||
|
{
|
||||||
|
Console.WriteLine($" WARNING: {dxfPath} not found, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!importer.GetGeometry(dxfPath, out var geometry) || geometry.Count == 0)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" WARNING: no geometry in {item.File}, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pgm = ConvertGeometry.ToProgram(geometry);
|
||||||
|
if (pgm == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" WARNING: failed to convert {item.File}, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var drawing = new Drawing(item.File, pgm);
|
||||||
|
drawing.Quantity.Required = item.Qty;
|
||||||
|
drawing.Material = new Material { Name = material };
|
||||||
|
drawing.Color = partColors[colorIndex % partColors.Length];
|
||||||
|
colorIndex++;
|
||||||
|
nest.Drawings.Add(drawing);
|
||||||
|
|
||||||
|
var bbox = pgm.BoundingBox();
|
||||||
|
var minDim = System.Math.Min(bbox.Width, bbox.Length);
|
||||||
|
var maxDim = System.Math.Max(bbox.Width, bbox.Length);
|
||||||
|
maxMinDim = System.Math.Max(maxMinDim, minDim);
|
||||||
|
maxMaxDim = System.Math.Max(maxMaxDim, maxDim);
|
||||||
|
|
||||||
|
Console.WriteLine($" {item.File}: {bbox.Width:F2} x {bbox.Length:F2}, qty={item.Qty}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose plate size based on largest part dimensions
|
||||||
|
// Size(width, length) — width is the short side, length is the long side
|
||||||
|
// Standard sizes: 48x96, 48x120, 60x120, 60x144, 72x144, 96x120
|
||||||
|
double plateW, plateL;
|
||||||
|
if (maxMinDim <= 47.5 && maxMaxDim <= 95.5)
|
||||||
|
{
|
||||||
|
plateW = 48; plateL = 96;
|
||||||
|
}
|
||||||
|
else if (maxMinDim <= 47.5 && maxMaxDim <= 119.5)
|
||||||
|
{
|
||||||
|
plateW = 48; plateL = 120;
|
||||||
|
}
|
||||||
|
else if (maxMinDim <= 59.5 && maxMaxDim <= 119.5)
|
||||||
|
{
|
||||||
|
plateW = 60; plateL = 120;
|
||||||
|
}
|
||||||
|
else if (maxMinDim <= 59.5 && maxMaxDim <= 143.5)
|
||||||
|
{
|
||||||
|
plateW = 60; plateL = 144;
|
||||||
|
}
|
||||||
|
else if (maxMinDim <= 71.5 && maxMaxDim <= 143.5)
|
||||||
|
{
|
||||||
|
plateW = 72; plateL = 144;
|
||||||
|
}
|
||||||
|
else if (maxMinDim <= 95.5 && maxMaxDim <= 119.5)
|
||||||
|
{
|
||||||
|
plateW = 96; plateL = 120;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Fallback: round up to nearest 12"
|
||||||
|
plateW = System.Math.Ceiling((maxMinDim + 1) / 12.0) * 12;
|
||||||
|
plateL = System.Math.Ceiling((maxMaxDim + 1) / 12.0) * 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create one empty plate via PlateDefaults so it inherits settings
|
||||||
|
nest.PlateDefaults.Size = new Size(plateW, plateL);
|
||||||
|
var plate = nest.CreatePlate();
|
||||||
|
plate.Quantity = 1;
|
||||||
|
|
||||||
|
Console.WriteLine($" Plate size: {plateW} x {plateL} (W x L)");
|
||||||
|
Console.WriteLine($" Drawings: {nest.Drawings.Count}");
|
||||||
|
|
||||||
|
var outputPath = Path.Combine(outputDir, $"{nestName}.nest");
|
||||||
|
var writer = new NestWriter(nest);
|
||||||
|
if (writer.Write(outputPath))
|
||||||
|
Console.WriteLine($" Saved: {outputPath}");
|
||||||
|
else
|
||||||
|
Console.WriteLine($" ERROR: failed to save {outputPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine("\nDone!");
|
||||||
Reference in New Issue
Block a user