Compare commits

...

14 Commits

Author SHA1 Message Date
aj 091e750e1b chore(cad-importer): remove dead code and cover named detector branch
- Drop CadImportResult.Document: no caller reads it after the
  migrations (BendDetectorRegistry runs inside CadImporter.Import
  itself, and downstream callers only consume the entity/bend data).
- Drop dead CadConverterForm.GetNextColor() helper: zero callers
  since GetDrawings stopped needing it.
- Drop stale 'using OpenNest.Properties;' and unused 'newItems'
  local in OnSplitClicked.
- Add Import_WhenNamedDetectorDoesNotExist_ReturnsEmptyBends to
  cover the previously untested named-detector branch in
  CadImporter.Import.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:13:44 -04:00
aj 87b965f895 refactor(ui): use CadImporter in BomImportForm
Replaces the hand-rolled DXF->Drawing pipeline (Dxf.Import + bend
detection + normalize + ConvertGeometry + pierce offset extraction)
with a single CadImporter.ImportDrawing call. Brings BomImportForm's
output in line with the rest of the callers: drawings now carry
Source.Offset, SourceEntities, SuppressedEntityIds, and detected bends,
and round-trip cleanly through nest files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:11:49 -04:00
aj 08f60690a7 docs: document CadImporter service in CLAUDE.md 2026-04-10 13:27:46 -04:00
aj a4609c816c refactor(ui): use CadImporter.BuildDrawing in CadConverterForm.GetDrawings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:24:26 -04:00
aj 5a4272696e refactor(ui): use CadImporter.Import in CadConverterForm.AddFile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:18:49 -04:00
aj 2cf03be360 refactor(training): use CadImporter for DXF import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:13:00 -04:00
aj 041e184d93 refactor(api): use CadImporter for DXF import in NestRunner
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:08:35 -04:00
aj 26df3174ea refactor(mcp): use CadImporter for DXF import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:05:28 -04:00
aj 0f5aace126 refactor(console): use CadImporter for DXF import
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 13:03:16 -04:00
aj 399f8dda6e feat: add CadImporter.ImportDrawing convenience method
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:59:06 -04:00
aj d921558b9c feat: add CadImporter.BuildDrawing stage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:51:58 -04:00
aj bf3e3e1f42 feat: add CadImporter.Import stage with bend detection
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 12:37:12 -04:00
aj e120ece014 feat: add CadImportResult data object for CadImporter 2026-04-10 12:28:17 -04:00
aj 264e8264be feat: add CadImportOptions for CadImporter service 2026-04-10 12:25:04 -04:00
11 changed files with 445 additions and 153 deletions
+3
View File
@@ -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).
- `ProgramReader` — G-code text parser.
- `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)
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.
- **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.
- **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.
+13 -9
View File
@@ -5,8 +5,6 @@ using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.IO;
namespace OpenNest.Api;
@@ -30,15 +28,21 @@ public static class NestRunner
if (!File.Exists(part.DxfPath))
throw new FileNotFoundException($"DXF file not found: {part.DxfPath}", part.DxfPath);
var geometry = Dxf.GetGeometry(part.DxfPath);
if (geometry.Count == 0)
Drawing drawing;
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}");
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);
}
+6 -17
View File
@@ -1,5 +1,4 @@
using OpenNest;
using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.IO;
using System;
@@ -241,25 +240,15 @@ static class NestConsole
static Drawing ImportDxf(string path)
{
var geometry = Dxf.GetGeometry(path);
if (geometry.Count == 0)
try
{
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;
}
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)
+39
View File
@@ -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();
}
}
+42
View File
@@ -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; }
}
}
+140
View File
@@ -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;
}
}
}
+10 -18
View File
@@ -1,6 +1,4 @@
using ModelContextProtocol.Server;
using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.IO;
using OpenNest.Shapes;
using System.ComponentModel;
@@ -96,24 +94,18 @@ namespace OpenNest.Mcp.Tools
if (!File.Exists(path))
return $"Error: file not found: {path}";
var geometry = Dxf.GetGeometry(path);
if (geometry.Count == 0)
return "Error: failed to read DXF file or no geometry found";
var normalized = ShapeProfile.NormalizeEntities(geometry);
var pgm = ConvertGeometry.ToProgram(normalized);
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();
try
{
var drawing = CadImporter.ImportDrawing(path, new CadImportOptions { Name = name });
_session.Drawings.Add(drawing);
var bbox = pgm.BoundingBox();
return $"Imported drawing '{drawingName}': bbox={bbox.Width:F2} x {bbox.Length:F2}";
var bbox = drawing.Program.BoundingBox();
return $"Imported drawing '{drawing.Name}': bbox={bbox.Width:F2} x {bbox.Length:F2}";
}
catch (System.Exception ex)
{
return $"Error: failed to import '{path}': {ex.Message}";
}
}
[McpServerTool(Name = "create_drawing")]
+138
View File
@@ -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);
}
}
}
+15 -6
View File
@@ -1,8 +1,8 @@
using OpenNest;
using OpenNest.Engine.BestFit;
using OpenNest.Engine.ML;
using OpenNest.Geometry;
using OpenNest.Gpu;
using OpenNest.Geometry;
using OpenNest.IO;
using OpenNest.Training;
using System;
@@ -128,17 +128,26 @@ int RunDataCollection(string dir, string dbPath, string saveDir, double s, strin
continue;
}
var entities = Dxf.GetGeometry(file);
if (entities.Count == 0)
Drawing drawing;
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)");
skippedGeometry++;
continue;
}
var drawing = new Drawing(Path.GetFileName(file));
var normalized = ShapeProfile.NormalizeEntities(entities);
drawing.Program = OpenNest.Converters.ConvertGeometry.ToProgram(normalized);
drawing.UpdateArea();
drawing.Color = PartColors[colorIndex % PartColors.Length];
colorIndex++;
+2 -30
View File
@@ -1,9 +1,5 @@
using OpenNest.Bending;
using OpenNest.CNC;
using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.IO;
using OpenNest.IO.Bending;
using OpenNest.IO.Bom;
using System;
using System.Collections.Generic;
@@ -470,33 +466,9 @@ namespace OpenNest.Forms
try
{
var result = Dxf.Import(part.DxfPath);
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;
var drawing = CadImporter.ImportDrawing(part.DxfPath,
new CadImportOptions { Quantity = part.Qty ?? 1 });
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);
}
catch (Exception ex)
+36 -72
View File
@@ -5,7 +5,6 @@ using OpenNest.Converters;
using OpenNest.Geometry;
using OpenNest.IO;
using OpenNest.IO.Bending;
using OpenNest.Properties;
using System;
using System.Collections.Generic;
using System.Drawing;
@@ -74,36 +73,24 @@ namespace OpenNest.Forms
{
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)
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
{
Name = Path.GetFileNameWithoutExtension(file),
Name = result.Name,
Entities = result.Entities,
Path = file,
Path = result.SourcePath,
Quantity = 1,
Customer = string.Empty,
Bends = bends,
Bounds = bounds,
Bends = result.Bends,
Bounds = result.Bounds,
EntityCount = result.Entities.Count
};
@@ -368,7 +355,6 @@ namespace OpenNest.Forms
: Path.GetTempPath();
var index = fileList.SelectedIndex;
var newItems = new List<string>();
var splitWriter = new SplitDxfWriter();
var splitItems = new List<FileListItem>();
@@ -381,7 +367,6 @@ namespace OpenNest.Forms
var splitPath = GetUniquePath(Path.Combine(writableDir, splitName));
splitWriter.Write(splitPath, splitDrawing);
newItems.Add(splitPath);
// Re-import geometry but keep bends from the split drawing
var result = Dxf.Import(splitPath);
@@ -669,53 +654,35 @@ namespace OpenNest.Forms
foreach (var item in fileList.Items)
{
var entities = item.Entities.Where(e => e.Layer.IsVisible && e.IsVisible).ToList();
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))
var visible = item.Entities
.Where(e => e.Layer.IsVisible && e.IsVisible)
.ToList();
drawing.SuppressedEntityIds = new HashSet<Guid>(
drawing.SourceEntities
.Where(e => !(e.Layer.IsVisible && e.IsVisible))
.Select(e => e.Id));
if (visible.Count == 0)
continue;
// 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);
@@ -780,9 +747,6 @@ namespace OpenNest.Forms
item.SuppressedEntityIds = null;
}
private static Color GetNextColor() => Drawing.GetNextColor();
private static bool IsDirectoryWritable(string path)
{
try