refactor: wire ExportDXF to use FabWorks API

Replace direct DB access with API client calls throughout MainForm,
DxfExportService, PartExporter, and Program. Add title field to UI,
async export flow, API-based dropdown loading, and file uploads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 20:37:05 -05:00
parent 696bf2f72c
commit cf76ca8bb1
6 changed files with 346 additions and 394 deletions

View File

@@ -42,6 +42,8 @@ namespace ExportDXF.Forms
label1 = new System.Windows.Forms.Label(); label1 = new System.Windows.Forms.Label();
label2 = new System.Windows.Forms.Label(); label2 = new System.Windows.Forms.Label();
drawingNoBox = new System.Windows.Forms.ComboBox(); drawingNoBox = new System.Windows.Forms.ComboBox();
titleLabel = new System.Windows.Forms.Label();
titleBox = new System.Windows.Forms.TextBox();
mainTabControl.SuspendLayout(); mainTabControl.SuspendLayout();
logEventsTab.SuspendLayout(); logEventsTab.SuspendLayout();
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).BeginInit(); ((System.ComponentModel.ISupportInitialize)logEventsDataGrid).BeginInit();
@@ -54,10 +56,10 @@ namespace ExportDXF.Forms
// runButton // runButton
// //
runButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right; runButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
runButton.Location = new System.Drawing.Point(821, 13); runButton.Location = new System.Drawing.Point(508, 12);
runButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); runButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
runButton.Name = "runButton"; runButton.Name = "runButton";
runButton.Size = new System.Drawing.Size(100, 55); runButton.Size = new System.Drawing.Size(65, 87);
runButton.TabIndex = 11; runButton.TabIndex = 11;
runButton.Text = "Start"; runButton.Text = "Start";
runButton.UseVisualStyleBackColor = true; runButton.UseVisualStyleBackColor = true;
@@ -66,7 +68,7 @@ namespace ExportDXF.Forms
// label3 // label3
// //
label3.AutoSize = true; label3.AutoSize = true;
label3.Location = new System.Drawing.Point(26, 46); label3.Location = new System.Drawing.Point(26, 77);
label3.Name = "label3"; label3.Name = "label3";
label3.Size = new System.Drawing.Size(105, 17); label3.Size = new System.Drawing.Size(105, 17);
label3.TabIndex = 2; label3.TabIndex = 2;
@@ -76,7 +78,7 @@ namespace ExportDXF.Forms
// //
viewFlipDeciderBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; viewFlipDeciderBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
viewFlipDeciderBox.FormattingEnabled = true; viewFlipDeciderBox.FormattingEnabled = true;
viewFlipDeciderBox.Location = new System.Drawing.Point(137, 43); viewFlipDeciderBox.Location = new System.Drawing.Point(137, 74);
viewFlipDeciderBox.Name = "viewFlipDeciderBox"; viewFlipDeciderBox.Name = "viewFlipDeciderBox";
viewFlipDeciderBox.Size = new System.Drawing.Size(365, 25); viewFlipDeciderBox.Size = new System.Drawing.Size(365, 25);
viewFlipDeciderBox.TabIndex = 3; viewFlipDeciderBox.TabIndex = 3;
@@ -87,11 +89,11 @@ namespace ExportDXF.Forms
mainTabControl.Controls.Add(logEventsTab); mainTabControl.Controls.Add(logEventsTab);
mainTabControl.Controls.Add(bomTab); mainTabControl.Controls.Add(bomTab);
mainTabControl.Controls.Add(cutTemplatesTab); mainTabControl.Controls.Add(cutTemplatesTab);
mainTabControl.Location = new System.Drawing.Point(15, 74); mainTabControl.Location = new System.Drawing.Point(15, 105);
mainTabControl.Name = "mainTabControl"; mainTabControl.Name = "mainTabControl";
mainTabControl.Padding = new System.Drawing.Point(20, 5); mainTabControl.Padding = new System.Drawing.Point(20, 5);
mainTabControl.SelectedIndex = 0; mainTabControl.SelectedIndex = 0;
mainTabControl.Size = new System.Drawing.Size(910, 441); mainTabControl.Size = new System.Drawing.Size(910, 492);
mainTabControl.TabIndex = 12; mainTabControl.TabIndex = 12;
// //
// logEventsTab // logEventsTab
@@ -100,7 +102,7 @@ namespace ExportDXF.Forms
logEventsTab.Location = new System.Drawing.Point(4, 30); logEventsTab.Location = new System.Drawing.Point(4, 30);
logEventsTab.Name = "logEventsTab"; logEventsTab.Name = "logEventsTab";
logEventsTab.Padding = new System.Windows.Forms.Padding(3); logEventsTab.Padding = new System.Windows.Forms.Padding(3);
logEventsTab.Size = new System.Drawing.Size(902, 407); logEventsTab.Size = new System.Drawing.Size(902, 458);
logEventsTab.TabIndex = 0; logEventsTab.TabIndex = 0;
logEventsTab.Text = "Log Events"; logEventsTab.Text = "Log Events";
logEventsTab.UseVisualStyleBackColor = true; logEventsTab.UseVisualStyleBackColor = true;
@@ -112,16 +114,16 @@ namespace ExportDXF.Forms
logEventsDataGrid.GridColor = System.Drawing.Color.WhiteSmoke; logEventsDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
logEventsDataGrid.Location = new System.Drawing.Point(6, 6); logEventsDataGrid.Location = new System.Drawing.Point(6, 6);
logEventsDataGrid.Name = "logEventsDataGrid"; logEventsDataGrid.Name = "logEventsDataGrid";
logEventsDataGrid.Size = new System.Drawing.Size(890, 391); logEventsDataGrid.Size = new System.Drawing.Size(890, 440);
logEventsDataGrid.TabIndex = 0; logEventsDataGrid.TabIndex = 0;
// //
// bomTab // bomTab
// //
bomTab.Controls.Add(bomDataGrid); bomTab.Controls.Add(bomDataGrid);
bomTab.Location = new System.Drawing.Point(4, 30); bomTab.Location = new System.Drawing.Point(4, 28);
bomTab.Name = "bomTab"; bomTab.Name = "bomTab";
bomTab.Padding = new System.Windows.Forms.Padding(3); bomTab.Padding = new System.Windows.Forms.Padding(3);
bomTab.Size = new System.Drawing.Size(902, 407); bomTab.Size = new System.Drawing.Size(902, 409);
bomTab.TabIndex = 1; bomTab.TabIndex = 1;
bomTab.Text = "Bill Of Materials"; bomTab.Text = "Bill Of Materials";
bomTab.UseVisualStyleBackColor = true; bomTab.UseVisualStyleBackColor = true;
@@ -139,10 +141,10 @@ namespace ExportDXF.Forms
// cutTemplatesTab // cutTemplatesTab
// //
cutTemplatesTab.Controls.Add(cutTemplatesDataGrid); cutTemplatesTab.Controls.Add(cutTemplatesDataGrid);
cutTemplatesTab.Location = new System.Drawing.Point(4, 30); cutTemplatesTab.Location = new System.Drawing.Point(4, 28);
cutTemplatesTab.Name = "cutTemplatesTab"; cutTemplatesTab.Name = "cutTemplatesTab";
cutTemplatesTab.Padding = new System.Windows.Forms.Padding(3); cutTemplatesTab.Padding = new System.Windows.Forms.Padding(3);
cutTemplatesTab.Size = new System.Drawing.Size(902, 407); cutTemplatesTab.Size = new System.Drawing.Size(902, 409);
cutTemplatesTab.TabIndex = 2; cutTemplatesTab.TabIndex = 2;
cutTemplatesTab.Text = "Cut Templates"; cutTemplatesTab.Text = "Cut Templates";
cutTemplatesTab.UseVisualStyleBackColor = true; cutTemplatesTab.UseVisualStyleBackColor = true;
@@ -191,10 +193,28 @@ namespace ExportDXF.Forms
drawingNoBox.Size = new System.Drawing.Size(119, 25); drawingNoBox.Size = new System.Drawing.Size(119, 25);
drawingNoBox.TabIndex = 13; drawingNoBox.TabIndex = 13;
// //
// titleLabel
//
titleLabel.AutoSize = true;
titleLabel.Location = new System.Drawing.Point(99, 46);
titleLabel.Name = "titleLabel";
titleLabel.Size = new System.Drawing.Size(32, 17);
titleLabel.TabIndex = 14;
titleLabel.Text = "Title";
//
// titleBox
//
titleBox.Location = new System.Drawing.Point(137, 43);
titleBox.Name = "titleBox";
titleBox.Size = new System.Drawing.Size(365, 25);
titleBox.TabIndex = 15;
//
// MainForm // MainForm
// //
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
ClientSize = new System.Drawing.Size(937, 527); ClientSize = new System.Drawing.Size(937, 609);
Controls.Add(titleBox);
Controls.Add(titleLabel);
Controls.Add(drawingNoBox); Controls.Add(drawingNoBox);
Controls.Add(equipmentBox); Controls.Add(equipmentBox);
Controls.Add(mainTabControl); Controls.Add(mainTabControl);
@@ -237,5 +257,7 @@ namespace ExportDXF.Forms
private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label2;
private System.Windows.Forms.ComboBox drawingNoBox; private System.Windows.Forms.ComboBox drawingNoBox;
private System.Windows.Forms.Label titleLabel;
private System.Windows.Forms.TextBox titleBox;
} }
} }

