Files
OpenNest/OpenNest/Forms/CadConverterForm.cs
AJ Isaacs d5b5ab57e3 fix: assign colors to SpecialLayers and ConvertProgram entities
ConvertProgram.ToGeometry() created entities without setting Color,
defaulting to Color.Empty (alpha=0). After ededc7b switched from
Pens.White to per-entity colors, these rendered fully transparent.

- Add explicit colors to all SpecialLayers (Cut=White, Rapid=Gray, etc.)
- Set entity Color from layer in ConvertProgram for lines, arcs, circles
- Add GetEntityPen fallback: treat Empty/alpha-0 as White
- Add bend line rendering and selection in EntityView/CadConverterForm
- Fix SolidWorksBendDetector MText formatting strip for bend notes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 21:57:50 -04:00

409 lines
13 KiB
C#

using OpenNest.Bending;
using OpenNest.CNC;
using OpenNest.Controls;
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;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace OpenNest.Forms
{
public partial class CadConverterForm : Form
{
private static int colorIndex;
public CadConverterForm()
{
InitializeComponent();
fileList.SelectedIndexChanged += OnFileSelected;
filterPanel.FilterChanged += OnFilterChanged;
filterPanel.BendLineSelected += OnBendLineSelected;
filterPanel.BendLineRemoved += OnBendLineRemoved;
btnSplit.Click += OnSplitClicked;
numQuantity.ValueChanged += OnQuantityChanged;
txtCustomer.TextChanged += OnCustomerChanged;
cboBendDetector.SelectedIndexChanged += OnBendDetectorChanged;
// Populate bend detector dropdown
cboBendDetector.Items.Add("Auto");
foreach (var detector in BendDetectorRegistry.Detectors)
cboBendDetector.Items.Add(detector.Name);
cboBendDetector.SelectedIndex = 0;
// Drag & drop
AllowDrop = true;
DragEnter += OnDragEnter;
DragDrop += OnDragDrop;
}
private FileListItem CurrentItem => fileList.SelectedItem;
#region File Import
public void AddFile(string file) => AddFile(file, 0, null);
private void AddFile(string file, int detectorIndex, string detectorName)
{
try
{
var importer = new DxfImporter();
importer.SplinePrecision = Settings.Default.ImportSplinePrecision;
var result = importer.Import(file);
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>();
}
var item = new FileListItem
{
Name = Path.GetFileNameWithoutExtension(file),
Entities = result.Entities,
Path = file,
Quantity = 1,
Customer = string.Empty,
Bends = bends,
Bounds = bounds,
EntityCount = result.Entities.Count
};
if (InvokeRequired)
BeginInvoke((Action)(() => fileList.AddItem(item)));
else
fileList.AddItem(item);
}
catch (Exception ex)
{
MessageBox.Show($"Error importing \"{file}\": {ex.Message}");
}
}
public void AddFiles(IEnumerable<string> files)
{
var fileArray = files.ToArray();
// Capture UI state on main thread before entering parallel loop
var detectorIndex = cboBendDetector.SelectedIndex;
var detectorName = cboBendDetector.SelectedItem?.ToString();
System.Threading.Tasks.Task.Run(() =>
{
Parallel.ForEach(fileArray, file => AddFile(file, detectorIndex, detectorName));
});
}
#endregion
#region Event Handlers
private void OnFileSelected(object sender, int index)
{
var item = CurrentItem;
if (item == null)
{
ClearDetailBar();
return;
}
LoadItem(item);
}
private void LoadItem(FileListItem item)
{
entityView1.ClearPenCache();
entityView1.Entities.Clear();
entityView1.Entities.AddRange(item.Entities);
entityView1.Bends = item.Bends ?? new List<Bend>();
item.Entities.ForEach(e => e.IsVisible = true);
if (item.Entities.Any(e => e.Layer != null))
item.Entities.ForEach(e => e.Layer.IsVisible = true);
filterPanel.LoadItem(item.Entities, item.Bends);
numQuantity.Value = item.Quantity;
txtCustomer.Text = item.Customer ?? "";
var bounds = item.Bounds;
lblDimensions.Text = bounds != null
? $"{bounds.Width:0.#} x {bounds.Length:0.#}"
: "";
lblEntityCount.Text = $"{item.EntityCount} entities";
entityView1.ZoomToFit();
}
private void ClearDetailBar()
{
numQuantity.Value = 1;
txtCustomer.Text = "";
lblDimensions.Text = "";
lblEntityCount.Text = "";
entityView1.Entities.Clear();
entityView1.Invalidate();
}
private void OnFilterChanged(object sender, EventArgs e)
{
var item = CurrentItem;
if (item == null) return;
filterPanel.ApplyFilters(item.Entities);
entityView1.Invalidate();
}
private void OnBendLineSelected(object sender, int index)
{
entityView1.SelectedBendIndex = index;
entityView1.Invalidate();
}
private void OnBendLineRemoved(object sender, int index)
{
var item = CurrentItem;
if (item == null || index < 0 || index >= item.Bends.Count) return;
item.Bends.RemoveAt(index);
entityView1.Bends = item.Bends;
entityView1.SelectedBendIndex = -1;
filterPanel.LoadItem(item.Entities, item.Bends);
entityView1.Invalidate();
}
private void OnQuantityChanged(object sender, EventArgs e)
{
var item = CurrentItem;
if (item != null)
item.Quantity = (int)numQuantity.Value;
}
private void OnCustomerChanged(object sender, EventArgs e)
{
var item = CurrentItem;
if (item != null)
item.Customer = txtCustomer.Text;
}
private void OnBendDetectorChanged(object sender, EventArgs e)
{
// Re-run bend detection on current item if it has a document
// For now, bend detection only runs at import time
}
private void OnSplitClicked(object sender, EventArgs e)
{
var item = CurrentItem;
if (item == null) return;
var entities = item.Entities.Where(en => en.Layer.IsVisible && en.IsVisible).ToList();
if (entities.Count == 0) return;
var shape = new ShapeProfile(entities);
SetRotation(shape.Perimeter, RotationType.CW);
foreach (var cutout in shape.Cutouts)
SetRotation(cutout, RotationType.CCW);
var drawEntities = new List<Entity>();
drawEntities.AddRange(shape.Perimeter.Entities);
shape.Cutouts.ForEach(c => drawEntities.AddRange(c.Entities));
var pgm = ConvertGeometry.ToProgram(drawEntities);
if (pgm.Codes.Count > 0 && pgm[0].Type == CodeType.RapidMove)
{
var rapid = (RapidMove)pgm[0];
pgm.Offset(-rapid.EndPoint);
pgm.Codes.RemoveAt(0);
}
var drawing = new Drawing(item.Name, pgm);
using var form = new SplitDrawingForm(drawing);
if (form.ShowDialog(this) != DialogResult.OK || form.ResultDrawings?.Count <= 1)
return;
// Write split DXF files and re-import
var sourceDir = Path.GetDirectoryName(item.Path);
var baseName = Path.GetFileNameWithoutExtension(item.Path);
var writableDir = Directory.Exists(sourceDir) && IsDirectoryWritable(sourceDir)
? sourceDir
: Path.GetTempPath();
var index = fileList.SelectedIndex;
var newItems = new List<string>();
var splitWriter = new SplitDxfWriter();
for (var i = 0; i < form.ResultDrawings.Count; i++)
{
var splitDrawing = form.ResultDrawings[i];
// Assign bends from the source item — spatial filtering is a future enhancement
splitDrawing.Bends.AddRange(item.Bends);
var splitName = $"{baseName}_split{i + 1}.dxf";
var splitPath = GetUniquePath(Path.Combine(writableDir, splitName));
splitWriter.Write(splitPath, splitDrawing);
newItems.Add(splitPath);
}
// Remove original and add split files
fileList.RemoveAt(index);
foreach (var path in newItems)
AddFile(path);
if (writableDir != sourceDir)
MessageBox.Show($"Split files written to: {writableDir}", "Split Output",
MessageBoxButtons.OK, MessageBoxIcon.Information);
}
private void OnDragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
e.Effect = DragDropEffects.Copy;
}
private void OnDragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
var files = (string[])e.Data.GetData(DataFormats.FileDrop);
var dxfFiles = files.Where(f =>
f.EndsWith(".dxf", StringComparison.OrdinalIgnoreCase)).ToArray();
if (dxfFiles.Length > 0)
AddFiles(dxfFiles);
}
}
#endregion
#region Output
public List<Drawing> GetDrawings()
{
var drawings = new List<Drawing>();
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 = 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 shape = new ShapeProfile(entities);
SetRotation(shape.Perimeter, RotationType.CW);
foreach (var cutout in shape.Cutouts)
SetRotation(cutout, RotationType.CCW);
entities = new List<Entity>();
entities.AddRange(shape.Perimeter.Entities);
shape.Cutouts.ForEach(cutout => entities.AddRange(cutout.Entities));
var pgm = ConvertGeometry.ToProgram(entities);
var firstCode = pgm[0];
if (firstCode.Type == CodeType.RapidMove)
{
var rapid = (RapidMove)firstCode;
drawing.Source.Offset = rapid.EndPoint;
pgm.Offset(-rapid.EndPoint);
pgm.Codes.RemoveAt(0);
}
drawing.Program = pgm;
drawings.Add(drawing);
Thread.Sleep(20);
}
return drawings;
}
#endregion
#region Helpers
private static void SetRotation(Shape shape, RotationType rotation)
{
try
{
var dir = shape.ToPolygon(3).RotationDirection();
if (dir != rotation) shape.Reverse();
}
catch { }
}
private static Color GetNextColor()
{
var color = ColorScheme.PartColors[colorIndex % ColorScheme.PartColors.Length];
colorIndex++;
return color;
}
private static bool IsDirectoryWritable(string path)
{
try
{
var testFile = Path.Combine(path, $".writetest_{Guid.NewGuid()}");
File.WriteAllText(testFile, "");
File.Delete(testFile);
return true;
}
catch { return false; }
}
private static string GetUniquePath(string path)
{
if (!File.Exists(path)) return path;
var dir = Path.GetDirectoryName(path);
var name = Path.GetFileNameWithoutExtension(path);
var ext = Path.GetExtension(path);
var counter = 2;
while (File.Exists(path))
{
path = Path.Combine(dir, $"{name}_{counter}{ext}");
counter++;
}
return path;
}
#endregion
}
}