Compare commits
24 Commits
a6c2235647
...
v0.2.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c53d6fecd | |||
| e239967a7b | |||
| 9d57d3875a | |||
| 0e299d7f6f | |||
| c6f544c5d7 | |||
| 9563094c2b | |||
| 091e750e1b | |||
| 87b965f895 | |||
| 08f60690a7 | |||
| a4609c816c | |||
| 5a4272696e | |||
| 2cf03be360 | |||
| 041e184d93 | |||
| 26df3174ea | |||
| 0f5aace126 | |||
| 399f8dda6e | |||
| d921558b9c | |||
| bf3e3e1f42 | |||
| e120ece014 | |||
| 264e8264be | |||
| 24babe353e | |||
| e63be93051 | |||
| ba3c3cbea3 | |||
| 572fa06a21 |
@@ -211,8 +211,5 @@ FakesAssemblies/
|
|||||||
.superpowers/
|
.superpowers/
|
||||||
docs/superpowers/
|
docs/superpowers/
|
||||||
|
|
||||||
# Documentation (manuals, templates, etc.)
|
|
||||||
docs/
|
|
||||||
|
|
||||||
# Launch settings
|
# Launch settings
|
||||||
**/Properties/launchSettings.json
|
**/Properties/launchSettings.json
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ File I/O and format conversion. Uses ACadSharp for DXF/DWG support.
|
|||||||
- `NestReader`/`NestWriter` — custom ZIP-based nest format (JSON metadata + G-code programs, v2 format).
|
- `NestReader`/`NestWriter` — custom ZIP-based nest format (JSON metadata + G-code programs, v2 format).
|
||||||
- `ProgramReader` — G-code text parser.
|
- `ProgramReader` — G-code text parser.
|
||||||
- `Extensions` — conversion helpers between ACadSharp and OpenNest geometry types.
|
- `Extensions` — conversion helpers between ACadSharp and OpenNest geometry types.
|
||||||
|
- `CadImporter` — shared "DXF → Drawing" service used by the UI, console, MCP, API, and training projects. Two-stage API: `Import(path, options)` loads raw entities, runs bend detection, and returns a mutable `CadImportResult`; `BuildDrawing(result, visible, bends, quantity, customer, editedProgram)` produces a fully-populated `Drawing` with `Source.Offset`, `SourceEntities`, `SuppressedEntityIds`, and bends. `ImportDrawing(path, options)` composes both stages for headless callers.
|
||||||
|
- `CadImportOptions`, `CadImportResult` — inputs and intermediate state for `CadImporter`.
|
||||||
|
|
||||||
### OpenNest.Console (console app, depends on Core + Engine + IO)
|
### OpenNest.Console (console app, depends on Core + Engine + IO)
|
||||||
Command-line interface for batch nesting. Supports DXF import, plate configuration, linear fill, and NFP-based auto-nesting (`--autonest`).
|
Command-line interface for batch nesting. Supports DXF import, plate configuration, linear fill, and NFP-based auto-nesting (`--autonest`).
|
||||||
@@ -117,3 +119,4 @@ Always keep `README.md` and `CLAUDE.md` up to date when making changes that affe
|
|||||||
- `FillScore` uses lexicographic comparison (count > utilization > compactness) to rank fill results consistently across all fill strategies.
|
- `FillScore` uses lexicographic comparison (count > utilization > compactness) to rank fill results consistently across all fill strategies.
|
||||||
- **Cut-off materialization lifecycle**: `CutOff` objects live on `Plate.CutOffs`. Each generates a `Drawing` (with `IsCutOff = true`) whose `Program` contains trimmed line segments. `Plate.RegenerateCutOffs(settings)` removes old cut-off Parts, recomputes programs, and re-adds them to `Plate.Parts`. Regeneration triggers: cut-off add/remove/move, part drag complete, fill complete, plate transform. Cut-off Parts are excluded from quantity tracking, utilization, overlap detection, and nest file serialization (programs are regenerated from definitions on load).
|
- **Cut-off materialization lifecycle**: `CutOff` objects live on `Plate.CutOffs`. Each generates a `Drawing` (with `IsCutOff = true`) whose `Program` contains trimmed line segments. `Plate.RegenerateCutOffs(settings)` removes old cut-off Parts, recomputes programs, and re-adds them to `Plate.Parts`. Regeneration triggers: cut-off add/remove/move, part drag complete, fill complete, plate transform. Cut-off Parts are excluded from quantity tracking, utilization, overlap detection, and nest file serialization (programs are regenerated from definitions on load).
|
||||||
- **User-defined G-code variables**: Programs can contain named variable definitions (`name = expression [inline] [global]`) referenced in coordinates with `$name`. Variables resolve to doubles at parse time for geometry/nesting. `VariableRefs` on `Motion`/`Feedrate` track the symbolic link so post processors can emit machine variable references. Cincinnati post maps non-inline variables to numbered machine variables (`#200+`) with descriptive comments. Global variables share a number across programs; local variables get per-drawing numbers. `ProgramReader` uses a two-pass parse (collect definitions, then parse G-code with substitution). `NestWriter` serializes definitions and `$references` back to text for round-trip fidelity.
|
- **User-defined G-code variables**: Programs can contain named variable definitions (`name = expression [inline] [global]`) referenced in coordinates with `$name`. Variables resolve to doubles at parse time for geometry/nesting. `VariableRefs` on `Motion`/`Feedrate` track the symbolic link so post processors can emit machine variable references. Cincinnati post maps non-inline variables to numbered machine variables (`#200+`) with descriptive comments. Global variables share a number across programs; local variables get per-drawing numbers. `ProgramReader` uses a two-pass parse (collect definitions, then parse G-code with substitution). `NestWriter` serializes definitions and `$references` back to text for round-trip fidelity.
|
||||||
|
- **CAD import pipeline**: All "DXF → Drawing" conversion goes through `OpenNest.IO.CadImporter`. The UI form uses `Import` on file load (storing the mutable result in a `FileListItem`) and `BuildDrawing` on save (passing the user's current visible entities and bends). Console, MCP, API, and Training projects use `ImportDrawing` for headless conversion. This guarantees all callers produce drawings with the same shape: pierce-point `Source.Offset`, stable `SourceEntities` with GUIDs, `SuppressedEntityIds`, detected bends, and metadata.
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using OpenNest.Converters;
|
|
||||||
using OpenNest.Geometry;
|
|
||||||
using OpenNest.IO;
|
using OpenNest.IO;
|
||||||
|
|
||||||
namespace OpenNest.Api;
|
namespace OpenNest.Api;
|
||||||
@@ -30,15 +28,21 @@ public static class NestRunner
|
|||||||
if (!File.Exists(part.DxfPath))
|
if (!File.Exists(part.DxfPath))
|
||||||
throw new FileNotFoundException($"DXF file not found: {part.DxfPath}", part.DxfPath);
|
throw new FileNotFoundException($"DXF file not found: {part.DxfPath}", part.DxfPath);
|
||||||
|
|
||||||
var geometry = Dxf.GetGeometry(part.DxfPath);
|
Drawing drawing;
|
||||||
if (geometry.Count == 0)
|
try
|
||||||
|
{
|
||||||
|
drawing = CadImporter.ImportDrawing(part.DxfPath,
|
||||||
|
new CadImportOptions { Quantity = part.Quantity });
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Failed to import DXF: {part.DxfPath}", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawing.Program == null || drawing.Program.Codes.Count == 0)
|
||||||
throw new InvalidOperationException($"Failed to import DXF: {part.DxfPath}");
|
throw new InvalidOperationException($"Failed to import DXF: {part.DxfPath}");
|
||||||
|
|
||||||
var normalized = ShapeProfile.NormalizeEntities(geometry);
|
|
||||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
|
||||||
var name = Path.GetFileNameWithoutExtension(part.DxfPath);
|
|
||||||
var drawing = new Drawing(name);
|
|
||||||
drawing.Program = pgm;
|
|
||||||
drawings.Add(drawing);
|
drawings.Add(drawing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using OpenNest;
|
using OpenNest;
|
||||||
using OpenNest.Converters;
|
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
using OpenNest.IO;
|
using OpenNest.IO;
|
||||||
using System;
|
using System;
|
||||||
@@ -241,25 +240,15 @@ static class NestConsole
|
|||||||
|
|
||||||
static Drawing ImportDxf(string path)
|
static Drawing ImportDxf(string path)
|
||||||
{
|
{
|
||||||
var geometry = Dxf.GetGeometry(path);
|
try
|
||||||
|
|
||||||
if (geometry.Count == 0)
|
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"Error: failed to read DXF file or no geometry found: {path}");
|
return CadImporter.ImportDrawing(path);
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"Error: failed to import DXF '{path}': {ex.Message}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var normalized = ShapeProfile.NormalizeEntities(geometry);
|
|
||||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
|
||||||
|
|
||||||
if (pgm == null)
|
|
||||||
{
|
|
||||||
Console.Error.WriteLine($"Error: failed to convert geometry: {path}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var name = Path.GetFileNameWithoutExtension(path);
|
|
||||||
return new Drawing(name, pgm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ApplyTemplate(Plate plate, Options options)
|
static void ApplyTemplate(Plate plate, Options options)
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ namespace OpenNest.CNC
|
|||||||
{
|
{
|
||||||
var code = Codes[i];
|
var code = Codes[i];
|
||||||
|
|
||||||
|
if (code is SubProgramCall subpgm)
|
||||||
|
{
|
||||||
|
subpgm.Offset = new Geometry.Vector(
|
||||||
|
subpgm.Offset.X + x, subpgm.Offset.Y + y);
|
||||||
|
}
|
||||||
|
|
||||||
if (code is Motion == false)
|
if (code is Motion == false)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -150,6 +156,12 @@ namespace OpenNest.CNC
|
|||||||
{
|
{
|
||||||
var code = Codes[i];
|
var code = Codes[i];
|
||||||
|
|
||||||
|
if (code is SubProgramCall subpgm)
|
||||||
|
{
|
||||||
|
subpgm.Offset = new Geometry.Vector(
|
||||||
|
subpgm.Offset.X + voffset.X, subpgm.Offset.Y + voffset.Y);
|
||||||
|
}
|
||||||
|
|
||||||
if (code is Motion == false)
|
if (code is Motion == false)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -288,6 +300,10 @@ namespace OpenNest.CNC
|
|||||||
|
|
||||||
private Box BoundingBox(ref Vector pos)
|
private Box BoundingBox(ref Vector pos)
|
||||||
{
|
{
|
||||||
|
// Capture the frame origin at entry. Sub-program Offsets and
|
||||||
|
// absolute-mode endpoints are relative to this fixed origin.
|
||||||
|
var frameOrigin = pos;
|
||||||
|
|
||||||
double minX = 0.0;
|
double minX = 0.0;
|
||||||
double minY = 0.0;
|
double minY = 0.0;
|
||||||
double maxX = 0.0;
|
double maxX = 0.0;
|
||||||
@@ -303,7 +319,7 @@ namespace OpenNest.CNC
|
|||||||
{
|
{
|
||||||
var line = (LinearMove)code;
|
var line = (LinearMove)code;
|
||||||
var pt = Mode == Mode.Absolute ?
|
var pt = Mode == Mode.Absolute ?
|
||||||
line.EndPoint :
|
frameOrigin + line.EndPoint :
|
||||||
line.EndPoint + pos;
|
line.EndPoint + pos;
|
||||||
|
|
||||||
if (pt.X > maxX)
|
if (pt.X > maxX)
|
||||||
@@ -325,7 +341,7 @@ namespace OpenNest.CNC
|
|||||||
{
|
{
|
||||||
var line = (RapidMove)code;
|
var line = (RapidMove)code;
|
||||||
var pt = Mode == Mode.Absolute
|
var pt = Mode == Mode.Absolute
|
||||||
? line.EndPoint
|
? frameOrigin + line.EndPoint
|
||||||
: line.EndPoint + pos;
|
: line.EndPoint + pos;
|
||||||
|
|
||||||
if (pt.X > maxX)
|
if (pt.X > maxX)
|
||||||
@@ -358,8 +374,8 @@ namespace OpenNest.CNC
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
endpt = arc.EndPoint;
|
endpt = frameOrigin + arc.EndPoint;
|
||||||
centerpt = arc.CenterPoint;
|
centerpt = frameOrigin + arc.CenterPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
double minX1;
|
double minX1;
|
||||||
@@ -433,10 +449,13 @@ namespace OpenNest.CNC
|
|||||||
case CodeType.SubProgramCall:
|
case CodeType.SubProgramCall:
|
||||||
{
|
{
|
||||||
var subpgm = (SubProgramCall)code;
|
var subpgm = (SubProgramCall)code;
|
||||||
var subPos = subpgm.Offset.X != 0 || subpgm.Offset.Y != 0
|
if (subpgm.Program == null)
|
||||||
? new Vector(subpgm.Offset.X, subpgm.Offset.Y)
|
break;
|
||||||
: pos;
|
|
||||||
var box = subpgm.Program.BoundingBox(ref subPos);
|
// Sub-program frame origin in this program's frame
|
||||||
|
// is frameOrigin + Offset, regardless of current pos.
|
||||||
|
pos = frameOrigin + subpgm.Offset;
|
||||||
|
var box = subpgm.Program.BoundingBox(ref pos);
|
||||||
|
|
||||||
if (box.Left < minX)
|
if (box.Left < minX)
|
||||||
minX = box.Left;
|
minX = box.Left;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using OpenNest.Geometry;
|
using System.Text;
|
||||||
|
using OpenNest.Geometry;
|
||||||
using OpenNest.Math;
|
using OpenNest.Math;
|
||||||
|
|
||||||
namespace OpenNest.CNC
|
namespace OpenNest.CNC
|
||||||
@@ -90,9 +91,13 @@ namespace OpenNest.CNC
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
sb.Append($"G65 P{Id}");
|
||||||
if (Offset.X != 0 || Offset.Y != 0)
|
if (Offset.X != 0 || Offset.Y != 0)
|
||||||
return string.Format("G65 P{0} X{1} Y{2}", Id, Offset.X, Offset.Y);
|
sb.Append($" X{Offset.X} Y{Offset.Y}");
|
||||||
return string.Format("G65 P{0} R{1}", Id, Rotation);
|
if (Rotation != 0)
|
||||||
|
sb.Append($" R{Rotation}");
|
||||||
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using OpenNest.CNC;
|
using OpenNest.CNC;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
namespace OpenNest.Converters
|
namespace OpenNest.Converters
|
||||||
@@ -9,7 +9,6 @@ namespace OpenNest.Converters
|
|||||||
/// Converts the program to absolute coordinates.
|
/// Converts the program to absolute coordinates.
|
||||||
/// Does NOT check program mode before converting.
|
/// Does NOT check program mode before converting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pgm"></param>
|
|
||||||
public static void ToAbsolute(Program pgm)
|
public static void ToAbsolute(Program pgm)
|
||||||
{
|
{
|
||||||
var pos = new Vector(0, 0);
|
var pos = new Vector(0, 0);
|
||||||
@@ -17,21 +16,27 @@ namespace OpenNest.Converters
|
|||||||
for (int i = 0; i < pgm.Codes.Count; ++i)
|
for (int i = 0; i < pgm.Codes.Count; ++i)
|
||||||
{
|
{
|
||||||
var code = pgm.Codes[i];
|
var code = pgm.Codes[i];
|
||||||
var motion = code as Motion;
|
|
||||||
|
|
||||||
if (motion != null)
|
if (code is SubProgramCall subCall && subCall.Program != null)
|
||||||
{
|
{
|
||||||
motion.Offset(pos);
|
// Sub-program is placed at Offset in this program's frame.
|
||||||
|
// After it runs, the tool is at Offset + (sub's end in its own frame).
|
||||||
|
pos = ComputeEndPosition(subCall.Program, subCall.Offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code is Motion motion)
|
||||||
|
{
|
||||||
|
motion.Offset(pos.X, pos.Y);
|
||||||
pos = motion.EndPoint;
|
pos = motion.EndPoint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts the program to intermental coordinates.
|
/// Converts the program to incremental coordinates.
|
||||||
/// Does NOT check program mode before converting.
|
/// Does NOT check program mode before converting.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pgm"></param>
|
|
||||||
public static void ToIncremental(Program pgm)
|
public static void ToIncremental(Program pgm)
|
||||||
{
|
{
|
||||||
var pos = new Vector(0, 0);
|
var pos = new Vector(0, 0);
|
||||||
@@ -39,9 +44,16 @@ namespace OpenNest.Converters
|
|||||||
for (int i = 0; i < pgm.Codes.Count; ++i)
|
for (int i = 0; i < pgm.Codes.Count; ++i)
|
||||||
{
|
{
|
||||||
var code = pgm.Codes[i];
|
var code = pgm.Codes[i];
|
||||||
var motion = code as Motion;
|
|
||||||
|
|
||||||
if (motion != null)
|
if (code is SubProgramCall subCall && subCall.Program != null)
|
||||||
|
{
|
||||||
|
// Sub-program is placed at Offset in this program's frame,
|
||||||
|
// regardless of where the tool was before the call.
|
||||||
|
pos = ComputeEndPosition(subCall.Program, subCall.Offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code is Motion motion)
|
||||||
{
|
{
|
||||||
var pos2 = motion.EndPoint;
|
var pos2 = motion.EndPoint;
|
||||||
motion.Offset(-pos.X, -pos.Y);
|
motion.Offset(-pos.X, -pos.Y);
|
||||||
@@ -49,5 +61,37 @@ namespace OpenNest.Converters
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes the tool position after executing <paramref name="pgm"/>,
|
||||||
|
/// given that the program's frame origin is at <paramref name="startPos"/>
|
||||||
|
/// in the caller's frame. Walks nested sub-program calls recursively.
|
||||||
|
/// </summary>
|
||||||
|
private static Vector ComputeEndPosition(Program pgm, Vector startPos)
|
||||||
|
{
|
||||||
|
var pos = startPos;
|
||||||
|
|
||||||
|
for (int i = 0; i < pgm.Codes.Count; ++i)
|
||||||
|
{
|
||||||
|
var code = pgm.Codes[i];
|
||||||
|
|
||||||
|
if (code is SubProgramCall subCall && subCall.Program != null)
|
||||||
|
{
|
||||||
|
// Nested sub's frame origin in the caller's frame is startPos + Offset.
|
||||||
|
pos = ComputeEndPosition(subCall.Program, startPos + subCall.Offset);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (code is Motion motion)
|
||||||
|
{
|
||||||
|
if (pgm.Mode == Mode.Incremental)
|
||||||
|
pos = pos + motion.EndPoint;
|
||||||
|
else
|
||||||
|
pos = startPos + motion.EndPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ namespace OpenNest.Converters
|
|||||||
|
|
||||||
private static void AddProgram(Program program, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
private static void AddProgram(Program program, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||||
{
|
{
|
||||||
|
// Capture the frame origin at entry. Sub-program Offsets are relative
|
||||||
|
// to this fixed origin, not to the current tool position.
|
||||||
|
var frameOrigin = curpos;
|
||||||
mode = program.Mode;
|
mode = program.Mode;
|
||||||
|
|
||||||
for (int i = 0; i < program.Length; ++i)
|
for (int i = 0; i < program.Length; ++i)
|
||||||
@@ -43,20 +46,13 @@ namespace OpenNest.Converters
|
|||||||
case CodeType.SubProgramCall:
|
case CodeType.SubProgramCall:
|
||||||
var subpgm = (SubProgramCall)code;
|
var subpgm = (SubProgramCall)code;
|
||||||
var savedMode = mode;
|
var savedMode = mode;
|
||||||
var savedPos = curpos;
|
|
||||||
|
|
||||||
// Position the sub-program at savedPos + Offset.
|
// The sub-program's frame origin in this program's frame is
|
||||||
// savedPos is the base position ((0,0) here, Part.Location in rendering).
|
// frameOrigin + Offset — independent of current tool position.
|
||||||
// Offset is the hole center in drawing-local coordinates.
|
curpos = new Vector(frameOrigin.X + subpgm.Offset.X, frameOrigin.Y + subpgm.Offset.Y);
|
||||||
curpos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
|
|
||||||
|
|
||||||
AddProgram(subpgm.Program, ref mode, ref curpos, ref geometry);
|
AddProgram(subpgm.Program, ref mode, ref curpos, ref geometry);
|
||||||
mode = savedMode;
|
mode = savedMode;
|
||||||
|
|
||||||
// Restore curpos: ConvertMode.ToIncremental skips SubProgramCalls
|
|
||||||
// when computing deltas, so subsequent incremental codes expect
|
|
||||||
// curpos to be where it was before the call.
|
|
||||||
curpos = savedPos;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace OpenNest
|
||||||
|
{
|
||||||
|
public interface IMaterialProvidingPostProcessor
|
||||||
|
{
|
||||||
|
IEnumerable<string> GetMaterialNames();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace OpenNest
|
||||||
|
{
|
||||||
|
public interface IPostProcessorNestAware
|
||||||
|
{
|
||||||
|
void PrepareForNest(Nest nest);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace OpenNest.Engine.Strategies
|
|||||||
public int PlateNumber { get; init; }
|
public int PlateNumber { get; init; }
|
||||||
public CancellationToken Token { get; init; }
|
public CancellationToken Token { get; init; }
|
||||||
public IProgress<NestProgress> Progress { get; init; }
|
public IProgress<NestProgress> Progress { get; init; }
|
||||||
public FillPolicy Policy { get; init; }
|
public FillPolicy Policy { get; init; } = new FillPolicy(new DefaultFillComparer());
|
||||||
public int MaxQuantity { get; init; }
|
public int MaxQuantity { get; init; }
|
||||||
public PartType PartType { get; set; }
|
public PartType PartType { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
namespace OpenNest.IO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Options controlling how <see cref="CadImporter"/> loads a CAD file
|
||||||
|
/// and builds a <see cref="Drawing"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class CadImportOptions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Detector name to use for bend detection. Null = auto-detect.
|
||||||
|
/// </summary>
|
||||||
|
public string BendDetectorName { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When false, skips bend detection entirely. Default true.
|
||||||
|
/// </summary>
|
||||||
|
public bool DetectBends { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Override the drawing name. Null = filename without extension.
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Required quantity on the produced drawing. Default 1.
|
||||||
|
/// </summary>
|
||||||
|
public int Quantity { get; set; } = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Customer name on the produced drawing. Default null.
|
||||||
|
/// </summary>
|
||||||
|
public string Customer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a default options instance.
|
||||||
|
/// </summary>
|
||||||
|
public static CadImportOptions Default => new CadImportOptions();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using OpenNest.Bending;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
|
namespace OpenNest.IO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Intermediate result of <see cref="CadImporter.Import"/>. Holds raw loaded
|
||||||
|
/// geometry and detected bends. Callers may mutate <see cref="Entities"/> and
|
||||||
|
/// <see cref="Bends"/> before passing to <see cref="CadImporter.BuildDrawing"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class CadImportResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All entities loaded from the source file, including promoted bend
|
||||||
|
/// source entities. Mutable.
|
||||||
|
/// </summary>
|
||||||
|
public List<Entity> Entities { get; set; } = new List<Entity>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bends detected during import. Mutable — callers may add, remove,
|
||||||
|
/// or replace entries before building the drawing.
|
||||||
|
/// </summary>
|
||||||
|
public List<Bend> Bends { get; set; } = new List<Bend>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Bounding box of <see cref="Entities"/> at import time. May be stale
|
||||||
|
/// if callers mutate <see cref="Entities"/>; recompute if needed.
|
||||||
|
/// </summary>
|
||||||
|
public Box Bounds { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Absolute path to the source file.
|
||||||
|
/// </summary>
|
||||||
|
public string SourcePath { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default drawing name (filename without extension, unless overridden).
|
||||||
|
/// </summary>
|
||||||
|
public string Name { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenNest.Bending;
|
||||||
|
using OpenNest.Converters;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
using OpenNest.IO.Bending;
|
||||||
|
|
||||||
|
namespace OpenNest.IO
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Shared service that converts a CAD source file into a fully-populated
|
||||||
|
/// <see cref="Drawing"/>. Used by the UI, console, MCP, API, and training
|
||||||
|
/// tools so all code paths produce identical drawings.
|
||||||
|
/// </summary>
|
||||||
|
public static class CadImporter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Load a DXF file, run bend detection, and return a mutable result
|
||||||
|
/// ready for interactive editing or direct conversion to a Drawing.
|
||||||
|
/// </summary>
|
||||||
|
public static CadImportResult Import(string path, CadImportOptions options = null)
|
||||||
|
{
|
||||||
|
options ??= CadImportOptions.Default;
|
||||||
|
|
||||||
|
var dxf = Dxf.Import(path);
|
||||||
|
|
||||||
|
var bends = new List<Bend>();
|
||||||
|
if (options.DetectBends && dxf.Document != null)
|
||||||
|
{
|
||||||
|
bends = options.BendDetectorName == null
|
||||||
|
? BendDetectorRegistry.AutoDetect(dxf.Document)
|
||||||
|
: BendDetectorRegistry.GetByName(options.BendDetectorName)
|
||||||
|
?.DetectBends(dxf.Document)
|
||||||
|
?? new List<Bend>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bend.UpdateEtchEntities(dxf.Entities, bends);
|
||||||
|
|
||||||
|
return new CadImportResult
|
||||||
|
{
|
||||||
|
Entities = dxf.Entities,
|
||||||
|
Bends = bends,
|
||||||
|
Bounds = dxf.Entities.GetBoundingBox(),
|
||||||
|
SourcePath = path,
|
||||||
|
Name = options.Name ?? Path.GetFileNameWithoutExtension(path),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convenience for headless callers: Import a file and build a Drawing
|
||||||
|
/// in a single call, using all loaded entities and detected bends.
|
||||||
|
/// </summary>
|
||||||
|
public static Drawing ImportDrawing(string path, CadImportOptions options = null)
|
||||||
|
{
|
||||||
|
options ??= CadImportOptions.Default;
|
||||||
|
var result = Import(path, options);
|
||||||
|
return BuildDrawing(
|
||||||
|
result,
|
||||||
|
result.Entities,
|
||||||
|
result.Bends,
|
||||||
|
options.Quantity,
|
||||||
|
options.Customer,
|
||||||
|
editedProgram: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Build a fully-populated <see cref="Drawing"/> from an import result plus
|
||||||
|
/// the caller's current entity and bend state. UI callers pass the currently
|
||||||
|
/// visible subset; headless callers pass the full lists.
|
||||||
|
///
|
||||||
|
/// The produced drawing has:
|
||||||
|
/// - Program generated from the visible entities, with its first rapid moved
|
||||||
|
/// to the origin and the pierce location stored in Source.Offset
|
||||||
|
/// - SourceEntities containing all non-bend-source entities from the result
|
||||||
|
/// - SuppressedEntityIds containing entities whose layer or IsVisible is false
|
||||||
|
/// - Bends copied from the provided list
|
||||||
|
/// - Customer, Quantity, Source.Path from options / result
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="result">Import result from <see cref="Import"/>.</param>
|
||||||
|
/// <param name="entities">
|
||||||
|
/// Entities to build the program from. Typically the currently visible subset.
|
||||||
|
/// </param>
|
||||||
|
/// <param name="bends">Bends to attach to the drawing.</param>
|
||||||
|
/// <param name="quantity">Required quantity.</param>
|
||||||
|
/// <param name="customer">Customer name, or null.</param>
|
||||||
|
/// <param name="editedProgram">
|
||||||
|
/// When non-null, replaces the generated program (used by the UI to honor
|
||||||
|
/// in-place G-code edits). Source.Offset is still populated from the
|
||||||
|
/// generated program so round-trips stay consistent.
|
||||||
|
/// </param>
|
||||||
|
public static Drawing BuildDrawing(
|
||||||
|
CadImportResult result,
|
||||||
|
IEnumerable<Entity> entities,
|
||||||
|
IEnumerable<Bend> bends,
|
||||||
|
int quantity,
|
||||||
|
string customer,
|
||||||
|
OpenNest.CNC.Program editedProgram)
|
||||||
|
{
|
||||||
|
var visible = entities as IList<Entity> ?? new List<Entity>(entities);
|
||||||
|
var bendList = bends as IList<Bend> ?? new List<Bend>(bends);
|
||||||
|
|
||||||
|
var normalized = ShapeProfile.NormalizeEntities(visible);
|
||||||
|
var pgm = ConvertGeometry.ToProgram(normalized);
|
||||||
|
|
||||||
|
var offset = Vector.Zero;
|
||||||
|
if (pgm != null && pgm.Codes.Count > 0 && pgm[0].Type == OpenNest.CNC.CodeType.RapidMove)
|
||||||
|
{
|
||||||
|
var rapid = (OpenNest.CNC.RapidMove)pgm[0];
|
||||||
|
offset = rapid.EndPoint;
|
||||||
|
pgm.Offset(-offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
var drawing = new Drawing(result.Name)
|
||||||
|
{
|
||||||
|
Color = Drawing.GetNextColor(),
|
||||||
|
Customer = customer,
|
||||||
|
};
|
||||||
|
drawing.Source.Path = result.SourcePath;
|
||||||
|
drawing.Source.Offset = offset;
|
||||||
|
drawing.Quantity.Required = quantity;
|
||||||
|
drawing.Bends.AddRange(bendList);
|
||||||
|
drawing.Program = editedProgram ?? pgm;
|
||||||
|
|
||||||
|
var bendSources = new HashSet<Entity>(
|
||||||
|
bendList.Where(b => b.SourceEntity != null).Select(b => b.SourceEntity));
|
||||||
|
|
||||||
|
drawing.SourceEntities = result.Entities
|
||||||
|
.Where(e => !bendSources.Contains(e))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
drawing.SuppressedEntityIds = new HashSet<System.Guid>(
|
||||||
|
drawing.SourceEntities
|
||||||
|
.Where(e => !(e.Layer != null && e.Layer.IsVisible && e.IsVisible))
|
||||||
|
.Select(e => e.Id));
|
||||||
|
|
||||||
|
return drawing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using OpenNest.Converters;
|
|
||||||
using OpenNest.Geometry;
|
|
||||||
using OpenNest.IO;
|
using OpenNest.IO;
|
||||||
using OpenNest.Shapes;
|
using OpenNest.Shapes;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -96,24 +94,18 @@ namespace OpenNest.Mcp.Tools
|
|||||||
if (!File.Exists(path))
|
if (!File.Exists(path))
|
||||||
return $"Error: file not found: {path}";
|
return $"Error: file not found: {path}";
|
||||||
|
|
||||||
var geometry = Dxf.GetGeometry(path);
|
try
|
||||||
|
{
|
||||||
|
var drawing = CadImporter.ImportDrawing(path, new CadImportOptions { Name = name });
|
||||||
|
_session.Drawings.Add(drawing);
|
||||||
|
|
||||||
if (geometry.Count == 0)
|
var bbox = drawing.Program.BoundingBox();
|
||||||
return "Error: failed to read DXF file or no geometry found";
|
return $"Imported drawing '{drawing.Name}': bbox={bbox.Width:F2} x {bbox.Length:F2}";
|
||||||
|
}
|
||||||
var normalized = ShapeProfile.NormalizeEntities(geometry);
|
catch (System.Exception ex)
|
||||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
{
|
||||||
|
return $"Error: failed to import '{path}': {ex.Message}";
|
||||||
if (pgm == null)
|
}
|
||||||
return "Error: failed to convert geometry to program";
|
|
||||||
|
|
||||||
var drawingName = name ?? Path.GetFileNameWithoutExtension(path);
|
|
||||||
var drawing = new Drawing(drawingName, pgm);
|
|
||||||
drawing.Color = Drawing.GetNextColor();
|
|
||||||
_session.Drawings.Add(drawing);
|
|
||||||
|
|
||||||
var bbox = pgm.BoundingBox();
|
|
||||||
return $"Imported drawing '{drawingName}': bbox={bbox.Width:F2} x {bbox.Length:F2}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[McpServerTool(Name = "create_drawing")]
|
[McpServerTool(Name = "create_drawing")]
|
||||||
|
|||||||
@@ -16,11 +16,16 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
{
|
{
|
||||||
private readonly CincinnatiPostConfig _config;
|
private readonly CincinnatiPostConfig _config;
|
||||||
private readonly CincinnatiFeatureWriter _featureWriter;
|
private readonly CincinnatiFeatureWriter _featureWriter;
|
||||||
|
private readonly CoordinateFormatter _fmt;
|
||||||
|
private readonly Dictionary<int, int> _holeSubprograms;
|
||||||
|
|
||||||
public CincinnatiPartSubprogramWriter(CincinnatiPostConfig config)
|
public CincinnatiPartSubprogramWriter(CincinnatiPostConfig config,
|
||||||
|
Dictionary<int, int> holeSubprograms = null)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_featureWriter = new CincinnatiFeatureWriter(config);
|
_featureWriter = new CincinnatiFeatureWriter(config);
|
||||||
|
_fmt = new CoordinateFormatter(config.PostedAccuracy);
|
||||||
|
_holeSubprograms = holeSubprograms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,6 +49,15 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
for (var i = 0; i < ordered.Count; i++)
|
for (var i = 0; i < ordered.Count; i++)
|
||||||
{
|
{
|
||||||
var (codes, isEtch) = ordered[i];
|
var (codes, isEtch) = ordered[i];
|
||||||
|
var isLastFeature = i == ordered.Count - 1;
|
||||||
|
|
||||||
|
// SubProgramCall features are emitted as M98 hole calls
|
||||||
|
if (codes.Count == 1 && codes[0] is SubProgramCall holeCall)
|
||||||
|
{
|
||||||
|
WriteHoleSubprogramCall(w, holeCall, i, isLastFeature);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var featureNumber = i == 0
|
var featureNumber = i == 0
|
||||||
? _config.FeatureLineNumberStart
|
? _config.FeatureLineNumberStart
|
||||||
: 1000 + i + 1;
|
: 1000 + i + 1;
|
||||||
@@ -55,7 +69,7 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
FeatureNumber = featureNumber,
|
FeatureNumber = featureNumber,
|
||||||
PartName = drawingName,
|
PartName = drawingName,
|
||||||
IsFirstFeatureOfPart = false,
|
IsFirstFeatureOfPart = false,
|
||||||
IsLastFeatureOnSheet = i == ordered.Count - 1,
|
IsLastFeatureOnSheet = isLastFeature,
|
||||||
IsSafetyHeadraise = false,
|
IsSafetyHeadraise = false,
|
||||||
IsExteriorFeature = false,
|
IsExteriorFeature = false,
|
||||||
IsEtch = isEtch,
|
IsEtch = isEtch,
|
||||||
@@ -70,6 +84,30 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
w.WriteLine($"M99 (END OF {drawingName})");
|
w.WriteLine($"M99 (END OF {drawingName})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WriteHoleSubprogramCall(TextWriter w, SubProgramCall call,
|
||||||
|
int featureIndex, bool isLastFeature)
|
||||||
|
{
|
||||||
|
var postSubNum = _holeSubprograms != null && _holeSubprograms.TryGetValue(call.Id, out var num)
|
||||||
|
? num : call.Id;
|
||||||
|
|
||||||
|
var featureNumber = featureIndex == 0
|
||||||
|
? _config.FeatureLineNumberStart
|
||||||
|
: 1000 + featureIndex + 1;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
if (_config.UseLineNumbers)
|
||||||
|
sb.Append($"N{featureNumber} ");
|
||||||
|
sb.Append($"G52 X{_fmt.FormatCoord(call.Offset.X)} Y{_fmt.FormatCoord(call.Offset.Y)}");
|
||||||
|
w.WriteLine(sb.ToString());
|
||||||
|
|
||||||
|
w.WriteLine($"M98 P{postSubNum}");
|
||||||
|
|
||||||
|
w.WriteLine("G52 X0 Y0");
|
||||||
|
|
||||||
|
if (!isLastFeature)
|
||||||
|
w.WriteLine("M47");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the program has no leading rapid, inserts a synthetic rapid at the
|
/// If the program has no leading rapid, inserts a synthetic rapid at the
|
||||||
/// last motion endpoint (the contour return point). This ensures the feature
|
/// last motion endpoint (the contour return point). This ensures the feature
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace OpenNest.Posts.Cincinnati
|
namespace OpenNest.Posts.Cincinnati
|
||||||
{
|
{
|
||||||
@@ -277,6 +279,24 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
[DisplayName("Etch Libraries")]
|
[DisplayName("Etch Libraries")]
|
||||||
[Description("Gas-to-library mapping for etch operations.")]
|
[Description("Gas-to-library mapping for etch operations.")]
|
||||||
public List<EtchLibraryEntry> EtchLibraries { get; set; } = new();
|
public List<EtchLibraryEntry> EtchLibraries { get; set; } = new();
|
||||||
|
|
||||||
|
[Category("B. Libraries")]
|
||||||
|
[DisplayName("Selected Library")]
|
||||||
|
[Description("Overrides Material/Thickness/Gas auto-resolution. Pick an existing entry from Material Libraries, or leave blank to auto-resolve.")]
|
||||||
|
[TypeConverter(typeof(MaterialLibraryNameConverter))]
|
||||||
|
public string SelectedLibrary { get; set; } = "";
|
||||||
|
|
||||||
|
public string FindBestLibrary(string materialName, double thickness)
|
||||||
|
{
|
||||||
|
if (MaterialLibraries == null || string.IsNullOrEmpty(materialName))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
return MaterialLibraries
|
||||||
|
.Where(e => string.Equals(e.Material, materialName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.OrderBy(e => System.Math.Abs(e.Thickness - thickness))
|
||||||
|
.Select(e => e.Library)
|
||||||
|
.FirstOrDefault() ?? "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MaterialLibraryEntry
|
public class MaterialLibraryEntry
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using OpenNest.CNC;
|
|||||||
|
|
||||||
namespace OpenNest.Posts.Cincinnati
|
namespace OpenNest.Posts.Cincinnati
|
||||||
{
|
{
|
||||||
public sealed class CincinnatiPostProcessor : IConfigurablePostProcessor
|
public sealed class CincinnatiPostProcessor : IConfigurablePostProcessor, IPostProcessorNestAware, IMaterialProvidingPostProcessor
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
@@ -25,6 +25,23 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
|
|
||||||
object IConfigurablePostProcessor.Config => Config;
|
object IConfigurablePostProcessor.Config => Config;
|
||||||
|
|
||||||
|
public IEnumerable<string> GetMaterialNames()
|
||||||
|
{
|
||||||
|
if (Config?.MaterialLibraries == null)
|
||||||
|
return System.Array.Empty<string>();
|
||||||
|
|
||||||
|
return Config.MaterialLibraries
|
||||||
|
.Select(e => e.Material)
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrepareForNest(Nest nest)
|
||||||
|
{
|
||||||
|
var materialName = nest?.Material?.Name ?? "";
|
||||||
|
var thickness = nest?.Thickness ?? 0.0;
|
||||||
|
Config.SelectedLibrary = Config.FindBestLibrary(materialName, thickness);
|
||||||
|
}
|
||||||
|
|
||||||
public CincinnatiPostProcessor()
|
public CincinnatiPostProcessor()
|
||||||
{
|
{
|
||||||
var configPath = GetConfigPath();
|
var configPath = GetConfigPath();
|
||||||
@@ -128,7 +145,8 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
// 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,
|
||||||
|
holeMapping.Count > 0 ? holeMapping : null);
|
||||||
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)
|
||||||
|
|||||||
@@ -226,12 +226,23 @@ public sealed class CincinnatiSheetWriter
|
|||||||
? _config.FeatureLineNumberStart
|
? _config.FeatureLineNumberStart
|
||||||
: 1000 + featureIndex + 1;
|
: 1000 + featureIndex + 1;
|
||||||
|
|
||||||
|
// Shift the local origin to the hole center via G52 (manual §1.52).
|
||||||
|
// G52 does not move the nozzle, so the sub-program's first rapid
|
||||||
|
// (the lead-in to the pierce point) takes the tool straight from the
|
||||||
|
// previous feature's end to pierce. The hole sub-program is authored
|
||||||
|
// in hole-local coordinates and resolves to `hole + local` under the
|
||||||
|
// shift. See docs/cincinnati-post-output.md for the full bracket.
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
if (_config.UseLineNumbers)
|
if (_config.UseLineNumbers)
|
||||||
sb.Append($"N{featureNumber} ");
|
sb.Append($"N{featureNumber} ");
|
||||||
sb.Append($"M98 P{postSubNum} X{_fmt.FormatCoord(call.Offset.X)} Y{_fmt.FormatCoord(call.Offset.Y)}");
|
sb.Append($"G52 X{_fmt.FormatCoord(call.Offset.X)} Y{_fmt.FormatCoord(call.Offset.Y)}");
|
||||||
w.WriteLine(sb.ToString());
|
w.WriteLine(sb.ToString());
|
||||||
|
|
||||||
|
w.WriteLine($"M98 P{postSubNum}");
|
||||||
|
|
||||||
|
// Cancel the local shift (manual §1.52).
|
||||||
|
w.WriteLine("G52 X0 Y0");
|
||||||
|
|
||||||
if (!isLastFeature)
|
if (!isLastFeature)
|
||||||
w.WriteLine("M47");
|
w.WriteLine("M47");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace OpenNest.Posts.Cincinnati
|
||||||
|
{
|
||||||
|
public sealed class MaterialLibraryNameConverter : StringConverter
|
||||||
|
{
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true;
|
||||||
|
|
||||||
|
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) => false;
|
||||||
|
|
||||||
|
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
|
||||||
|
{
|
||||||
|
var config = context?.Instance as CincinnatiPostConfig;
|
||||||
|
var names = new List<string> { "" };
|
||||||
|
|
||||||
|
if (config?.MaterialLibraries != null)
|
||||||
|
{
|
||||||
|
names.AddRange(config.MaterialLibraries
|
||||||
|
.Select(e => e.Library)
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(s => s, StringComparer.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StandardValuesCollection(names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,15 +10,20 @@ public sealed class MaterialLibraryResolver
|
|||||||
|
|
||||||
private readonly List<MaterialLibraryEntry> _materialLibraries;
|
private readonly List<MaterialLibraryEntry> _materialLibraries;
|
||||||
private readonly List<EtchLibraryEntry> _etchLibraries;
|
private readonly List<EtchLibraryEntry> _etchLibraries;
|
||||||
|
private readonly string _selectedLibrary;
|
||||||
|
|
||||||
public MaterialLibraryResolver(CincinnatiPostConfig config)
|
public MaterialLibraryResolver(CincinnatiPostConfig config)
|
||||||
{
|
{
|
||||||
_materialLibraries = config.MaterialLibraries ?? new List<MaterialLibraryEntry>();
|
_materialLibraries = config.MaterialLibraries ?? new List<MaterialLibraryEntry>();
|
||||||
_etchLibraries = config.EtchLibraries ?? new List<EtchLibraryEntry>();
|
_etchLibraries = config.EtchLibraries ?? new List<EtchLibraryEntry>();
|
||||||
|
_selectedLibrary = config.SelectedLibrary ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ResolveCutLibrary(string materialName, double thickness, string gas)
|
public string ResolveCutLibrary(string materialName, double thickness, string gas)
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_selectedLibrary))
|
||||||
|
return EnsureLibExtension(_selectedLibrary);
|
||||||
|
|
||||||
var entry = _materialLibraries.FirstOrDefault(e =>
|
var entry = _materialLibraries.FirstOrDefault(e =>
|
||||||
string.Equals(e.Material, materialName, StringComparison.OrdinalIgnoreCase) &&
|
string.Equals(e.Material, materialName, StringComparison.OrdinalIgnoreCase) &&
|
||||||
System.Math.Abs(e.Thickness - thickness) <= ThicknessTolerance &&
|
System.Math.Abs(e.Thickness - thickness) <= ThicknessTolerance &&
|
||||||
|
|||||||
@@ -6,11 +6,19 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="OpenNest.Posts.Cincinnati.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
<Target Name="CopyToPostsDir" AfterTargets="Build">
|
<Target Name="CopyToPostsDir" AfterTargets="Build">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostsDir>..\OpenNest\bin\$(Configuration)\$(TargetFramework)\Posts\</PostsDir>
|
<PostsDir>..\OpenNest\bin\$(Configuration)\$(TargetFramework)\Posts\</PostsDir>
|
||||||
|
<ConfigJson>$(MSBuildProjectDirectory)\OpenNest.Posts.Cincinnati.json</ConfigJson>
|
||||||
|
<DeployedConfigJson>$(PostsDir)OpenNest.Posts.Cincinnati.json</DeployedConfigJson>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<MakeDir Directories="$(PostsDir)" />
|
<MakeDir Directories="$(PostsDir)" />
|
||||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(PostsDir)" SkipUnchangedFiles="true" ContinueOnError="true" />
|
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(PostsDir)" SkipUnchangedFiles="true" ContinueOnError="true" />
|
||||||
|
<Copy SourceFiles="$(ConfigJson)" DestinationFolder="$(PostsDir)" SkipUnchangedFiles="true" ContinueOnError="true" Condition="!Exists('$(DeployedConfigJson)')" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
{
|
||||||
|
"ConfigurationName": "CL940",
|
||||||
|
"PostedUnits": "Inches",
|
||||||
|
"PostedAccuracy": 4,
|
||||||
|
"UseLineNumbers": true,
|
||||||
|
"FeatureLineNumberStart": 1,
|
||||||
|
"UseSheetSubprograms": true,
|
||||||
|
"SheetSubprogramStart": 101,
|
||||||
|
"UsePartSubprograms": false,
|
||||||
|
"PartSubprogramStart": 200,
|
||||||
|
"VariableDeclarationSubprogram": 100,
|
||||||
|
"CoordModeBetweenParts": "G92",
|
||||||
|
"ProcessParameterMode": "LibraryFile",
|
||||||
|
"DefaultAssistGas": "O2",
|
||||||
|
"DefaultEtchGas": "N2",
|
||||||
|
"UseExactStopMode": false,
|
||||||
|
"UseSpeedGas": false,
|
||||||
|
"UseAntiDive": true,
|
||||||
|
"UseSmartRapids": false,
|
||||||
|
"KerfCompensation": "ControllerSide",
|
||||||
|
"DefaultKerfSide": "Left",
|
||||||
|
"InteriorM47": "Always",
|
||||||
|
"ExteriorM47": "Always",
|
||||||
|
"M47OverrideDistanceThreshold": null,
|
||||||
|
"SafetyHeadraiseDistance": 2000,
|
||||||
|
"PalletExchange": "EndOfSheet",
|
||||||
|
"LeadInFeedratePercent": 0.5,
|
||||||
|
"LeadInArcLine2FeedratePercent": 0.5,
|
||||||
|
"LeadOutFeedratePercent": 0.5,
|
||||||
|
"CircleFeedrateMultiplier": 0.8,
|
||||||
|
"ArcFeedrate": "None",
|
||||||
|
"ArcFeedrateRanges": [
|
||||||
|
{ "MaxRadius": 0.125, "FeedratePercent": 0.25, "VariableNumber": 123 },
|
||||||
|
{ "MaxRadius": 0.75, "FeedratePercent": 0.5, "VariableNumber": 124 },
|
||||||
|
{ "MaxRadius": 4.5, "FeedratePercent": 0.8, "VariableNumber": 125 }
|
||||||
|
],
|
||||||
|
"UserVariableStart": 200,
|
||||||
|
"SheetWidthVariable": 110,
|
||||||
|
"SheetLengthVariable": 111,
|
||||||
|
"MaterialLibraries": [
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.032, "Gas": "AIR", "Library": "AL032AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.032, "Gas": "N2", "Library": "AL032N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.032, "Gas": "O2", "Library": "AL032O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.050, "Gas": "AIR", "Library": "AL050AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.050, "Gas": "N2", "Library": "AL050N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.050, "Gas": "O2", "Library": "AL050O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.063, "Gas": "AIR", "Library": "AL063AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.063, "Gas": "N2", "Library": "AL063N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.063, "Gas": "O2", "Library": "AL063O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.080, "Gas": "AIR", "Library": "AL080AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.080, "Gas": "N2", "Library": "AL080N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.080, "Gas": "O2", "Library": "AL080O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.090, "Gas": "AIR", "Library": "AL090AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.090, "Gas": "N2", "Library": "AL090N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.090, "Gas": "O2", "Library": "AL090O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.100, "Gas": "AIR", "Library": "AL100AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.100, "Gas": "N2", "Library": "AL100N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.100, "Gas": "O2", "Library": "AL100O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.125, "Gas": "AIR", "Library": "AL125AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.125, "Gas": "N2", "Library": "AL125N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.125, "Gas": "O2", "Library": "AL125O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.190, "Gas": "AIR", "Library": "AL190AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.190, "Gas": "N2", "Library": "AL190N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.190, "Gas": "O2", "Library": "AL190O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.250, "Gas": "AIR", "Library": "AL250AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.250, "Gas": "N2", "Library": "AL250N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.250, "Gas": "O2", "Library": "AL250O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.375, "Gas": "AIR", "Library": "AL375AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.375, "Gas": "N2", "Library": "AL375N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.375, "Gas": "O2", "Library": "AL375O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.500, "Gas": "AIR", "Library": "AL500AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.500, "Gas": "N2", "Library": "AL500N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.500, "Gas": "O2", "Library": "AL500O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.625, "Gas": "N2", "Library": "AL625N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.750, "Gas": "AIR", "Library": "AL750AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.750, "Gas": "N2", "Library": "AL750N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.750, "Gas": "O2", "Library": "AL750O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 1.000, "Gas": "AIR", "Library": "AL1000AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 1.000, "Gas": "N2", "Library": "AL1000N2" },
|
||||||
|
|
||||||
|
{ "Material": "Galvanized Steel", "Thickness": 0.135, "Gas": "N2", "Library": "GALV135N2" },
|
||||||
|
{ "Material": "Galvanized Steel", "Thickness": 0.188, "Gas": "N2", "Library": "GALV188N2" },
|
||||||
|
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.036, "Gas": "AIR", "Library": "MS036AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.036, "Gas": "N2", "Library": "MS036N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.048, "Gas": "AIR", "Library": "MS048AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.048, "Gas": "N2", "Library": "MS048N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.060, "Gas": "AIR", "Library": "MS060AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.060, "Gas": "N2", "Library": "MS060N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.075, "Gas": "AIR", "Library": "MS075AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.075, "Gas": "N2", "Library": "MS075N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.075, "Gas": "N2", "Library": "MS075N2FE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.090, "Gas": "N2", "Library": "MS090N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.105, "Gas": "AIR", "Library": "MS105AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.105, "Gas": "N2", "Library": "MS105N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.120, "Gas": "AIR", "Library": "MS120AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.120, "Gas": "N2", "Library": "MS120N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.120, "Gas": "N2", "Library": "MS120N2FE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.135, "Gas": "AIR", "Library": "MS135AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.135, "Gas": "N2", "Library": "MS135N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.135, "Gas": "N2", "Library": "MS135N2FE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.135, "Gas": "N2", "Library": "MS135N2Panel" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.188, "Gas": "AIR", "Library": "MS188AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.188, "Gas": "N2", "Library": "MS188N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.188, "Gas": "N2", "Library": "MS188N2FLOORPLATE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.188, "Gas": "O2", "Library": "MS188O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.250, "Gas": "AIR", "Library": "MS250AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.250, "Gas": "N2", "Library": "MS250N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.250, "Gas": "N2", "Library": "MS250N2FLOORPLATE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.250, "Gas": "O2", "Library": "MS250O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.313, "Gas": "O2", "Library": "MS313O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.375, "Gas": "O2", "Library": "MS375O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.500, "Gas": "N2", "Library": "MS500N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.500, "Gas": "O2", "Library": "MS500O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.625, "Gas": "O2", "Library": "MS625O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.750, "Gas": "O2", "Library": "MS750O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 1.000, "Gas": "O2", "Library": "MS1000O2" },
|
||||||
|
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.036, "Gas": "AIR", "Library": "SS036AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.036, "Gas": "N2", "Library": "SS036N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.048, "Gas": "AIR", "Library": "SS048AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.048, "Gas": "N2", "Library": "SS048N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.060, "Gas": "AIR", "Library": "SS060AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.060, "Gas": "N2", "Library": "SS060N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.075, "Gas": "AIR", "Library": "SS075AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.075, "Gas": "N2", "Library": "SS075N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.075, "Gas": "N2", "Library": "SS075N2FE" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.105, "Gas": "AIR", "Library": "SS105AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.105, "Gas": "N2", "Library": "SS105N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.105, "Gas": "N2", "Library": "SS105N2FE" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.120, "Gas": "AIR", "Library": "SS120AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.120, "Gas": "N2", "Library": "SS120N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.120, "Gas": "N2", "Library": "SS120N2FE" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.135, "Gas": "AIR", "Library": "SS135AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.135, "Gas": "N2", "Library": "SS135N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.135, "Gas": "N2", "Library": "SS135N2FE" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.188, "Gas": "AIR", "Library": "SS188AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.188, "Gas": "N2", "Library": "SS188N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.250, "Gas": "AIR", "Library": "SS250AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.250, "Gas": "N2", "Library": "SS250N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.313, "Gas": "N2", "Library": "SS313N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.375, "Gas": "AIR", "Library": "SS375AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.375, "Gas": "N2", "Library": "SS375N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.500, "Gas": "AIR", "Library": "SS500AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.500, "Gas": "N2", "Library": "SS500N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.625, "Gas": "N2", "Library": "SS625N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.750, "Gas": "AIR", "Library": "SS750AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.750, "Gas": "N2", "Library": "SS750N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 1.000, "Gas": "AIR", "Library": "SS1000AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 1.000, "Gas": "N2", "Library": "SS1000N2" },
|
||||||
|
|
||||||
|
{ "Material": "Phenolic", "Thickness": 0.0, "Gas": "", "Library": "Phenolic" },
|
||||||
|
{ "Material": "Gasket", "Thickness": 0.250, "Gas": "N2", "Library": "GASKET250N2" }
|
||||||
|
],
|
||||||
|
"EtchLibraries": [
|
||||||
|
{ "Gas": "AIR", "Library": "EtchAIR" },
|
||||||
|
{ "Gas": "N2", "Library": "EtchN2" },
|
||||||
|
{ "Gas": "N2", "Library": "EtchN2_fast" },
|
||||||
|
{ "Gas": "N2", "Library": "Etchn2_no_mark_pvc" },
|
||||||
|
{ "Gas": "O2", "Library": "EtchO2" },
|
||||||
|
{ "Gas": "O2", "Library": "ETCHO2FINE" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -44,6 +44,25 @@ public class HoleSubProgramTests
|
|||||||
Assert.Contains("Y2.5", str);
|
Assert.Contains("Y2.5", str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubProgramCall_ToString_IncludesOffsetAndRotation()
|
||||||
|
{
|
||||||
|
var call = new SubProgramCall { Id = 1000, Offset = new Vector(1.5, 2.5), Rotation = 30 };
|
||||||
|
var str = call.ToString();
|
||||||
|
Assert.Contains("P1000", str);
|
||||||
|
Assert.Contains("X1.5", str);
|
||||||
|
Assert.Contains("Y2.5", str);
|
||||||
|
Assert.Contains("R30", str);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SubProgramCall_ToString_OmitsZeroFields()
|
||||||
|
{
|
||||||
|
var call = new SubProgramCall { Id = 1000 };
|
||||||
|
var str = call.ToString();
|
||||||
|
Assert.Equal("G65 P1000", str);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Program_SubPrograms_EmptyByDefault()
|
public void Program_SubPrograms_EmptyByDefault()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using OpenNest.IO;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace OpenNest.Tests.IO
|
||||||
|
{
|
||||||
|
public class CadImporterTests
|
||||||
|
{
|
||||||
|
private static string TestDxf =>
|
||||||
|
Path.Combine("Bending", "TestData", "4526 A14 PT11.dxf");
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Import_LoadsEntitiesAndDetectsBends()
|
||||||
|
{
|
||||||
|
var result = CadImporter.Import(TestDxf);
|
||||||
|
|
||||||
|
Assert.NotNull(result);
|
||||||
|
Assert.NotEmpty(result.Entities);
|
||||||
|
Assert.NotNull(result.Bends);
|
||||||
|
Assert.NotNull(result.Bounds);
|
||||||
|
Assert.Equal(TestDxf, result.SourcePath);
|
||||||
|
Assert.Equal("4526 A14 PT11", result.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Import_WhenDetectBendsFalse_ReturnsEmptyBends()
|
||||||
|
{
|
||||||
|
var result = CadImporter.Import(TestDxf, new CadImportOptions { DetectBends = false });
|
||||||
|
|
||||||
|
Assert.Empty(result.Bends);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Import_WhenNameOverrideProvided_UsesOverride()
|
||||||
|
{
|
||||||
|
var result = CadImporter.Import(TestDxf, new CadImportOptions { Name = "custom" });
|
||||||
|
|
||||||
|
Assert.Equal("custom", result.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Import_WhenNamedDetectorDoesNotExist_ReturnsEmptyBends()
|
||||||
|
{
|
||||||
|
// Exercises the named-detector branch: when BendDetectorName doesn't
|
||||||
|
// match any registered detector, bends should be an empty list
|
||||||
|
// (not a crash, and no fall-through to auto-detect).
|
||||||
|
var result = CadImporter.Import(TestDxf,
|
||||||
|
new CadImportOptions { BendDetectorName = "__nonexistent__" });
|
||||||
|
|
||||||
|
Assert.Empty(result.Bends);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildDrawing_ProducesDrawingWithProgramAndMetadata()
|
||||||
|
{
|
||||||
|
var result = CadImporter.Import(TestDxf);
|
||||||
|
|
||||||
|
var drawing = CadImporter.BuildDrawing(
|
||||||
|
result,
|
||||||
|
result.Entities,
|
||||||
|
result.Bends,
|
||||||
|
quantity: 5,
|
||||||
|
customer: "ACME",
|
||||||
|
editedProgram: null);
|
||||||
|
|
||||||
|
Assert.NotNull(drawing);
|
||||||
|
Assert.Equal("4526 A14 PT11", drawing.Name);
|
||||||
|
Assert.Equal("ACME", drawing.Customer);
|
||||||
|
Assert.Equal(5, drawing.Quantity.Required);
|
||||||
|
Assert.Equal(TestDxf, drawing.Source.Path);
|
||||||
|
Assert.NotNull(drawing.Program);
|
||||||
|
Assert.NotEmpty(drawing.Program.Codes);
|
||||||
|
Assert.NotNull(drawing.SourceEntities);
|
||||||
|
Assert.NotEmpty(drawing.SourceEntities);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildDrawing_ExtractsFirstRapidAsSourceOffset()
|
||||||
|
{
|
||||||
|
var result = CadImporter.Import(TestDxf);
|
||||||
|
|
||||||
|
var drawing = CadImporter.BuildDrawing(result, result.Entities, result.Bends,
|
||||||
|
quantity: 1, customer: null, editedProgram: null);
|
||||||
|
|
||||||
|
Assert.NotNull(drawing.Source.Offset);
|
||||||
|
// After offset extraction, the program's first rapid must start at origin.
|
||||||
|
var firstRapid = (OpenNest.CNC.RapidMove)drawing.Program.Codes[0];
|
||||||
|
Assert.Equal(0, firstRapid.EndPoint.X, 6);
|
||||||
|
Assert.Equal(0, firstRapid.EndPoint.Y, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildDrawing_WhenEntityHidden_TracksSuppressedId()
|
||||||
|
{
|
||||||
|
var result = CadImporter.Import(TestDxf);
|
||||||
|
// Suppress the first non-bend-source entity
|
||||||
|
var bendSources = result.Bends
|
||||||
|
.Where(b => b.SourceEntity != null)
|
||||||
|
.Select(b => b.SourceEntity)
|
||||||
|
.ToHashSet();
|
||||||
|
var hidden = result.Entities.First(e => !bendSources.Contains(e));
|
||||||
|
hidden.IsVisible = false;
|
||||||
|
|
||||||
|
var drawing = CadImporter.BuildDrawing(result, result.Entities, result.Bends,
|
||||||
|
quantity: 1, customer: null, editedProgram: null);
|
||||||
|
|
||||||
|
Assert.Contains(hidden.Id, drawing.SuppressedEntityIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BuildDrawing_WhenEditedProgramProvided_UsesEditedProgram()
|
||||||
|
{
|
||||||
|
var result = CadImporter.Import(TestDxf);
|
||||||
|
var edited = new OpenNest.CNC.Program();
|
||||||
|
edited.MoveTo(new OpenNest.Geometry.Vector(0, 0));
|
||||||
|
|
||||||
|
var drawing = CadImporter.BuildDrawing(result, result.Entities, result.Bends,
|
||||||
|
quantity: 1, customer: null, editedProgram: edited);
|
||||||
|
|
||||||
|
Assert.Same(edited, drawing.Program);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ImportDrawing_ComposesImportAndBuild()
|
||||||
|
{
|
||||||
|
var drawing = CadImporter.ImportDrawing(TestDxf,
|
||||||
|
new CadImportOptions { Quantity = 3, Customer = "ACME" });
|
||||||
|
|
||||||
|
Assert.NotNull(drawing);
|
||||||
|
Assert.Equal("4526 A14 PT11", drawing.Name);
|
||||||
|
Assert.Equal(3, drawing.Quantity.Required);
|
||||||
|
Assert.Equal("ACME", drawing.Customer);
|
||||||
|
Assert.NotNull(drawing.Program);
|
||||||
|
Assert.NotNull(drawing.SourceEntities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using OpenNest;
|
using OpenNest;
|
||||||
using OpenNest.Engine.BestFit;
|
using OpenNest.Engine.BestFit;
|
||||||
using OpenNest.Engine.ML;
|
using OpenNest.Engine.ML;
|
||||||
using OpenNest.Geometry;
|
|
||||||
using OpenNest.Gpu;
|
using OpenNest.Gpu;
|
||||||
|
using OpenNest.Geometry;
|
||||||
using OpenNest.IO;
|
using OpenNest.IO;
|
||||||
using OpenNest.Training;
|
using OpenNest.Training;
|
||||||
using System;
|
using System;
|
||||||
@@ -128,17 +128,26 @@ int RunDataCollection(string dir, string dbPath, string saveDir, double s, strin
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var entities = Dxf.GetGeometry(file);
|
Drawing drawing;
|
||||||
if (entities.Count == 0)
|
try
|
||||||
|
{
|
||||||
|
drawing = CadImporter.ImportDrawing(file,
|
||||||
|
new CadImportOptions { DetectBends = false, Name = Path.GetFileName(file) });
|
||||||
|
}
|
||||||
|
catch (System.Exception ex)
|
||||||
|
{
|
||||||
|
Console.WriteLine($" - SKIP ({ex.Message})");
|
||||||
|
skippedGeometry++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drawing.Program == null || drawing.Program.Codes.Count == 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine(" - SKIP (no geometry)");
|
Console.WriteLine(" - SKIP (no geometry)");
|
||||||
skippedGeometry++;
|
skippedGeometry++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var drawing = new Drawing(Path.GetFileName(file));
|
|
||||||
var normalized = ShapeProfile.NormalizeEntities(entities);
|
|
||||||
drawing.Program = OpenNest.Converters.ConvertGeometry.ToProgram(normalized);
|
|
||||||
drawing.UpdateArea();
|
drawing.UpdateArea();
|
||||||
drawing.Color = PartColors[colorIndex % PartColors.Length];
|
drawing.Color = PartColors[colorIndex % PartColors.Length];
|
||||||
colorIndex++;
|
colorIndex++;
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ namespace OpenNest.Controls
|
|||||||
{
|
{
|
||||||
public static void DrawProgram(Graphics g, DrawControl view, Program pgm, ref Vector pos,
|
public static void DrawProgram(Graphics g, DrawControl view, Program pgm, ref Vector pos,
|
||||||
Pen pen, double spacing, float arrowSize)
|
Pen pen, double spacing, float arrowSize)
|
||||||
|
{
|
||||||
|
DrawProgram(g, view, pgm, pos, ref pos, pen, spacing, arrowSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DrawProgram(Graphics g, DrawControl view, Program pgm, Vector basePos, ref Vector pos,
|
||||||
|
Pen pen, double spacing, float arrowSize)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < pgm.Length; ++i)
|
for (var i = 0; i < pgm.Length; ++i)
|
||||||
{
|
{
|
||||||
@@ -19,10 +25,9 @@ namespace OpenNest.Controls
|
|||||||
var subpgm = (SubProgramCall)code;
|
var subpgm = (SubProgramCall)code;
|
||||||
if (subpgm.Program != null)
|
if (subpgm.Program != null)
|
||||||
{
|
{
|
||||||
var savedPos = pos;
|
var holeBase = basePos + subpgm.Offset;
|
||||||
pos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
|
pos = holeBase;
|
||||||
DrawProgram(g, view, subpgm.Program, ref pos, pen, spacing, arrowSize);
|
DrawProgram(g, view, subpgm.Program, holeBase, ref pos, pen, spacing, arrowSize);
|
||||||
pos = savedPos;
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -31,7 +36,7 @@ namespace OpenNest.Controls
|
|||||||
|
|
||||||
var endpt = pgm.Mode == Mode.Incremental
|
var endpt = pgm.Mode == Mode.Incremental
|
||||||
? motion.EndPoint + pos
|
? motion.EndPoint + pos
|
||||||
: motion.EndPoint;
|
: motion.EndPoint + basePos;
|
||||||
|
|
||||||
if (code.Type == CodeType.LinearMove)
|
if (code.Type == CodeType.LinearMove)
|
||||||
{
|
{
|
||||||
@@ -46,7 +51,7 @@ namespace OpenNest.Controls
|
|||||||
{
|
{
|
||||||
var center = pgm.Mode == Mode.Incremental
|
var center = pgm.Mode == Mode.Incremental
|
||||||
? arc.CenterPoint + pos
|
? arc.CenterPoint + pos
|
||||||
: arc.CenterPoint;
|
: arc.CenterPoint + basePos;
|
||||||
DrawArcArrows(g, view, pos, endpt, center, arc.Rotation, pen, spacing, arrowSize);
|
DrawArcArrows(g, view, pos, endpt, center, arc.Rotation, pen, spacing, arrowSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -395,8 +395,8 @@ namespace OpenNest.Controls
|
|||||||
var piercePoint = GetFirstPiercePoint(pgm, part.Location);
|
var piercePoint = GetFirstPiercePoint(pgm, part.Location);
|
||||||
DrawLine(g, pos, piercePoint, view.ColorScheme.RapidPen);
|
DrawLine(g, pos, piercePoint, view.ColorScheme.RapidPen);
|
||||||
|
|
||||||
pos = part.Location;
|
pos = piercePoint;
|
||||||
DrawRapids(g, pgm, ref pos, skipFirstRapid: true);
|
DrawRapids(g, pgm, part.Location, ref pos, skipFirstRapid: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,15 +409,13 @@ namespace OpenNest.Controls
|
|||||||
|
|
||||||
if (pgm[i] is Motion motion)
|
if (pgm[i] is Motion motion)
|
||||||
{
|
{
|
||||||
if (pgm.Mode == Mode.Incremental)
|
return motion.EndPoint + partLocation;
|
||||||
return motion.EndPoint + partLocation;
|
|
||||||
return motion.EndPoint;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return partLocation;
|
return partLocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRapids(Graphics g, Program pgm, ref Vector pos, bool skipFirstRapid = false)
|
private void DrawRapids(Graphics g, Program pgm, Vector basePos, ref Vector pos, bool skipFirstRapid = false)
|
||||||
{
|
{
|
||||||
var firstRapidSkipped = false;
|
var firstRapidSkipped = false;
|
||||||
|
|
||||||
@@ -425,62 +423,49 @@ namespace OpenNest.Controls
|
|||||||
{
|
{
|
||||||
var code = pgm[i];
|
var code = pgm[i];
|
||||||
|
|
||||||
if (code.Type == CodeType.SubProgramCall)
|
if (code is SubProgramCall { Program: { } program } call)
|
||||||
{
|
{
|
||||||
var subpgm = (SubProgramCall)code;
|
// A SubProgramCall is a coordinate-frame shift, not a physical
|
||||||
var program = subpgm.Program;
|
// rapid to the hole center. The Cincinnati post emits it as a
|
||||||
|
// G52 bracket, so the physical rapid is the sub-program's first
|
||||||
|
// motion, which goes straight from here to the lead-in pierce.
|
||||||
|
// Look ahead for that pierce point and draw the direct rapid,
|
||||||
|
// then recurse with skipFirstRapid so the sub doesn't also draw
|
||||||
|
// its first rapid on top. See docs/cincinnati-post-output.md.
|
||||||
|
var holeBase = basePos + call.Offset;
|
||||||
|
var firstPierce = GetFirstPiercePoint(program, holeBase);
|
||||||
|
|
||||||
if (program != null)
|
if (ShouldDrawRapid(skipFirstRapid, ref firstRapidSkipped))
|
||||||
{
|
DrawLine(g, pos, firstPierce, view.ColorScheme.RapidPen);
|
||||||
var holePos = new Vector(pos.X + subpgm.Offset.X, pos.Y + subpgm.Offset.Y);
|
|
||||||
|
|
||||||
// Draw rapid from current position to hole center
|
var subPos = holeBase;
|
||||||
if (!(skipFirstRapid && !firstRapidSkipped))
|
DrawRapids(g, program, holeBase, ref subPos, skipFirstRapid: true);
|
||||||
DrawLine(g, pos, holePos, view.ColorScheme.RapidPen);
|
pos = subPos;
|
||||||
else
|
|
||||||
firstRapidSkipped = true;
|
|
||||||
|
|
||||||
pos = holePos;
|
|
||||||
DrawRapids(g, program, ref pos);
|
|
||||||
// Don't restore pos — let it advance so the next hole's
|
|
||||||
// rapid starts from where this one ended.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else if (code is Motion motion)
|
||||||
{
|
{
|
||||||
var motion = code as Motion;
|
var endpt = pgm.Mode == Mode.Incremental
|
||||||
|
? motion.EndPoint + pos
|
||||||
|
: motion.EndPoint;
|
||||||
|
|
||||||
if (motion != null)
|
if (code.Type == CodeType.RapidMove && ShouldDrawRapid(skipFirstRapid, ref firstRapidSkipped))
|
||||||
{
|
DrawLine(g, pos, endpt, view.ColorScheme.RapidPen);
|
||||||
if (pgm.Mode == Mode.Incremental)
|
|
||||||
{
|
|
||||||
var endpt = motion.EndPoint + pos;
|
|
||||||
|
|
||||||
if (code.Type == CodeType.RapidMove)
|
pos = endpt;
|
||||||
{
|
|
||||||
if (skipFirstRapid && !firstRapidSkipped)
|
|
||||||
firstRapidSkipped = true;
|
|
||||||
else
|
|
||||||
DrawLine(g, pos, endpt, view.ColorScheme.RapidPen);
|
|
||||||
}
|
|
||||||
pos = endpt;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (code.Type == CodeType.RapidMove)
|
|
||||||
{
|
|
||||||
if (skipFirstRapid && !firstRapidSkipped)
|
|
||||||
firstRapidSkipped = true;
|
|
||||||
else
|
|
||||||
DrawLine(g, pos, motion.EndPoint, view.ColorScheme.RapidPen);
|
|
||||||
}
|
|
||||||
pos = motion.EndPoint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShouldDrawRapid(bool skipFirstRapid, ref bool firstRapidSkipped)
|
||||||
|
{
|
||||||
|
if (skipFirstRapid && !firstRapidSkipped)
|
||||||
|
{
|
||||||
|
firstRapidSkipped = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawAllPiercePoints(Graphics g)
|
private void DrawAllPiercePoints(Graphics g)
|
||||||
{
|
{
|
||||||
using var brush = new SolidBrush(Color.Red);
|
using var brush = new SolidBrush(Color.Red);
|
||||||
@@ -491,11 +476,11 @@ namespace OpenNest.Controls
|
|||||||
var part = view.Plate.Parts[i];
|
var part = view.Plate.Parts[i];
|
||||||
var pgm = part.Program;
|
var pgm = part.Program;
|
||||||
var pos = part.Location;
|
var pos = part.Location;
|
||||||
DrawProgramPiercePoints(g, pgm, ref pos, brush, pen);
|
DrawProgramPiercePoints(g, pgm, part.Location, ref pos, brush, pen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawProgramPiercePoints(Graphics g, Program pgm, ref Vector pos, Brush brush, Pen pen)
|
private void DrawProgramPiercePoints(Graphics g, Program pgm, Vector basePos, ref Vector pos, Brush brush, Pen pen)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < pgm.Length; ++i)
|
for (var i = 0; i < pgm.Length; ++i)
|
||||||
{
|
{
|
||||||
@@ -506,10 +491,9 @@ namespace OpenNest.Controls
|
|||||||
var subpgm = (SubProgramCall)code;
|
var subpgm = (SubProgramCall)code;
|
||||||
if (subpgm.Program != null)
|
if (subpgm.Program != null)
|
||||||
{
|
{
|
||||||
var savedPos = pos;
|
var holeBase = basePos + subpgm.Offset;
|
||||||
pos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
|
pos = holeBase;
|
||||||
DrawProgramPiercePoints(g, subpgm.Program, ref pos, brush, pen);
|
DrawProgramPiercePoints(g, subpgm.Program, holeBase, ref pos, brush, pen);
|
||||||
pos = savedPos;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -519,7 +503,7 @@ namespace OpenNest.Controls
|
|||||||
|
|
||||||
var endpt = pgm.Mode == Mode.Incremental
|
var endpt = pgm.Mode == Mode.Incremental
|
||||||
? motion.EndPoint + pos
|
? motion.EndPoint + pos
|
||||||
: motion.EndPoint;
|
: motion.EndPoint + basePos;
|
||||||
|
|
||||||
if (code.Type == CodeType.RapidMove)
|
if (code.Type == CodeType.RapidMove)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
using OpenNest.Bending;
|
|
||||||
using OpenNest.CNC;
|
|
||||||
using OpenNest.Converters;
|
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
using OpenNest.IO;
|
using OpenNest.IO;
|
||||||
using OpenNest.IO.Bending;
|
|
||||||
using OpenNest.IO.Bom;
|
using OpenNest.IO.Bom;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -470,33 +466,9 @@ namespace OpenNest.Forms
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = Dxf.Import(part.DxfPath);
|
var drawing = CadImporter.ImportDrawing(part.DxfPath,
|
||||||
|
new CadImportOptions { Quantity = part.Qty ?? 1 });
|
||||||
var bends = new List<Bend>();
|
|
||||||
if (result.Document != null)
|
|
||||||
bends = BendDetectorRegistry.AutoDetect(result.Document);
|
|
||||||
Bend.UpdateEtchEntities(result.Entities, bends);
|
|
||||||
|
|
||||||
var drawingName = Path.GetFileNameWithoutExtension(part.DxfPath);
|
|
||||||
var drawing = new Drawing(drawingName);
|
|
||||||
drawing.Color = Drawing.GetNextColor();
|
|
||||||
drawing.Source.Path = part.DxfPath;
|
|
||||||
drawing.Quantity.Required = part.Qty ?? 1;
|
|
||||||
drawing.Material = new Material(material);
|
drawing.Material = new Material(material);
|
||||||
if (bends.Count > 0)
|
|
||||||
drawing.Bends.AddRange(bends);
|
|
||||||
|
|
||||||
var normalized = ShapeProfile.NormalizeEntities(result.Entities);
|
|
||||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
|
||||||
|
|
||||||
if (pgm.Codes.Count > 0 && pgm[0].Type == CodeType.RapidMove)
|
|
||||||
{
|
|
||||||
var rapid = (RapidMove)pgm[0];
|
|
||||||
drawing.Source.Offset = rapid.EndPoint;
|
|
||||||
pgm.Offset(-rapid.EndPoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
drawing.Program = pgm;
|
|
||||||
nest.Drawings.Add(drawing);
|
nest.Drawings.Add(drawing);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ using OpenNest.Converters;
|
|||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
using OpenNest.IO;
|
using OpenNest.IO;
|
||||||
using OpenNest.IO.Bending;
|
using OpenNest.IO.Bending;
|
||||||
using OpenNest.Properties;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
@@ -74,36 +73,24 @@ namespace OpenNest.Forms
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = Dxf.Import(file);
|
var options = new CadImportOptions
|
||||||
|
{
|
||||||
|
BendDetectorName = detectorIndex == 0 ? null : detectorName,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = CadImporter.Import(file, options);
|
||||||
if (result.Entities.Count == 0)
|
if (result.Entities.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Compute bounds
|
|
||||||
var bounds = result.Entities.GetBoundingBox();
|
|
||||||
|
|
||||||
// Detect bends (detectorIndex/Name captured on UI thread)
|
|
||||||
var bends = new List<Bend>();
|
|
||||||
if (result.Document != null)
|
|
||||||
{
|
|
||||||
bends = detectorIndex == 0
|
|
||||||
? BendDetectorRegistry.AutoDetect(result.Document)
|
|
||||||
: BendDetectorRegistry.GetByName(detectorName)
|
|
||||||
?.DetectBends(result.Document)
|
|
||||||
?? new List<Bend>();
|
|
||||||
}
|
|
||||||
|
|
||||||
Bend.UpdateEtchEntities(result.Entities, bends);
|
|
||||||
|
|
||||||
var item = new FileListItem
|
var item = new FileListItem
|
||||||
{
|
{
|
||||||
Name = Path.GetFileNameWithoutExtension(file),
|
Name = result.Name,
|
||||||
Entities = result.Entities,
|
Entities = result.Entities,
|
||||||
Path = file,
|
Path = result.SourcePath,
|
||||||
Quantity = 1,
|
Quantity = 1,
|
||||||
Customer = string.Empty,
|
Customer = string.Empty,
|
||||||
Bends = bends,
|
Bends = result.Bends,
|
||||||
Bounds = bounds,
|
Bounds = result.Bounds,
|
||||||
EntityCount = result.Entities.Count
|
EntityCount = result.Entities.Count
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -368,7 +355,6 @@ namespace OpenNest.Forms
|
|||||||
: Path.GetTempPath();
|
: Path.GetTempPath();
|
||||||
|
|
||||||
var index = fileList.SelectedIndex;
|
var index = fileList.SelectedIndex;
|
||||||
var newItems = new List<string>();
|
|
||||||
|
|
||||||
var splitWriter = new SplitDxfWriter();
|
var splitWriter = new SplitDxfWriter();
|
||||||
var splitItems = new List<FileListItem>();
|
var splitItems = new List<FileListItem>();
|
||||||
@@ -381,7 +367,6 @@ namespace OpenNest.Forms
|
|||||||
var splitPath = GetUniquePath(Path.Combine(writableDir, splitName));
|
var splitPath = GetUniquePath(Path.Combine(writableDir, splitName));
|
||||||
|
|
||||||
splitWriter.Write(splitPath, splitDrawing);
|
splitWriter.Write(splitPath, splitDrawing);
|
||||||
newItems.Add(splitPath);
|
|
||||||
|
|
||||||
// Re-import geometry but keep bends from the split drawing
|
// Re-import geometry but keep bends from the split drawing
|
||||||
var result = Dxf.Import(splitPath);
|
var result = Dxf.Import(splitPath);
|
||||||
@@ -669,53 +654,35 @@ namespace OpenNest.Forms
|
|||||||
|
|
||||||
foreach (var item in fileList.Items)
|
foreach (var item in fileList.Items)
|
||||||
{
|
{
|
||||||
var entities = item.Entities.Where(e => e.Layer.IsVisible && e.IsVisible).ToList();
|
var visible = item.Entities
|
||||||
|
.Where(e => e.Layer.IsVisible && e.IsVisible)
|
||||||
if (entities.Count == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var drawing = new Drawing(item.Name);
|
|
||||||
drawing.Color = Drawing.GetNextColor();
|
|
||||||
drawing.Customer = item.Customer;
|
|
||||||
drawing.Source.Path = item.Path;
|
|
||||||
drawing.Quantity.Required = item.Quantity;
|
|
||||||
|
|
||||||
// Copy bends
|
|
||||||
if (item.Bends != null)
|
|
||||||
drawing.Bends.AddRange(item.Bends);
|
|
||||||
|
|
||||||
var normalized = ShapeProfile.NormalizeEntities(entities);
|
|
||||||
var pgm = ConvertGeometry.ToProgram(normalized);
|
|
||||||
var firstCode = pgm[0];
|
|
||||||
|
|
||||||
if (firstCode.Type == CodeType.RapidMove)
|
|
||||||
{
|
|
||||||
var rapid = (RapidMove)firstCode;
|
|
||||||
drawing.Source.Offset = rapid.EndPoint;
|
|
||||||
pgm.Offset(-rapid.EndPoint);
|
|
||||||
// Keep the rapid (now at origin) — it marks the contour
|
|
||||||
// start and is needed by the post for correct pierce placement.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item == CurrentItem && programEditor.IsDirty && programEditor.Program != null)
|
|
||||||
drawing.Program = programEditor.Program;
|
|
||||||
else
|
|
||||||
drawing.Program = pgm;
|
|
||||||
|
|
||||||
// Store all entities with stable GUIDs; track suppressed by ID
|
|
||||||
var bendSources = new HashSet<Entity>(
|
|
||||||
(item.Bends ?? new List<Bend>())
|
|
||||||
.Where(b => b.SourceEntity != null)
|
|
||||||
.Select(b => b.SourceEntity));
|
|
||||||
|
|
||||||
drawing.SourceEntities = item.Entities
|
|
||||||
.Where(e => !bendSources.Contains(e))
|
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
drawing.SuppressedEntityIds = new HashSet<Guid>(
|
if (visible.Count == 0)
|
||||||
drawing.SourceEntities
|
continue;
|
||||||
.Where(e => !(e.Layer.IsVisible && e.IsVisible))
|
|
||||||
.Select(e => e.Id));
|
// Rebuild a CadImportResult from the FileListItem's current state so
|
||||||
|
// BuildDrawing sees the user's edits (filters, suppressions, new bends).
|
||||||
|
var result = new CadImportResult
|
||||||
|
{
|
||||||
|
Entities = item.Entities,
|
||||||
|
Bends = item.Bends ?? new List<Bend>(),
|
||||||
|
Bounds = item.Bounds,
|
||||||
|
SourcePath = item.Path,
|
||||||
|
Name = item.Name,
|
||||||
|
};
|
||||||
|
|
||||||
|
var editedProgram = (item == CurrentItem && programEditor.IsDirty && programEditor.Program != null)
|
||||||
|
? programEditor.Program
|
||||||
|
: null;
|
||||||
|
|
||||||
|
var drawing = CadImporter.BuildDrawing(
|
||||||
|
result,
|
||||||
|
visible,
|
||||||
|
result.Bends,
|
||||||
|
item.Quantity,
|
||||||
|
item.Customer,
|
||||||
|
editedProgram);
|
||||||
|
|
||||||
drawings.Add(drawing);
|
drawings.Add(drawing);
|
||||||
|
|
||||||
@@ -780,9 +747,6 @@ namespace OpenNest.Forms
|
|||||||
item.SuppressedEntityIds = null;
|
item.SuppressedEntityIds = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Color GetNextColor() => Drawing.GetNextColor();
|
|
||||||
|
|
||||||
private static bool IsDirectoryWritable(string path)
|
private static bool IsDirectoryWritable(string path)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
Generated
+1
-1
@@ -81,8 +81,8 @@
|
|||||||
//
|
//
|
||||||
// tabControl1
|
// tabControl1
|
||||||
//
|
//
|
||||||
tabControl1.Controls.Add(tabPage1);
|
|
||||||
tabControl1.Controls.Add(tabPage2);
|
tabControl1.Controls.Add(tabPage2);
|
||||||
|
tabControl1.Controls.Add(tabPage1);
|
||||||
tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
|
tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
tabControl1.ItemSize = new System.Drawing.Size(100, 22);
|
tabControl1.ItemSize = new System.Drawing.Size(100, 22);
|
||||||
tabControl1.Location = new System.Drawing.Point(0, 0);
|
tabControl1.Location = new System.Drawing.Point(0, 0);
|
||||||
|
|||||||
+4
-3
@@ -63,7 +63,7 @@
|
|||||||
this.textBox2 = new System.Windows.Forms.TextBox();
|
this.textBox2 = new System.Windows.Forms.TextBox();
|
||||||
this.label5 = new System.Windows.Forms.Label();
|
this.label5 = new System.Windows.Forms.Label();
|
||||||
this.labelMaterial = new System.Windows.Forms.Label();
|
this.labelMaterial = new System.Windows.Forms.Label();
|
||||||
this.materialBox = new System.Windows.Forms.TextBox();
|
this.materialBox = new System.Windows.Forms.ComboBox();
|
||||||
this.tabPage2 = new System.Windows.Forms.TabPage();
|
this.tabPage2 = new System.Windows.Forms.TabPage();
|
||||||
this.tabPage3 = new System.Windows.Forms.TabPage();
|
this.tabPage3 = new System.Windows.Forms.TabPage();
|
||||||
this.notesBox = new System.Windows.Forms.TextBox();
|
this.notesBox = new System.Windows.Forms.TextBox();
|
||||||
@@ -516,9 +516,10 @@
|
|||||||
// materialBox
|
// materialBox
|
||||||
//
|
//
|
||||||
this.materialBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
|
this.materialBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.materialBox.FormattingEnabled = true;
|
||||||
this.materialBox.Location = new System.Drawing.Point(135, 159);
|
this.materialBox.Location = new System.Drawing.Point(135, 159);
|
||||||
this.materialBox.Name = "materialBox";
|
this.materialBox.Name = "materialBox";
|
||||||
this.materialBox.Size = new System.Drawing.Size(224, 22);
|
this.materialBox.Size = new System.Drawing.Size(224, 24);
|
||||||
this.materialBox.TabIndex = 11;
|
this.materialBox.TabIndex = 11;
|
||||||
//
|
//
|
||||||
// label3
|
// label3
|
||||||
@@ -729,6 +730,6 @@
|
|||||||
private System.Windows.Forms.RadioButton radioButton2;
|
private System.Windows.Forms.RadioButton radioButton2;
|
||||||
private System.Windows.Forms.Label label5;
|
private System.Windows.Forms.Label label5;
|
||||||
private System.Windows.Forms.Label labelMaterial;
|
private System.Windows.Forms.Label labelMaterial;
|
||||||
private System.Windows.Forms.TextBox materialBox;
|
private System.Windows.Forms.ComboBox materialBox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,9 @@ namespace OpenNest.Forms
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
foreach (var name in PostProcessorMaterials.Names)
|
||||||
|
materialBox.Items.Add(name);
|
||||||
|
|
||||||
timer = new Timer
|
timer = new Timer
|
||||||
{
|
{
|
||||||
SynchronizingObject = this,
|
SynchronizingObject = this,
|
||||||
|
|||||||
@@ -351,6 +351,9 @@ namespace OpenNest.Forms
|
|||||||
postProcessorMenuItem.Tag = postProcessor;
|
postProcessorMenuItem.Tag = postProcessor;
|
||||||
postProcessorMenuItem.Click += PostProcessor_Click;
|
postProcessorMenuItem.Click += PostProcessor_Click;
|
||||||
mnuNestPost.DropDownItems.Add(postProcessorMenuItem);
|
mnuNestPost.DropDownItems.Add(postProcessorMenuItem);
|
||||||
|
|
||||||
|
if (postProcessor is IMaterialProvidingPostProcessor materialProvider)
|
||||||
|
PostProcessorMaterials.AddFrom(materialProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1157,6 +1160,9 @@ namespace OpenNest.Forms
|
|||||||
if (postProcessor == null)
|
if (postProcessor == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (postProcessor is IPostProcessorNestAware nestAware)
|
||||||
|
nestAware.PrepareForNest(activeForm.Nest);
|
||||||
|
|
||||||
if (postProcessor is IConfigurablePostProcessor configurable)
|
if (postProcessor is IConfigurablePostProcessor configurable)
|
||||||
{
|
{
|
||||||
using var configForm = new PostProcessorConfigForm(configurable);
|
using var configForm = new PostProcessorConfigForm(configurable);
|
||||||
|
|||||||
@@ -98,6 +98,9 @@ namespace OpenNest
|
|||||||
private static void AddProgramSplit(GraphicsPath cutPath, GraphicsPath leadPath,
|
private static void AddProgramSplit(GraphicsPath cutPath, GraphicsPath leadPath,
|
||||||
Program pgm, Mode mode, ref Vector curpos)
|
Program pgm, Mode mode, ref Vector curpos)
|
||||||
{
|
{
|
||||||
|
// Capture the frame origin at entry. Sub-program Offsets are relative
|
||||||
|
// to this fixed origin, not to the current tool position.
|
||||||
|
var frameOrigin = curpos;
|
||||||
mode = pgm.Mode;
|
mode = pgm.Mode;
|
||||||
|
|
||||||
for (var i = 0; i < pgm.Length; ++i)
|
for (var i = 0; i < pgm.Length; ++i)
|
||||||
@@ -147,10 +150,8 @@ namespace OpenNest
|
|||||||
{
|
{
|
||||||
cutPath.StartFigure();
|
cutPath.StartFigure();
|
||||||
leadPath.StartFigure();
|
leadPath.StartFigure();
|
||||||
var savedPos = curpos;
|
curpos = new Vector(frameOrigin.X + subpgm.Offset.X, frameOrigin.Y + subpgm.Offset.Y);
|
||||||
curpos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
|
|
||||||
AddProgramSplit(cutPath, leadPath, subpgm.Program, mode, ref curpos);
|
AddProgramSplit(cutPath, leadPath, subpgm.Program, mode, ref curpos);
|
||||||
curpos = savedPos;
|
|
||||||
}
|
}
|
||||||
mode = tmpmode;
|
mode = tmpmode;
|
||||||
break;
|
break;
|
||||||
@@ -240,6 +241,9 @@ namespace OpenNest
|
|||||||
|
|
||||||
private static void AddProgram(GraphicsPath path, Program pgm, Mode mode, ref Vector curpos)
|
private static void AddProgram(GraphicsPath path, Program pgm, Mode mode, ref Vector curpos)
|
||||||
{
|
{
|
||||||
|
// Capture the frame origin at entry. Sub-program Offsets are relative
|
||||||
|
// to this fixed origin, not to the current tool position.
|
||||||
|
var frameOrigin = curpos;
|
||||||
mode = pgm.Mode;
|
mode = pgm.Mode;
|
||||||
GraphicsPath currentFigure = null;
|
GraphicsPath currentFigure = null;
|
||||||
|
|
||||||
@@ -308,10 +312,8 @@ namespace OpenNest
|
|||||||
|
|
||||||
if (subpgm.Program != null)
|
if (subpgm.Program != null)
|
||||||
{
|
{
|
||||||
var savedPos = curpos;
|
curpos = new Vector(frameOrigin.X + subpgm.Offset.X, frameOrigin.Y + subpgm.Offset.Y);
|
||||||
curpos = new Vector(savedPos.X + subpgm.Offset.X, savedPos.Y + subpgm.Offset.Y);
|
|
||||||
AddProgram(path, subpgm.Program, mode, ref curpos);
|
AddProgram(path, subpgm.Program, mode, ref curpos);
|
||||||
curpos = savedPos;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mode = tmpmode;
|
mode = tmpmode;
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace OpenNest
|
||||||
|
{
|
||||||
|
public static class PostProcessorMaterials
|
||||||
|
{
|
||||||
|
private static readonly List<string> materials = new();
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> Names => materials;
|
||||||
|
|
||||||
|
public static void AddFrom(IMaterialProvidingPostProcessor provider)
|
||||||
|
{
|
||||||
|
if (provider == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var name in provider.GetMaterialNames())
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(name)
|
||||||
|
&& !materials.Contains(name, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
materials.Add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
materials.Sort(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,212 @@
|
|||||||
|
# Cincinnati Post Output Reference
|
||||||
|
|
||||||
|
Reference for the G-code structure emitted by `OpenNest.Posts.Cincinnati`.
|
||||||
|
Every code listed here maps to a section in the Cincinnati Laser Programming
|
||||||
|
Manual (`docs/CINCINNATI LASER PROGRAMMING MANUAL.pdf`, EM-423 R-02/11).
|
||||||
|
Section numbers in parentheses (e.g. `§1.52`) refer to the manual.
|
||||||
|
|
||||||
|
If you add a new emission in the post, either cite the manual section it maps
|
||||||
|
to, or flag it here as a known custom extension. "Custom code" in this project
|
||||||
|
means something that is not documented in the manual but that the Cincinnati
|
||||||
|
control is known to accept — none exist today and we should not introduce any
|
||||||
|
without confirming the control behavior.
|
||||||
|
|
||||||
|
## Overall file structure
|
||||||
|
|
||||||
|
A generated file contains, in order:
|
||||||
|
|
||||||
|
1. **Main program** (`CincinnatiPreambleWriter.WriteMainProgram`)
|
||||||
|
Preamble, unit/mode setup, initial library, variable-declaration call, one
|
||||||
|
`M98 P<sheetSubNum>` call per plate quantity, and `M30` to end.
|
||||||
|
|
||||||
|
2. **Variable declaration sub-program** (`CincinnatiPreambleWriter.WriteVariableDeclaration`)
|
||||||
|
Machine variables (`#number = value`) used across the nest, terminated
|
||||||
|
with `M99`.
|
||||||
|
|
||||||
|
3. **Sheet sub-programs** (`CincinnatiSheetWriter.Write`), one per unique plate
|
||||||
|
layout. A sheet sub-program contains the cutting sequence for a whole
|
||||||
|
plate, either with features inlined or with `M98` calls into part
|
||||||
|
sub-programs.
|
||||||
|
|
||||||
|
4. **Part sub-programs** (`CincinnatiPartSubprogramWriter.Write`), one per
|
||||||
|
unique `(drawing, rotation)` pair, only emitted when
|
||||||
|
`Config.UsePartSubprograms` is enabled.
|
||||||
|
|
||||||
|
5. **Hole sub-programs** (`CincinnatiPartSubprogramWriter.Write` reused with a
|
||||||
|
`"HOLE"` label), one per unique hole geometry keyed by radius and lead-in
|
||||||
|
normal angle.
|
||||||
|
|
||||||
|
Sub-program bodies start with a `:<subNum>` label and end with `M99`.
|
||||||
|
|
||||||
|
## Feature blocks
|
||||||
|
|
||||||
|
A "feature" is a single contour: lead-in → cut moves → lead-out. Each feature
|
||||||
|
block in a sheet or sub-program output follows this order
|
||||||
|
(`CincinnatiFeatureWriter.Write`):
|
||||||
|
|
||||||
|
1. `G0 X_ Y_` — rapid to the pierce point (§1.00).
|
||||||
|
2. Optional part-name comment, only on the first feature of each part.
|
||||||
|
3. `G89 P<library>` — load process parameters (§2.89). `P` is a library file
|
||||||
|
name; the `(...)` trailing comment carries speed-class info.
|
||||||
|
4. `G84` (cut) or `G85` (etch / no-pierce) — pierce and start cut, or start
|
||||||
|
cut without pierce (§2.84 / §2.85).
|
||||||
|
5. `M130 (ANTI DIVE OFF)` — disable anti-dive, only if configured (§3.130).
|
||||||
|
6. Contour moves:
|
||||||
|
- `G41` (left) or `G42` (right) kerf compensation on the first cut move
|
||||||
|
(§1.41 / §1.42), suppressed for etch features.
|
||||||
|
- `G1 X_ Y_ [F<feedvar>]` — linear cut move (§1.01). Feedrate references a
|
||||||
|
machine variable such as `#148` and is emitted only when it changes.
|
||||||
|
- `G2 X_ Y_ I_ J_ [F<feedvar>]` (CW) or `G3` (CCW) — arc (§1.02 / §1.03).
|
||||||
|
`I`/`J` are incremental offsets from the current position to the center.
|
||||||
|
7. `G40` — cancel kerf compensation (§1.40), only if it was applied.
|
||||||
|
8. `M35` (or `M135` if SpeedGas is enabled) — beam off (§3.35 / §3.135).
|
||||||
|
9. `M131 (ANTI DIVE ON)` — re-enable anti-dive (§3.131).
|
||||||
|
10. `M47` or `M47 P<distance>` — raise Z-axis, unless this is the last feature
|
||||||
|
on the sheet (§3.47). A leading `/` (block delete, §5.6) is prepended when
|
||||||
|
the configured override distance exceeds the default.
|
||||||
|
|
||||||
|
Sheet sub-program and sheet-level feature calls add `G92 X#5021 Y#5022`
|
||||||
|
(§1.92) at the top so the local origin is anchored to the machine's current
|
||||||
|
absolute position (`#5021`/`#5022` are the machine X/Y system variables).
|
||||||
|
|
||||||
|
## Sub-program call patterns
|
||||||
|
|
||||||
|
There are two distinct call-site patterns, depending on whether the call
|
||||||
|
targets a whole-part sub-program or a hole sub-program.
|
||||||
|
|
||||||
|
### Part sub-program call (`WriteSubprogramCall`)
|
||||||
|
|
||||||
|
Used when `Config.UsePartSubprograms` is enabled. The tool physically rapids
|
||||||
|
to the part corner, then G92 sets the current position as the local origin,
|
||||||
|
the sub-program executes in its own local coordinate frame, and G92 restores
|
||||||
|
the original absolute position after return.
|
||||||
|
|
||||||
|
```
|
||||||
|
G0 X<left> Y<bottom> ; rapid to part bounding box corner (§1.00)
|
||||||
|
(PART: <name>)
|
||||||
|
G92 X0 Y0 ; set local origin at current position (§1.92)
|
||||||
|
M98 P<partSubNum> (<name>) ; call the part sub-program (§3.98)
|
||||||
|
G92 X<left> Y<bottom> ; restore the sheet coordinate system (§1.92)
|
||||||
|
M47 ; head raise unless this is the last part (§3.47)
|
||||||
|
```
|
||||||
|
|
||||||
|
This pattern uses G92 because the tool is physically positioned at the part
|
||||||
|
corner first. The sub-program's coordinates are part-local, so they are
|
||||||
|
interpreted against the new origin until G92 restores the sheet frame.
|
||||||
|
|
||||||
|
### Hole sub-program call (`WriteHoleSubprogramCall`)
|
||||||
|
|
||||||
|
Used for the `SubProgramCall` codes that a `ContourCuttingStrategy` emits for
|
||||||
|
each circular hole. Unlike parts, we do **not** want a physical rapid to the
|
||||||
|
hole center before calling — the sub-program's first rapid is the lead-in to
|
||||||
|
the pierce point, and the machine should travel directly from the previous
|
||||||
|
feature's end to that pierce.
|
||||||
|
|
||||||
|
```
|
||||||
|
G52 X<hole.x> Y<hole.y> ; shift local origin to hole center (§1.52)
|
||||||
|
M98 P<holeSubNum> ; call the shared hole sub-program (§3.98)
|
||||||
|
G52 X0 Y0 ; restore the original coordinate system (§1.52)
|
||||||
|
M47 ; head raise unless this is the last feature (§3.47)
|
||||||
|
```
|
||||||
|
|
||||||
|
G52 specifies the new origin in the current work coordinate system and — per
|
||||||
|
§1.52 — "does not move the cutting nozzle". The hole sub-program is written
|
||||||
|
in hole-local coordinates (origin at the hole center, produced by
|
||||||
|
`ContourCuttingStrategy`), so its first `G0 X_ Y_` resolves to `hole + local`
|
||||||
|
in absolute terms. That is the first physical motion, and it takes the tool
|
||||||
|
straight from wherever it was to the lead-in pierce point. G52 X0 Y0 cancels
|
||||||
|
the shift after `M99` returns control.
|
||||||
|
|
||||||
|
## G-code reference
|
||||||
|
|
||||||
|
These are every G/M code the post emits, grouped by category. Anything here is
|
||||||
|
documented in the programming manual. Anything not here should be audited the
|
||||||
|
next time the post is edited.
|
||||||
|
|
||||||
|
### Motion modes and contouring
|
||||||
|
|
||||||
|
| Code | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `G0 X_ Y_` | Rapid traverse | §1.00 |
|
||||||
|
| `G1 X_ Y_ F_` | Linear feedrate move | §1.01 |
|
||||||
|
| `G2 X_ Y_ I_ J_ F_` | Clockwise arc | §1.02 |
|
||||||
|
| `G3 X_ Y_ I_ J_ F_` | Counter-clockwise arc | §1.03 |
|
||||||
|
|
||||||
|
### Units and coordinate mode
|
||||||
|
|
||||||
|
| Code | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `G20` | Inch mode | §1.20 |
|
||||||
|
| `G21` | Metric mode | §1.21 |
|
||||||
|
| `G90` | Absolute mode | §1.90 |
|
||||||
|
|
||||||
|
### Kerf compensation
|
||||||
|
|
||||||
|
| Code | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `G40` | Cancel kerf compensation | §1.40 |
|
||||||
|
| `G41` | Kerf compensation, left side | §1.41 |
|
||||||
|
| `G42` | Kerf compensation, right side | §1.42 |
|
||||||
|
|
||||||
|
### Work coordinate systems
|
||||||
|
|
||||||
|
| Code | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `G52 X_ Y_` | Temporary local work coordinate offset. Does not move the tool. `G52 X0 Y0` cancels. | §1.52 |
|
||||||
|
| `G92 X_ Y_` | Sets the current tool position to `(X, Y)` in the work coordinate system, implicitly redefining the WCS origin. | §1.92 |
|
||||||
|
|
||||||
|
### Exact stop
|
||||||
|
|
||||||
|
| Code | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `G61` | Exact stop mode | §1.61 |
|
||||||
|
|
||||||
|
### Cutting operations (custom Cincinnati G-codes)
|
||||||
|
|
||||||
|
| Code | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `G84` | Pierce and start cut | §2.84 |
|
||||||
|
| `G85` | Start cut without pierce (used for etch) | §2.85 |
|
||||||
|
| `G89 P<file>` | Load process parameters from a library file | §2.89 |
|
||||||
|
| `G121` | Enable non-stop cutting (Smart Rapids) | §2.121 |
|
||||||
|
|
||||||
|
### Program flow
|
||||||
|
|
||||||
|
| Code | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `M30` | End of main program with rewind | §3.30 |
|
||||||
|
| `M98 P_` | Sub-program call. **Takes only `P` and `L` — not `X`/`Y`.** | §3.98 |
|
||||||
|
| `M99` | Return from sub-program | §3.99 |
|
||||||
|
|
||||||
|
### Machine state
|
||||||
|
|
||||||
|
| Code | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `M35` | Beam off | §3.35 |
|
||||||
|
| `M42` | Retract Z-axis | §3.42 |
|
||||||
|
| `M47 [P<dist>]` | Raise Z-axis, optionally by a distance | §3.47 |
|
||||||
|
| `M50` | Switch pallets | §3.50 |
|
||||||
|
| `M130` | Anti-dive off | §3.130 |
|
||||||
|
| `M131` | Anti-dive on | §3.131 |
|
||||||
|
| `M135` | Discharge current off (keeps assist gas on) | §3.135 |
|
||||||
|
|
||||||
|
### Comments, labels, and block delete
|
||||||
|
|
||||||
|
| Syntax | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `(text)` | Inline comment | §5.4 |
|
||||||
|
| `:<number>` | Sub-program label | §3.98 |
|
||||||
|
| `/<block>` | Block delete — operator can toggle the line off | §5.6 |
|
||||||
|
| `N<number>` | Line number, used by M99 P / GOTO targets | §5.5 |
|
||||||
|
|
||||||
|
## System variables referenced
|
||||||
|
|
||||||
|
| Variable | Description | Manual |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `#148` | Default cut feedrate variable (used in `F#148`) | §2.89 |
|
||||||
|
| `#5021` | Current machine X position | §6 (table of system variables) |
|
||||||
|
| `#5022` | Current machine Y position | §6 (table of system variables) |
|
||||||
|
|
||||||
|
Project-defined variables start at `Config.SheetWidthVariable` /
|
||||||
|
`Config.SheetLengthVariable` and at `Config.UserVariableStart`. Those ranges
|
||||||
|
are documented in `CincinnatiPostConfig.cs`.
|
||||||
Reference in New Issue
Block a user