View File

@@ -1,4 +1,4 @@
using ExportDXF.Data; using ExportDXF.ApiClient;
using ExportDXF.Extensions; using ExportDXF.Extensions;
using ExportDXF.Models; using ExportDXF.Models;
using ExportDXF.Services; using ExportDXF.Services;
@@ -18,15 +18,14 @@ namespace ExportDXF.Forms
{ {
private readonly ISolidWorksService _solidWorksService; private readonly ISolidWorksService _solidWorksService;
private readonly IDxfExportService _exportService; private readonly IDxfExportService _exportService;
private readonly IFileExportService _fileExportService; private readonly IFabWorksApiClient _apiClient;
private readonly Func<ExportDxfDbContext> _dbContextFactory;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private readonly BindingList<LogEvent> _logEvents; private readonly BindingList<LogEvent> _logEvents;
private readonly BindingList<BomItem> _bomItems; private readonly BindingList<BomItem> _bomItems;
private readonly BindingList<CutTemplate> _cutTemplates; private readonly BindingList<CutTemplate> _cutTemplates;
private List<DrawingInfo> _allDrawings; private List<DrawingInfo> _allDrawings;
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IFileExportService fileExportService, Func<ExportDxfDbContext> dbContextFactory = null) public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IFabWorksApiClient apiClient)
{ {
InitializeComponent(); InitializeComponent();
_solidWorksService = solidWorksService ?? _solidWorksService = solidWorksService ??
@@ -34,9 +33,8 @@ namespace ExportDXF.Forms
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged; _solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
_exportService = exportService ?? _exportService = exportService ??
throw new ArgumentNullException(nameof(exportService)); throw new ArgumentNullException(nameof(exportService));
_fileExportService = fileExportService ?? _apiClient = apiClient ??
throw new ArgumentNullException(nameof(fileExportService)); throw new ArgumentNullException(nameof(apiClient));
_dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext());
_logEvents = new BindingList<LogEvent>(); _logEvents = new BindingList<LogEvent>();
_bomItems = new BindingList<BomItem>(); _bomItems = new BindingList<BomItem>();
_cutTemplates = new BindingList<CutTemplate>(); _cutTemplates = new BindingList<CutTemplate>();
@@ -70,9 +68,10 @@ namespace ExportDXF.Forms
LogMessage("Connecting to SolidWorks, this may take a minute..."); LogMessage("Connecting to SolidWorks, this may take a minute...");
await _solidWorksService.ConnectAsync(); await _solidWorksService.ConnectAsync();
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged; _solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
LogMessage($"Output folder: {_fileExportService.OutputFolder}"); LogMessage("Files will be uploaded to FabWorks API");
await LoadDrawingDropdownsAsync();
LogMessage("Ready"); LogMessage("Ready");
UpdateActiveDocumentDisplay(); await UpdateActiveDocumentDisplayAsync();
runButton.Enabled = true; runButton.Enabled = true;
} }
catch (Exception ex) catch (Exception ex)
@@ -274,74 +273,60 @@ namespace ExportDXF.Forms
} }
private void InitializeDrawingDropdowns() private void InitializeDrawingDropdowns()
{
// Wire up event handler; actual data loading happens in LoadDrawingDropdownsAsync
equipmentBox.SelectedIndexChanged += EquipmentBox_SelectedIndexChanged;
}
private async Task LoadDrawingDropdownsAsync()
{ {
try try
{ {
using (var db = _dbContextFactory()) var equipmentNumbers = await _apiClient.GetEquipmentNumbersAsync();
equipmentBox.Items.Clear();
equipmentBox.Items.Add("");
foreach (var eq in equipmentNumbers)
{ {
// Get all drawing numbers from the database equipmentBox.Items.Add(eq);
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;
} }
// Clear _allDrawings — drawing list is now loaded on equipment selection
_allDrawings = new List<DrawingInfo>();
await UpdateDrawingDropdownAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
// Database might not exist yet - that's OK // API might not be available yet - that's OK
System.Diagnostics.Debug.WriteLine($"Failed to load drawings from database: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"Failed to load equipment numbers from API: {ex.Message}");
} }
} }
private void EquipmentBox_SelectedIndexChanged(object sender, EventArgs e) private async void EquipmentBox_SelectedIndexChanged(object sender, EventArgs e)
{ {
UpdateDrawingDropdown(); await UpdateDrawingDropdownAsync();
} }
private void UpdateDrawingDropdown() private async Task UpdateDrawingDropdownAsync()
{ {
var selectedEquipment = equipmentBox.SelectedItem?.ToString(); var selectedEquipment = equipmentBox.SelectedItem?.ToString();
var filteredDrawings = string.IsNullOrEmpty(selectedEquipment)
? _allDrawings
: _allDrawings.Where(d => d.EquipmentNo == selectedEquipment).ToList();
drawingNoBox.Items.Clear(); drawingNoBox.Items.Clear();
drawingNoBox.Items.Add(""); // Empty option drawingNoBox.Items.Add("");
foreach (var drawing in filteredDrawings)
try
{ {
drawingNoBox.Items.Add(drawing.DrawingNo); var drawingNumbers = await _apiClient.GetDrawingNumbersByEquipmentAsync(
string.IsNullOrEmpty(selectedEquipment) ? null : selectedEquipment);
foreach (var dn in drawingNumbers)
{
drawingNoBox.Items.Add(dn);
}
}
catch
{
// API might not be available
} }
if (drawingNoBox.Items.Count > 0) if (drawingNoBox.Items.Count > 0)
@@ -380,11 +365,13 @@ namespace ExportDXF.Forms
// Use equipment/drawing values from the UI dropdowns // Use equipment/drawing values from the UI dropdowns
var equipment = equipmentBox.Text?.Trim(); var equipment = equipmentBox.Text?.Trim();
var drawingNo = drawingNoBox.Text?.Trim(); var drawingNo = drawingNoBox.Text?.Trim();
var filePrefix = !string.IsNullOrEmpty(equipment) && !string.IsNullOrEmpty(drawingNo) var filePrefix = !string.IsNullOrEmpty(equipment)
? $"{equipment} {drawingNo}" ? (!string.IsNullOrEmpty(drawingNo) ? $"{equipment} {drawingNo}" : equipment)
: activeDoc.Title; : activeDoc.Title;
var viewFlipDecider = GetSelectedViewFlipDecider(); var viewFlipDecider = GetSelectedViewFlipDecider();
var title = titleBox.Text?.Trim();
var exportContext = new ExportContext var exportContext = new ExportContext
{ {
ActiveDocument = activeDoc, ActiveDocument = activeDoc,
@@ -392,6 +379,7 @@ namespace ExportDXF.Forms
FilePrefix = filePrefix, FilePrefix = filePrefix,
Equipment = equipment, Equipment = equipment,
DrawingNo = drawingNo, DrawingNo = drawingNo,
Title = string.IsNullOrEmpty(title) ? null : title,
EquipmentId = null, EquipmentId = null,
CancellationToken = token, CancellationToken = token,
ProgressCallback = (msg, level, file) => LogMessage(msg, level, file), ProgressCallback = (msg, level, file) => LogMessage(msg, level, file),
@@ -403,11 +391,11 @@ namespace ExportDXF.Forms
_cutTemplates.Clear(); _cutTemplates.Clear();
LogMessage($"Started at {DateTime.Now:t}"); LogMessage($"Started at {DateTime.Now:t}");
LogMessage($"Exporting to: {_fileExportService.OutputFolder}"); LogMessage("Exporting (files will be uploaded to API)...");
_solidWorksService.SetCommandInProgress(true); _solidWorksService.SetCommandInProgress(true);
await Task.Run(() => _exportService.Export(exportContext), token); await Task.Run(async () => await _exportService.ExportAsync(exportContext), token);
LogMessage("Done."); LogMessage("Done.");
} }
@@ -454,17 +442,17 @@ namespace ExportDXF.Forms
runButton.Enabled = true; runButton.Enabled = true;
} }
private void OnActiveDocumentChanged(object sender, EventArgs e) private async void OnActiveDocumentChanged(object sender, EventArgs e)
{ {
if (InvokeRequired) if (InvokeRequired)
{ {
Invoke(new Action(() => OnActiveDocumentChanged(sender, e))); Invoke(new Action(async () => await UpdateActiveDocumentDisplayAsync()));
return; return;
} }
UpdateActiveDocumentDisplay(); await UpdateActiveDocumentDisplayAsync();
} }
private void UpdateActiveDocumentDisplay() private async Task UpdateActiveDocumentDisplayAsync()
{ {
var activeDoc = _solidWorksService.GetActiveDocument(); var activeDoc = _solidWorksService.GetActiveDocument();
var docTitle = activeDoc?.Title ?? "No Document Open"; var docTitle = activeDoc?.Title ?? "No Document Open";
@@ -473,12 +461,12 @@ namespace ExportDXF.Forms
if (activeDoc == null) if (activeDoc == null)
return; return;
// Try database first: look up the most recent export for this file path // Try API first: look up the most recent export for this file path
DrawingInfo drawingInfo = null; DrawingInfo drawingInfo = null;
if (!string.IsNullOrEmpty(activeDoc.FilePath)) if (!string.IsNullOrEmpty(activeDoc.FilePath))
{ {
drawingInfo = LookupDrawingInfoFromHistory(activeDoc.FilePath); drawingInfo = await LookupDrawingInfoFromHistoryAsync(activeDoc.FilePath);
} }
// Fall back to parsing the document title // Fall back to parsing the document title
@@ -489,36 +477,37 @@ namespace ExportDXF.Forms
if (drawingInfo != null) if (drawingInfo != null)
{ {
if (!equipmentBox.Items.Contains(drawingInfo.EquipmentNo)) if (!string.IsNullOrEmpty(drawingInfo.EquipmentNo))
equipmentBox.Items.Add(drawingInfo.EquipmentNo); {
equipmentBox.Text = drawingInfo.EquipmentNo; if (!equipmentBox.Items.Contains(drawingInfo.EquipmentNo))
equipmentBox.Items.Add(drawingInfo.EquipmentNo);
equipmentBox.Text = drawingInfo.EquipmentNo;
}
if (!drawingNoBox.Items.Contains(drawingInfo.DrawingNo)) if (!string.IsNullOrEmpty(drawingInfo.DrawingNo))
drawingNoBox.Items.Add(drawingInfo.DrawingNo); {
drawingNoBox.Text = drawingInfo.DrawingNo; if (!drawingNoBox.Items.Contains(drawingInfo.DrawingNo))
drawingNoBox.Items.Add(drawingInfo.DrawingNo);
drawingNoBox.Text = drawingInfo.DrawingNo;
}
} }
} }
private DrawingInfo LookupDrawingInfoFromHistory(string filePath) private async Task<DrawingInfo> LookupDrawingInfoFromHistoryAsync(string filePath)
{ {
try try
{ {
using (var db = _dbContextFactory()) var dto = await _apiClient.GetExportBySourceFileAsync(filePath);
if (dto != null && !string.IsNullOrEmpty(dto.DrawingNumber))
{ {
var drawingNumber = db.ExportRecords if (!string.IsNullOrEmpty(dto.Title))
.Where(r => r.SourceFilePath.ToLower() == filePath.ToLower() titleBox.Text = dto.Title;
&& !string.IsNullOrEmpty(r.DrawingNumber)) return DrawingInfo.Parse(dto.DrawingNumber);
.OrderByDescending(r => r.Id)
.Select(r => r.DrawingNumber)
.FirstOrDefault();
if (drawingNumber != null)
return DrawingInfo.Parse(drawingNumber);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
System.Diagnostics.Debug.WriteLine($"Failed to look up drawing info from history: {ex.Message}"); System.Diagnostics.Debug.WriteLine($"Failed to look up drawing info from API: {ex.Message}");
} }
return null; return null;

View File

@@ -68,8 +68,9 @@ namespace ExportDXF.Services
public string ContentHash { get; set; } public string ContentHash { get; set; }
/// <summary> /// <summary>
/// Path to the stashed (backed-up) previous DXF file (transient, not persisted). /// Full path to the locally-exported DXF temp file (transient, not persisted).
/// Set after successful export; used for upload to the API.
/// </summary> /// </summary>
public string StashedFilePath { get; set; } public string LocalTempPath { get; set; }
} }
} }

View File

@@ -1,7 +1,9 @@
using ExportDXF.ApiClient;
using ExportDXF.Forms; using ExportDXF.Forms;
using ExportDXF.Services; using ExportDXF.Services;
using System; using System;
using System.Configuration; using System.Configuration;
using System.Net.Http;
using System.Windows.Forms; using System.Windows.Forms;
namespace ExportDXF namespace ExportDXF
@@ -28,29 +30,35 @@ namespace ExportDXF
/// </summary> /// </summary>
public class ServiceContainer public class ServiceContainer
{ {
private readonly string _outputFolder; private readonly string _apiBaseUrl;
public ServiceContainer() public ServiceContainer()
{ {
_outputFolder = ConfigurationManager.AppSettings["ExportOutputFolder"] ?? @"C:\ExportDXF\Output"; _apiBaseUrl = ConfigurationManager.AppSettings["FabWorksApiUrl"] ?? "http://localhost:5206";
} }
public MainForm ResolveMainForm() public MainForm ResolveMainForm()
{ {
var solidWorksService = new SolidWorksService(); var solidWorksService = new SolidWorksService();
var bomExtractor = new BomExtractor(); var bomExtractor = new BomExtractor();
var fileExportService = new FileExportService(_outputFolder); var partExporter = new PartExporter();
var partExporter = new PartExporter(fileExportService);
var drawingExporter = new DrawingExporter(); var drawingExporter = new DrawingExporter();
var httpClient = new HttpClient
{
BaseAddress = new Uri(_apiBaseUrl),
Timeout = TimeSpan.FromSeconds(30)
};
var apiClient = new FabWorksApiClient(httpClient);
var exportService = new DxfExportService( var exportService = new DxfExportService(
solidWorksService, solidWorksService,
bomExtractor, bomExtractor,
partExporter, partExporter,
drawingExporter, drawingExporter,
fileExportService); apiClient);
return new MainForm(solidWorksService, exportService, fileExportService); return new MainForm(solidWorksService, exportService, apiClient);
} }
} }
} }

