Sets CommandInProgress to block user interaction with SolidWorks while the DXF export is running, preventing accidental interference. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
482 lines
17 KiB
C#
482 lines
17 KiB
C#
using ExportDXF.Data;
|
|
using ExportDXF.Extensions;
|
|
using ExportDXF.Models;
|
|
using ExportDXF.Services;
|
|
using ExportDXF.ViewFlipDeciders;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
|
|
namespace ExportDXF.Forms
|
|
{
|
|
public partial class MainForm : Form
|
|
{
|
|
private readonly ISolidWorksService _solidWorksService;
|
|
private readonly IDxfExportService _exportService;
|
|
private readonly IFileExportService _fileExportService;
|
|
private readonly Func<ExportDxfDbContext> _dbContextFactory;
|
|
private CancellationTokenSource _cancellationTokenSource;
|
|
private readonly BindingList<LogEvent> _logEvents;
|
|
private readonly BindingList<BomItem> _bomItems;
|
|
private List<DrawingInfo> _allDrawings;
|
|
|
|
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IFileExportService fileExportService, Func<ExportDxfDbContext> dbContextFactory = null)
|
|
{
|
|
InitializeComponent();
|
|
_solidWorksService = solidWorksService ??
|
|
throw new ArgumentNullException(nameof(solidWorksService));
|
|
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
|
_exportService = exportService ??
|
|
throw new ArgumentNullException(nameof(exportService));
|
|
_fileExportService = fileExportService ??
|
|
throw new ArgumentNullException(nameof(fileExportService));
|
|
_dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext());
|
|
_logEvents = new BindingList<LogEvent>();
|
|
_bomItems = new BindingList<BomItem>();
|
|
_allDrawings = new List<DrawingInfo>();
|
|
InitializeViewFlipDeciders();
|
|
InitializeLogEventsGrid();
|
|
InitializeBomGrid();
|
|
InitializeDrawingDropdowns();
|
|
}
|
|
|
|
~MainForm()
|
|
{
|
|
_cancellationTokenSource?.Dispose();
|
|
_solidWorksService?.Dispose();
|
|
components?.Dispose();
|
|
Dispose(false);
|
|
}
|
|
|
|
protected override async void OnLoad(EventArgs e)
|
|
{
|
|
base.OnLoad(e);
|
|
runButton.Enabled = false;
|
|
await InitializeAsync();
|
|
}
|
|
|
|
private async Task InitializeAsync()
|
|
{
|
|
try
|
|
{
|
|
LogMessage("Connecting to SolidWorks, this may take a minute...");
|
|
await _solidWorksService.ConnectAsync();
|
|
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
|
LogMessage($"Output folder: {_fileExportService.OutputFolder}");
|
|
LogMessage("Ready");
|
|
UpdateActiveDocumentDisplay();
|
|
runButton.Enabled = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogMessage($"Failed to connect to SolidWorks: {ex.Message}", LogLevel.Error);
|
|
MessageBox.Show("Failed to connect to SolidWorks.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
Application.Exit();
|
|
}
|
|
}
|
|
|
|
private void InitializeViewFlipDeciders()
|
|
{
|
|
var items = ViewFlipDeciderFactory.GetAvailableDeciders()
|
|
.Select(d => new ViewFlipDeciderComboboxItem
|
|
{
|
|
Name = d.Name,
|
|
ViewFlipDecider = d
|
|
})
|
|
.ToList();
|
|
// Move "Automatic" to the top if it exists
|
|
var automatic = items.FirstOrDefault(i => i.Name == "Automatic");
|
|
if (automatic != null)
|
|
{
|
|
items.Remove(automatic);
|
|
items.Insert(0, automatic);
|
|
}
|
|
viewFlipDeciderBox.DataSource = items;
|
|
viewFlipDeciderBox.DisplayMember = "Name";
|
|
}
|
|
|
|
private void InitializeLogEventsGrid()
|
|
{
|
|
// Clear any existing columns first
|
|
logEventsDataGrid.Columns.Clear();
|
|
|
|
// Configure grid settings
|
|
logEventsDataGrid.AutoGenerateColumns = false;
|
|
logEventsDataGrid.AllowUserToAddRows = false;
|
|
logEventsDataGrid.AllowUserToDeleteRows = false;
|
|
logEventsDataGrid.ReadOnly = true;
|
|
logEventsDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
|
|
// Add columns
|
|
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(LogEvent.Time),
|
|
HeaderText = "Time",
|
|
Width = 80,
|
|
DefaultCellStyle = new DataGridViewCellStyle { Format = "HH:mm:ss" }
|
|
});
|
|
|
|
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(LogEvent.Level),
|
|
HeaderText = "Level",
|
|
Width = 70
|
|
});
|
|
|
|
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(LogEvent.Part),
|
|
HeaderText = "File",
|
|
Width = 180
|
|
});
|
|
|
|
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(LogEvent.Message),
|
|
HeaderText = "Message",
|
|
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
|
|
});
|
|
|
|
// Add row coloring based on log level
|
|
logEventsDataGrid.CellFormatting += LogEventsDataGrid_CellFormatting;
|
|
|
|
// Set the data source AFTER adding columns
|
|
logEventsDataGrid.DataSource = _logEvents;
|
|
}
|
|
|
|
private void InitializeBomGrid()
|
|
{
|
|
// Clear any existing columns first
|
|
bomDataGrid.Columns.Clear();
|
|
|
|
// Configure grid settings
|
|
bomDataGrid.AutoGenerateColumns = false;
|
|
bomDataGrid.AllowUserToAddRows = false;
|
|
bomDataGrid.AllowUserToDeleteRows = false;
|
|
bomDataGrid.ReadOnly = true;
|
|
bomDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
|
|
|
// Add columns
|
|
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(BomItem.ItemNo),
|
|
HeaderText = "Item #",
|
|
Width = 60
|
|
});
|
|
|
|
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(BomItem.PartNo),
|
|
HeaderText = "Part #",
|
|
Width = 100
|
|
});
|
|
|
|
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(BomItem.Qty),
|
|
HeaderText = "Qty",
|
|
Width = 50
|
|
});
|
|
|
|
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(BomItem.Description),
|
|
HeaderText = "Description",
|
|
Width = 200
|
|
});
|
|
|
|
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(BomItem.PartName),
|
|
HeaderText = "Part Name",
|
|
Width = 150
|
|
});
|
|
|
|
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(BomItem.ConfigurationName),
|
|
HeaderText = "Configuration",
|
|
Width = 120
|
|
});
|
|
|
|
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
|
{
|
|
DataPropertyName = nameof(BomItem.Material),
|
|
HeaderText = "Material",
|
|
Width = 120
|
|
});
|
|
|
|
// Set the data source AFTER adding columns
|
|
bomDataGrid.DataSource = _bomItems;
|
|
}
|
|
|
|
private void InitializeDrawingDropdowns()
|
|
{
|
|
try
|
|
{
|
|
using (var db = _dbContextFactory())
|
|
{
|
|
// Get all drawing numbers from the database
|
|
var drawingNumbers = db.ExportRecords
|
|
.Select(r => r.DrawingNumber)
|
|
.Where(d => !string.IsNullOrEmpty(d))
|
|
.Distinct()
|
|
.ToList();
|
|
|
|
// Parse into DrawingInfo objects
|
|
_allDrawings = drawingNumbers
|
|
.Select(DrawingInfo.Parse)
|
|
.Where(d => d != null)
|
|
.Distinct()
|
|
.OrderBy(d => d.EquipmentNo)
|
|
.ThenBy(d => d.DrawingNo)
|
|
.ToList();
|
|
|
|
// Get distinct equipment numbers
|
|
var equipmentNumbers = _allDrawings
|
|
.Select(d => d.EquipmentNo)
|
|
.Distinct()
|
|
.OrderBy(e => e)
|
|
.ToList();
|
|
|
|
// Populate equipment dropdown
|
|
equipmentBox.Items.Clear();
|
|
equipmentBox.Items.Add(""); // Empty option for "all"
|
|
foreach (var eq in equipmentNumbers)
|
|
{
|
|
equipmentBox.Items.Add(eq);
|
|
}
|
|
|
|
// Populate drawing dropdown with all drawings initially
|
|
UpdateDrawingDropdown();
|
|
|
|
// Wire up event handler for equipment selection change
|
|
equipmentBox.SelectedIndexChanged += EquipmentBox_SelectedIndexChanged;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Database might not exist yet - that's OK
|
|
System.Diagnostics.Debug.WriteLine($"Failed to load drawings from database: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void EquipmentBox_SelectedIndexChanged(object sender, EventArgs e)
|
|
{
|
|
UpdateDrawingDropdown();
|
|
}
|
|
|
|
private void UpdateDrawingDropdown()
|
|
{
|
|
var selectedEquipment = equipmentBox.SelectedItem?.ToString();
|
|
|
|
var filteredDrawings = string.IsNullOrEmpty(selectedEquipment)
|
|
? _allDrawings
|
|
: _allDrawings.Where(d => d.EquipmentNo == selectedEquipment).ToList();
|
|
|
|
drawingNoBox.Items.Clear();
|
|
drawingNoBox.Items.Add(""); // Empty option
|
|
foreach (var drawing in filteredDrawings)
|
|
{
|
|
drawingNoBox.Items.Add(drawing.DrawingNo);
|
|
}
|
|
|
|
if (drawingNoBox.Items.Count > 0)
|
|
{
|
|
drawingNoBox.SelectedIndex = 0;
|
|
}
|
|
}
|
|
|
|
private async void button1_Click(object sender, EventArgs e)
|
|
{
|
|
if (_cancellationTokenSource != null)
|
|
{
|
|
CancelExport();
|
|
}
|
|
else
|
|
{
|
|
await StartExportAsync();
|
|
}
|
|
}
|
|
|
|
private async Task StartExportAsync()
|
|
{
|
|
try
|
|
{
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
var token = _cancellationTokenSource.Token;
|
|
UpdateUIForExportStart();
|
|
|
|
var activeDoc = _solidWorksService.GetActiveDocument();
|
|
if (activeDoc == null)
|
|
{
|
|
LogMessage("No active document.", LogLevel.Error);
|
|
return;
|
|
}
|
|
|
|
// Parse drawing number from active document title
|
|
var drawingInfo = DrawingInfo.Parse(activeDoc.Title);
|
|
var filePrefix = drawingInfo != null ? $"{drawingInfo.EquipmentNo} {drawingInfo.DrawingNo}" : activeDoc.Title;
|
|
|
|
var viewFlipDecider = GetSelectedViewFlipDecider();
|
|
var exportContext = new ExportContext
|
|
{
|
|
ActiveDocument = activeDoc,
|
|
ViewFlipDecider = viewFlipDecider,
|
|
FilePrefix = filePrefix,
|
|
EquipmentId = null,
|
|
CancellationToken = token,
|
|
ProgressCallback = (msg, level, file) => LogMessage(msg, level, file),
|
|
BomItemCallback = AddBomItem
|
|
};
|
|
|
|
// Clear previous BOM items
|
|
_bomItems.Clear();
|
|
|
|
LogMessage($"Started at {DateTime.Now:t}");
|
|
LogMessage($"Exporting to: {_fileExportService.OutputFolder}");
|
|
|
|
_solidWorksService.SetCommandInProgress(true);
|
|
|
|
await Task.Run(() => _exportService.Export(exportContext), token);
|
|
|
|
LogMessage("Done.");
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
LogMessage("Export canceled.", LogLevel.Error);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogMessage($"Export failed: {ex.Message}", LogLevel.Error);
|
|
MessageBox.Show($"Export failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
finally
|
|
{
|
|
_solidWorksService.SetCommandInProgress(false);
|
|
UpdateUIForExportComplete();
|
|
_cancellationTokenSource?.Dispose();
|
|
_cancellationTokenSource = null;
|
|
}
|
|
}
|
|
|
|
private void CancelExport()
|
|
{
|
|
runButton.Enabled = false;
|
|
_cancellationTokenSource?.Cancel();
|
|
}
|
|
|
|
private IViewFlipDecider GetSelectedViewFlipDecider()
|
|
{
|
|
var item = viewFlipDeciderBox.SelectedItem as ViewFlipDeciderComboboxItem;
|
|
return item?.ViewFlipDecider;
|
|
}
|
|
|
|
private void UpdateUIForExportStart()
|
|
{
|
|
viewFlipDeciderBox.Enabled = false;
|
|
runButton.Text = "Stop";
|
|
}
|
|
|
|
private void UpdateUIForExportComplete()
|
|
{
|
|
viewFlipDeciderBox.Enabled = true;
|
|
runButton.Text = "Start";
|
|
runButton.Enabled = true;
|
|
}
|
|
|
|
private void OnActiveDocumentChanged(object sender, EventArgs e)
|
|
{
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action(() => OnActiveDocumentChanged(sender, e)));
|
|
return;
|
|
}
|
|
UpdateActiveDocumentDisplay();
|
|
}
|
|
|
|
private void UpdateActiveDocumentDisplay()
|
|
{
|
|
var activeDoc = _solidWorksService.GetActiveDocument();
|
|
var docTitle = activeDoc?.Title ?? "No Document Open";
|
|
this.Text = $"ExportDXF - {docTitle}";
|
|
}
|
|
|
|
private void LogMessage(string message, LogLevel level = LogLevel.Info, string file = null)
|
|
{
|
|
AddLogEvent(level, LogAction.Start, message, part: file);
|
|
}
|
|
|
|
private void AddLogEvent(LogLevel level, LogAction action, string message, string equipment = "", string drawing = "", string part = "", string target = "", string result = "OK", int durationMs = 0)
|
|
{
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action(() => AddLogEvent(level, action, message, equipment, drawing, part, target, result, durationMs)));
|
|
return;
|
|
}
|
|
|
|
var logEvent = new LogEvent
|
|
{
|
|
Time = DateTime.Now,
|
|
Level = level,
|
|
Action = action,
|
|
Message = message,
|
|
Equipment = equipment,
|
|
Drawing = drawing,
|
|
Part = part,
|
|
Target = target,
|
|
Result = result,
|
|
DurationMs = durationMs
|
|
};
|
|
|
|
_logEvents.Add(logEvent);
|
|
|
|
// Auto-scroll to the last row
|
|
if (logEventsDataGrid.Rows.Count > 0)
|
|
{
|
|
logEventsDataGrid.FirstDisplayedScrollingRowIndex = logEventsDataGrid.Rows.Count - 1;
|
|
}
|
|
}
|
|
|
|
public void AddBomItem(BomItem item)
|
|
{
|
|
if (InvokeRequired)
|
|
{
|
|
Invoke(new Action(() => AddBomItem(item)));
|
|
return;
|
|
}
|
|
_bomItems.Add(item);
|
|
}
|
|
|
|
private void LogEventsDataGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
|
{
|
|
if (e.RowIndex < 0 || e.RowIndex >= _logEvents.Count)
|
|
return;
|
|
|
|
var logEvent = _logEvents[e.RowIndex];
|
|
var row = logEventsDataGrid.Rows[e.RowIndex];
|
|
|
|
switch (logEvent.Level)
|
|
{
|
|
case LogLevel.Warning:
|
|
row.DefaultCellStyle.BackColor = Color.LightGoldenrodYellow;
|
|
row.DefaultCellStyle.ForeColor = Color.DarkOrange;
|
|
break;
|
|
case LogLevel.Error:
|
|
row.DefaultCellStyle.BackColor = Color.MistyRose;
|
|
row.DefaultCellStyle.ForeColor = Color.DarkRed;
|
|
break;
|
|
default:
|
|
row.DefaultCellStyle.BackColor = Color.White;
|
|
row.DefaultCellStyle.ForeColor = Color.Black;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|