View File

@@ -1,15 +1,15 @@
using ExportDXF.Data; using ExportDXF.ApiClient;
using ExportDXF.Extensions; using ExportDXF.Extensions;
using ExportDXF.ItemExtractors; using ExportDXF.ItemExtractors;
using ExportDXF.Models; using ExportDXF.Models;
using ExportDXF.Utilities; using ExportDXF.Utilities;
using ExportDXF; using ExportDXF;
using Microsoft.EntityFrameworkCore;
using SolidWorks.Interop.sldworks; using SolidWorks.Interop.sldworks;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace ExportDXF.Services namespace ExportDXF.Services
{ {
@@ -19,11 +19,12 @@ namespace ExportDXF.Services
/// Exports the document specified in the context to DXF format. /// Exports the document specified in the context to DXF format.
/// </summary> /// </summary>
/// <param name="context">The export context containing all necessary information.</param> /// <param name="context">The export context containing all necessary information.</param>
void Export(ExportContext context); Task ExportAsync(ExportContext context);
} }
/// <summary> /// <summary>
/// Service responsible for orchestrating the export of SolidWorks documents to DXF format. /// Service responsible for orchestrating the export of SolidWorks documents to DXF format.
/// Files are generated locally in a temp directory, then uploaded to the API for storage and versioning.
/// </summary> /// </summary>
public class DxfExportService : IDxfExportService public class DxfExportService : IDxfExportService
{ {
@@ -31,29 +32,26 @@ namespace ExportDXF.Services
private readonly IBomExtractor _bomExtractor; private readonly IBomExtractor _bomExtractor;
private readonly IPartExporter _partExporter; private readonly IPartExporter _partExporter;
private readonly IDrawingExporter _drawingExporter; private readonly IDrawingExporter _drawingExporter;
private readonly IFileExportService _fileExportService; private readonly IFabWorksApiClient _apiClient;
private readonly Func<ExportDxfDbContext> _dbContextFactory;
public DxfExportService( public DxfExportService(
ISolidWorksService solidWorksService, ISolidWorksService solidWorksService,
IBomExtractor bomExtractor, IBomExtractor bomExtractor,
IPartExporter partExporter, IPartExporter partExporter,
IDrawingExporter drawingExporter, IDrawingExporter drawingExporter,
IFileExportService fileExportService, IFabWorksApiClient apiClient)
Func<ExportDxfDbContext> dbContextFactory = null)
{ {
_solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService)); _solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService));
_bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor)); _bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor));
_partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter)); _partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter));
_drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter)); _drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter));
_fileExportService = fileExportService ?? throw new ArgumentNullException(nameof(fileExportService)); _apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
_dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext());
} }
/// <summary> /// <summary>
/// Exports the document specified in the context to DXF format. /// Exports the document specified in the context to DXF format.
/// </summary> /// </summary>
public void Export(ExportContext context) public async Task ExportAsync(ExportContext context)
{ {
if (context == null) if (context == null)
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
@@ -62,26 +60,26 @@ namespace ExportDXF.Services
SetupExportContext(context); SetupExportContext(context);
var startTime = DateTime.Now; var startTime = DateTime.Now;
var tempDir = CreateTempWorkDir();
var drawingNumber = ParseDrawingNumber(context);
var outputFolder = _fileExportService.GetDrawingOutputFolder(context.Equipment, context.DrawingNo);
try try
{ {
_solidWorksService.EnableUserControl(false); _solidWorksService.EnableUserControl(false);
var drawingNumber = ParseDrawingNumber(context);
switch (context.ActiveDocument.DocumentType) switch (context.ActiveDocument.DocumentType)
{ {
case DocumentType.Part: case DocumentType.Part:
ExportPart(context, outputFolder, drawingNumber); await ExportPartAsync(context, tempDir, drawingNumber);
break; break;
case DocumentType.Assembly: case DocumentType.Assembly:
ExportAssembly(context, outputFolder, drawingNumber); await ExportAssemblyAsync(context, tempDir, drawingNumber);
break; break;
case DocumentType.Drawing: case DocumentType.Drawing:
ExportDrawing(context, drawingNumber, outputFolder); await ExportDrawingAsync(context, drawingNumber, tempDir);
break; break;
default: default:
@@ -93,6 +91,7 @@ namespace ExportDXF.Services
{ {
CleanupExportContext(context); CleanupExportContext(context);
_solidWorksService.EnableUserControl(true); _solidWorksService.EnableUserControl(true);
CleanupTempDir(tempDir);
var duration = DateTime.Now - startTime; var duration = DateTime.Now - startTime;
LogProgress(context, $"Run time: {duration.ToReadableFormat()}"); LogProgress(context, $"Run time: {duration.ToReadableFormat()}");
@@ -101,7 +100,7 @@ namespace ExportDXF.Services
#region Export Methods by Document Type #region Export Methods by Document Type
private void ExportPart(ExportContext context, string outputFolder, string drawingNumber) private async Task ExportPartAsync(ExportContext context, string tempDir, string drawingNumber)
{ {
LogProgress(context, "Active document is a Part"); LogProgress(context, "Active document is a Part");
@@ -112,14 +111,14 @@ namespace ExportDXF.Services
return; return;
} }
var exportRecord = CreateExportRecord(context, drawingNumber, outputFolder); var exportRecord = await CreateExportRecordAsync(context, drawingNumber);
var item = _partExporter.ExportSinglePart(part, outputFolder, context); var item = _partExporter.ExportSinglePart(part, tempDir, context);
if (item != null) if (item != null)
{ {
// Assign auto-incremented item number // Check if this part+config already has a BOM item for this drawing
var nextItemNo = GetNextItemNumber(drawingNumber); var existingItemNo = await FindExistingItemNoAsync(exportRecord?.Id, item.PartName, item.Configuration);
item.ItemNo = nextItemNo; item.ItemNo = existingItemNo ?? await GetNextItemNumberAsync(drawingNumber);
var bomItem = new BomItem var bomItem = new BomItem
{ {
@@ -135,29 +134,31 @@ namespace ExportDXF.Services
Material = item.Material ?? "" Material = item.Material ?? ""
}; };
if (!string.IsNullOrEmpty(item.FileName)) // Upload DXF to API and get stored path
if (!string.IsNullOrEmpty(item.LocalTempPath))
{ {
var dxfPath = Path.Combine(outputFolder, item.FileName + ".dxf"); var uploadResult = await UploadDxfAsync(item, context);
bomItem.CutTemplate = new CutTemplate if (uploadResult != null)
{ {
DxfFilePath = dxfPath, bomItem.CutTemplate = new CutTemplate
ContentHash = item.ContentHash, {
Thickness = item.Thickness > 0 ? item.Thickness : null, DxfFilePath = uploadResult.StoredFilePath,
KFactor = item.KFactor > 0 ? item.KFactor : null, ContentHash = item.ContentHash,
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null Thickness = item.Thickness > 0 ? item.Thickness : null,
}; KFactor = item.KFactor > 0 ? item.KFactor : null,
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null
HandleDxfVersioning(item, dxfPath, context); };
}
} }
context.BomItemCallback?.Invoke(bomItem); context.BomItemCallback?.Invoke(bomItem);
if (exportRecord != null) if (exportRecord != null)
SaveBomItem(bomItem, context); await SaveBomItemAsync(exportRecord.Id, bomItem, context);
} }
} }
private void ExportAssembly(ExportContext context, string outputFolder, string drawingNumber) private async Task ExportAssemblyAsync(ExportContext context, string tempDir, string drawingNumber)
{ {
LogProgress(context, "Active document is an Assembly"); LogProgress(context, "Active document is an Assembly");
LogProgress(context, "Fetching components..."); LogProgress(context, "Fetching components...");
@@ -179,23 +180,31 @@ namespace ExportDXF.Services
LogProgress(context, $"Found {items.Count} item(s)."); LogProgress(context, $"Found {items.Count} item(s).");
var exportRecord = CreateExportRecord(context, drawingNumber, outputFolder); var exportRecord = await CreateExportRecordAsync(context, drawingNumber);
// Auto-assign item numbers for items that don't have one // Check existing BOM items and reuse item numbers, or assign new ones
var nextNum = int.Parse(GetNextItemNumber(drawingNumber)); var nextNum = int.Parse(await GetNextItemNumberAsync(drawingNumber));
foreach (var item in items) foreach (var item in items)
{ {
if (string.IsNullOrWhiteSpace(item.ItemNo)) if (string.IsNullOrWhiteSpace(item.ItemNo))
{ {
item.ItemNo = nextNum.ToString(); var existingItemNo = await FindExistingItemNoAsync(exportRecord?.Id, item.PartName, item.Configuration);
nextNum++; if (existingItemNo != null)
{
item.ItemNo = existingItemNo;
}
else
{
item.ItemNo = nextNum.ToString();
nextNum++;
}
} }
} }
ExportItems(items, outputFolder, context, exportRecord?.Id); await ExportItemsAsync(items, tempDir, context, exportRecord?.Id);
} }
private void ExportDrawing(ExportContext context, string drawingNumber, string drawingOutputFolder) private async Task ExportDrawingAsync(ExportContext context, string drawingNumber, string tempDir)
{ {
LogProgress(context, "Active document is a Drawing"); LogProgress(context, "Active document is a Drawing");
LogProgress(context, "Finding BOM tables..."); LogProgress(context, "Finding BOM tables...");
@@ -217,88 +226,46 @@ namespace ExportDXF.Services
LogProgress(context, $"Found {items.Count} component(s)"); LogProgress(context, $"Found {items.Count} component(s)");
// Export drawing to PDF // Export drawing to PDF in temp dir
var tempDir = CreateTempWorkDir();
_drawingExporter.ExportToPdf(drawing, tempDir, context); _drawingExporter.ExportToPdf(drawing, tempDir, context);
// Copy PDF to output folder with versioning // Create export record via API
string pdfStashPath = null; var exportRecord = await CreateExportRecordAsync(context, drawingNumber);
string savedPdfPath = null;
// Upload PDF to API with versioning
try try
{ {
var pdfs = Directory.GetFiles(tempDir, "*.pdf"); var pdfs = Directory.GetFiles(tempDir, "*.pdf");
if (pdfs.Length > 0) if (pdfs.Length > 0)
{ {
// Determine the destination path to stash the existing file var pdfTempPath = pdfs[0];
var pdfFileName = !string.IsNullOrEmpty(drawingNumber) var pdfHash = ContentHasher.ComputeFileHash(pdfTempPath);
? $"{drawingNumber}.pdf"
: Path.GetFileName(pdfs[0]);
var pdfDestPath = Path.Combine(drawingOutputFolder, pdfFileName);
pdfStashPath = _fileExportService.StashFile(pdfDestPath); var uploadResult = await _apiClient.UploadPdfAsync(
savedPdfPath = _fileExportService.SavePdfFile(pdfs[0], drawingNumber, drawingOutputFolder); pdfTempPath,
context.Equipment,
context.DrawingNo,
pdfHash,
exportRecord?.Id);
if (uploadResult != null)
{
if (uploadResult.WasUnchanged)
LogProgress(context, $"PDF unchanged: {uploadResult.FileName}", LogLevel.Info);
else if (uploadResult.IsNewFile)
LogProgress(context, $"Saved PDF: {uploadResult.FileName}", LogLevel.Info);
else
LogProgress(context, $"PDF updated: {uploadResult.FileName}", LogLevel.Info);
}
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
LogProgress(context, $"PDF save error: {ex.Message}", LogLevel.Error); LogProgress(context, $"PDF upload error: {ex.Message}", LogLevel.Error);
}
// Create export record in database
var exportRecord = CreateExportRecord(context, drawingNumber, drawingOutputFolder);
// Handle PDF versioning and update export record with hash
if (exportRecord != null && savedPdfPath != null)
{
try
{
HandlePdfVersioning(savedPdfPath, exportRecord.DrawingNumber, exportRecord, context);
// Archive or discard old PDF based on hash comparison
if (pdfStashPath != null)
{
using (var db = _dbContextFactory())
{
var previousRecord = db.ExportRecords
.Where(r => r.DrawingNumber == exportRecord.DrawingNumber
&& r.PdfContentHash != null
&& r.Id != exportRecord.Id)
.OrderByDescending(r => r.Id)
.FirstOrDefault();
if (previousRecord != null && previousRecord.PdfContentHash == exportRecord.PdfContentHash)
{
_fileExportService.DiscardStash(pdfStashPath);
}
else
{
_fileExportService.ArchiveFile(pdfStashPath, savedPdfPath);
}
}
}
// Update the record with the PDF hash
using (var db = _dbContextFactory())
{
db.ExportRecords.Attach(exportRecord);
db.Entry(exportRecord).Property(r => r.PdfContentHash).IsModified = true;
db.SaveChanges();
}
}
catch (Exception ex)
{
_fileExportService.DiscardStash(pdfStashPath);
LogProgress(context, $"PDF versioning error: {ex.Message}", LogLevel.Error);
}
}
else if (pdfStashPath != null)
{
// No export record - discard stash
_fileExportService.DiscardStash(pdfStashPath);
} }
// Export parts to DXF and save BOM items // Export parts to DXF and save BOM items
ExportItems(items, drawingOutputFolder, context, exportRecord?.Id); await ExportItemsAsync(items, tempDir, context, exportRecord?.Id);
} }
#endregion #endregion
@@ -357,7 +324,7 @@ namespace ExportDXF.Services
} }
} }
private void ExportItems(List<Item> items, string saveDirectory, ExportContext context, int? exportRecordId = null) private async Task ExportItemsAsync(List<Item> items, string tempDir, ExportContext context, int? exportRecordId = null)
{ {
int successCount = 0; int successCount = 0;
int skippedCount = 0; int skippedCount = 0;
@@ -375,7 +342,7 @@ namespace ExportDXF.Services
try try
{ {
// PartExporter will handle template drawing creation through context // PartExporter will handle template drawing creation through context
_partExporter.ExportItem(item, saveDirectory, context); _partExporter.ExportItem(item, tempDir, context);
// Always create BomItem for every item (sheet metal or not) // Always create BomItem for every item (sheet metal or not)
var bomItem = new BomItem var bomItem = new BomItem
@@ -392,23 +359,23 @@ namespace ExportDXF.Services
Material = item.Material ?? "" Material = item.Material ?? ""
}; };
// Only create CutTemplate if DXF was exported successfully // Only upload and create CutTemplate if DXF was exported successfully
if (!string.IsNullOrEmpty(item.FileName)) if (!string.IsNullOrEmpty(item.LocalTempPath))
{ {
successCount++; successCount++;
var dxfPath = Path.Combine(saveDirectory, item.FileName + ".dxf"); var uploadResult = await UploadDxfAsync(item, context);
bomItem.CutTemplate = new CutTemplate if (uploadResult != null)
{ {
DxfFilePath = dxfPath, bomItem.CutTemplate = new CutTemplate
ContentHash = item.ContentHash, {
Thickness = item.Thickness > 0 ? item.Thickness : null, DxfFilePath = uploadResult.StoredFilePath,
KFactor = item.KFactor > 0 ? item.KFactor : null, ContentHash = item.ContentHash,
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null Thickness = item.Thickness > 0 ? item.Thickness : null,
}; KFactor = item.KFactor > 0 ? item.KFactor : null,
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null
// Compare hash with previous export to decide archive/discard };
HandleDxfVersioning(item, dxfPath, context); }
} }
else else
{ {
@@ -418,21 +385,10 @@ namespace ExportDXF.Services
// Add to UI // Add to UI
context.BomItemCallback?.Invoke(bomItem); context.BomItemCallback?.Invoke(bomItem);
// Save BOM item to database if we have an export record // Save BOM item via API if we have an export record
if (exportRecordId.HasValue) if (exportRecordId.HasValue)
{ {
try await SaveBomItemAsync(exportRecordId.Value, bomItem, context);
{
using (var db = _dbContextFactory())
{
db.BomItems.Add(bomItem);
db.SaveChanges();
}
}
catch (Exception dbEx)
{
LogProgress(context, $"Database error saving BOM item: {dbEx.Message}", LogLevel.Error);
}
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -449,146 +405,103 @@ namespace ExportDXF.Services
if (exportRecordId.HasValue) if (exportRecordId.HasValue)
{ {
LogProgress(context, $"BOM items saved to database (ExportRecord ID: {exportRecordId.Value})", LogLevel.Info); LogProgress(context, $"BOM items saved (ExportRecord ID: {exportRecordId.Value})", LogLevel.Info);
} }
} }
#endregion #endregion
#region Versioning #region File Upload
private void HandleDxfVersioning(Item item, string dxfPath, ExportContext context) private async Task<ApiFileUploadResponse> UploadDxfAsync(Item item, ExportContext context)
{
if (string.IsNullOrEmpty(item.ContentHash))
return;
try
{
using (var db = _dbContextFactory())
{
var previousCutTemplate = db.CutTemplates
.Where(ct => ct.DxfFilePath == dxfPath && ct.ContentHash != null)
.OrderByDescending(ct => ct.Id)
.FirstOrDefault();
if (previousCutTemplate != null && previousCutTemplate.ContentHash == item.ContentHash)
{
// Content unchanged - discard the stashed file
_fileExportService.DiscardStash(item.StashedFilePath);
LogProgress(context, $"DXF unchanged: {item.FileName}.dxf", LogLevel.Info);
}
else
{
// Content changed or first export - archive the old file
if (!string.IsNullOrEmpty(item.StashedFilePath))
{
_fileExportService.ArchiveFile(item.StashedFilePath, dxfPath);
LogProgress(context, $"DXF updated, previous version archived: {item.FileName}.dxf", LogLevel.Info);
}
else
{
LogProgress(context, $"Exported: {item.FileName}.dxf", LogLevel.Info);
}
}
}
}
catch (Exception ex)
{
// Don't fail the export if versioning fails - just discard the stash
_fileExportService.DiscardStash(item.StashedFilePath);
LogProgress(context, $"Versioning check failed for {item.FileName}: {ex.Message}", LogLevel.Warning);
}
}
private void HandlePdfVersioning(string pdfPath, string drawingNumber, ExportRecord exportRecord, ExportContext context)
{ {
try try
{ {
var newHash = ContentHasher.ComputeFileHash(pdfPath); var result = await _apiClient.UploadDxfAsync(
item.LocalTempPath,
context.Equipment,
context.DrawingNo,
item.ItemNo,
item.ContentHash);
using (var db = _dbContextFactory()) if (result.WasUnchanged)
{ LogProgress(context, $"DXF unchanged: {result.FileName}", LogLevel.Info);
var previousRecord = db.ExportRecords else if (result.IsNewFile)
.Where(r => r.DrawingNumber == drawingNumber && r.PdfContentHash != null) LogProgress(context, $"Exported: {result.FileName}", LogLevel.Info);
.OrderByDescending(r => r.Id) else
.FirstOrDefault(); LogProgress(context, $"DXF updated: {result.FileName}", LogLevel.Info);
if (previousRecord != null && previousRecord.PdfContentHash == newHash) return result;
{
LogProgress(context, $"PDF unchanged: {Path.GetFileName(pdfPath)}", LogLevel.Info);
}
else
{
LogProgress(context, $"Saved PDF: {Path.GetFileName(pdfPath)}", LogLevel.Info);
}
}
if (exportRecord != null)
exportRecord.PdfContentHash = newHash;
} }
catch (Exception ex) catch (Exception ex)
{ {
LogProgress(context, $"PDF versioning check failed: {ex.Message}", LogLevel.Warning); LogProgress(context, $"DXF upload failed for {item.FileName}: {ex.Message}", LogLevel.Warning);
}
}
#endregion
#region Database Helpers
private ExportRecord CreateExportRecord(ExportContext context, string drawingNumber, string outputFolder)
{
try
{
using (var db = _dbContextFactory())
{
db.Database.Migrate();
var record = new ExportRecord
{
DrawingNumber = drawingNumber ?? context.ActiveDocument.Title,
SourceFilePath = context.ActiveDocument.FilePath,
OutputFolder = outputFolder,
ExportedAt = DateTime.Now,
ExportedBy = System.Environment.UserName
};
db.ExportRecords.Add(record);
db.SaveChanges();
LogProgress(context, $"Created export record (ID: {record.Id})", LogLevel.Info);
return record;
}
}
catch (Exception ex)
{
LogProgress(context, $"Database error creating export record: {ex.Message}", LogLevel.Error);
return null; return null;
} }
} }
private string GetNextItemNumber(string drawingNumber) #endregion
#region API Helpers
private async Task<ExportRecord> CreateExportRecordAsync(ExportContext context, string drawingNumber)
{
try
{
var dto = await _apiClient.CreateExportAsync(
drawingNumber ?? context.ActiveDocument.Title,
context.Equipment ?? "",
context.DrawingNo ?? "",
context.ActiveDocument.FilePath,
"", // Output folder is now managed by the API
context.Title);
var record = new ExportRecord
{
Id = dto.Id,
DrawingNumber = dto.DrawingNumber,
EquipmentNo = dto.EquipmentNo,
DrawingNo = dto.DrawingNo,
SourceFilePath = dto.SourceFilePath,
OutputFolder = dto.OutputFolder,
ExportedAt = dto.ExportedAt,
ExportedBy = dto.ExportedBy
};
LogProgress(context, $"Created export record (ID: {record.Id})", LogLevel.Info);
return record;
}
catch (Exception ex)
{
LogProgress(context, $"API error creating export record: {ex.Message}", LogLevel.Error);
return null;
}
}
private async Task<string> FindExistingItemNoAsync(int? exportRecordId, string partName, string configurationName)
{
if (!exportRecordId.HasValue)
return null;
try
{
var existing = await _apiClient.FindExistingBomItemAsync(exportRecordId.Value, partName, configurationName);
return existing?.ItemNo;
}
catch
{
return null;
}
}
private async Task<string> GetNextItemNumberAsync(string drawingNumber)
{ {
if (string.IsNullOrEmpty(drawingNumber)) if (string.IsNullOrEmpty(drawingNumber))
return "1"; return "1";
try try
{ {
using (var db = _dbContextFactory()) return await _apiClient.GetNextItemNumberAsync(drawingNumber);
{
var existingItems = db.ExportRecords
.Where(r => r.DrawingNumber == drawingNumber)
.SelectMany(r => r.BomItems)
.Select(b => b.ItemNo)
.ToList();
int maxNum = 0;
foreach (var itemNo in existingItems)
{
if (int.TryParse(itemNo, out var num) && num > maxNum)
maxNum = num;
}
return (maxNum + 1).ToString();
}
} }
catch catch
{ {
@@ -596,19 +509,40 @@ namespace ExportDXF.Services
} }
} }
private void SaveBomItem(BomItem bomItem, ExportContext context) private async Task SaveBomItemAsync(int exportRecordId, BomItem bomItem, ExportContext context)
{ {
try try
{ {
using (var db = _dbContextFactory()) var apiBomItem = new ApiBomItem
{ {
db.BomItems.Add(bomItem); ItemNo = bomItem.ItemNo,
db.SaveChanges(); PartNo = bomItem.PartNo,
SortOrder = bomItem.SortOrder,
Qty = bomItem.Qty,
TotalQty = bomItem.TotalQty,
Description = bomItem.Description,
PartName = bomItem.PartName,
ConfigurationName = bomItem.ConfigurationName,
Material = bomItem.Material
};
if (bomItem.CutTemplate != null)
{
apiBomItem.CutTemplate = new ApiCutTemplate
{
DxfFilePath = bomItem.CutTemplate.DxfFilePath,
ContentHash = bomItem.CutTemplate.ContentHash,
Thickness = bomItem.CutTemplate.Thickness,
KFactor = bomItem.CutTemplate.KFactor,
DefaultBendRadius = bomItem.CutTemplate.DefaultBendRadius
};
} }
await _apiClient.CreateBomItemAsync(exportRecordId, apiBomItem);
} }
catch (Exception dbEx) catch (Exception ex)
{ {
LogProgress(context, $"Database error saving BOM item: {dbEx.Message}", LogLevel.Error); LogProgress(context, $"API error saving BOM item: {ex.Message}", LogLevel.Error);
} }
} }
@@ -623,11 +557,28 @@ namespace ExportDXF.Services
return path; return path;
} }
private void CleanupTempDir(string tempDir)
{
try
{
if (Directory.Exists(tempDir))
Directory.Delete(tempDir, recursive: true);
}
catch
{
// Best-effort cleanup
}
}
private string ParseDrawingNumber(ExportContext context) private string ParseDrawingNumber(ExportContext context)
{ {
// Use explicit Equipment/DrawingNo from the UI when available // Use explicit Equipment/DrawingNo from the UI when available
if (!string.IsNullOrWhiteSpace(context?.Equipment) && !string.IsNullOrWhiteSpace(context?.DrawingNo)) if (!string.IsNullOrWhiteSpace(context?.Equipment))
return $"{context.Equipment} {context.DrawingNo}"; {
return !string.IsNullOrWhiteSpace(context?.DrawingNo)
? $"{context.Equipment} {context.DrawingNo}"
: context.Equipment;
}
// Fallback: parse from prefix or document title // Fallback: parse from prefix or document title
var candidate = context?.FilePrefix; var candidate = context?.FilePrefix;
@@ -637,7 +588,7 @@ namespace ExportDXF.Services
var title = context?.ActiveDocument?.Title; var title = context?.ActiveDocument?.Title;
info = string.IsNullOrWhiteSpace(title) ? null : DrawingInfo.Parse(title); info = string.IsNullOrWhiteSpace(title) ? null : DrawingInfo.Parse(title);
} }
return info != null ? ($"{info.EquipmentNo} {info.DrawingNo}") : null; return info?.ToString();
} }
private void ValidateContext(ExportContext context) private void ValidateContext(ExportContext context)

View File

@@ -1,4 +1,4 @@
using ExportDXF.Extensions; using ExportDXF.Extensions;
using ExportDXF.Models; using ExportDXF.Models;
using ExportDXF.Utilities; using ExportDXF.Utilities;
using SolidWorks.Interop.sldworks; using SolidWorks.Interop.sldworks;
@@ -18,7 +18,7 @@ namespace ExportDXF.Services
/// Returns an Item with export metadata (filename, hash, sheet metal properties), or null if export failed. /// Returns an Item with export metadata (filename, hash, sheet metal properties), or null if export failed.
/// </summary> /// </summary>
/// <param name="part">The part document to export.</param> /// <param name="part">The part document to export.</param>
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param> /// <param name="saveDirectory">The temp directory where the DXF file will be saved.</param>
/// <param name="context">The export context.</param> /// <param name="context">The export context.</param>
Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context); Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
@@ -26,18 +26,15 @@ namespace ExportDXF.Services
/// Exports an item (component from BOM or assembly) to DXF. /// Exports an item (component from BOM or assembly) to DXF.
/// </summary> /// </summary>
/// <param name="item">The item to export.</param> /// <param name="item">The item to export.</param>
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param> /// <param name="saveDirectory">The temp directory where the DXF file will be saved.</param>
/// <param name="context">The export context.</param> /// <param name="context">The export context.</param>
void ExportItem(Item item, string saveDirectory, ExportContext context); void ExportItem(Item item, string saveDirectory, ExportContext context);
} }
public class PartExporter : IPartExporter public class PartExporter : IPartExporter
{ {
private readonly IFileExportService _fileExportService; public PartExporter()
public PartExporter(IFileExportService fileExportService)
{ {
_fileExportService = fileExportService ?? throw new ArgumentNullException(nameof(fileExportService));
} }
public Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context) public Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
@@ -90,25 +87,17 @@ namespace ExportDXF.Services
// Get material // Get material
item.Material = part.GetMaterialPropertyName2(originalConfigName, out _); item.Material = part.GetMaterialPropertyName2(originalConfigName, out _);
// Stash existing file before overwriting
item.StashedFilePath = _fileExportService.StashFile(savePath);
context.GetOrCreateTemplateDrawing(); context.GetOrCreateTemplateDrawing();
if (ExportPartToDxf(part, originalConfigName, savePath, context)) if (ExportPartToDxf(part, originalConfigName, savePath, context))
{ {
item.FileName = Path.GetFileNameWithoutExtension(savePath); item.FileName = Path.GetFileNameWithoutExtension(savePath);
item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath); item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath);
item.LocalTempPath = savePath;
return item; return item;
} }
else else
{ {
// Export failed - restore stashed file
if (item.StashedFilePath != null && File.Exists(item.StashedFilePath))
{
File.Move(item.StashedFilePath, savePath, overwrite: true);
item.StashedFilePath = null;
}
return null; return null;
} }
} }
@@ -155,22 +144,14 @@ namespace ExportDXF.Services
var templateDrawing = context.GetOrCreateTemplateDrawing(); var templateDrawing = context.GetOrCreateTemplateDrawing();
// Stash existing file before overwriting
item.StashedFilePath = _fileExportService.StashFile(savePath);
if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, savePath, context)) if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, savePath, context))
{ {
item.FileName = Path.GetFileNameWithoutExtension(savePath); item.FileName = Path.GetFileNameWithoutExtension(savePath);
item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath); item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath);
item.LocalTempPath = savePath;
} }
else else
{ {
// Export failed - restore stashed file if we have one
if (item.StashedFilePath != null && File.Exists(item.StashedFilePath))
{
File.Move(item.StashedFilePath, savePath, overwrite: true);
item.StashedFilePath = null;
}
LogExportFailure(item, context); LogExportFailure(item, context);
} }
} }