From cf76ca8bb192bb198a0374d63d9c694fc0eca48e Mon Sep 17 00:00:00 2001 From: AJ Isaacs Date: Wed, 18 Feb 2026 20:37:05 -0500 Subject: [PATCH] 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 --- ExportDXF/Forms/MainForm.Designer.cs | 48 ++- ExportDXF/Forms/MainForm.cs | 161 ++++----- ExportDXF/Models/Item.cs | 5 +- ExportDXF/Program.cs | 20 +- ExportDXF/Services/DxfExportService.cs | 475 +++++++++++-------------- ExportDXF/Services/PartExporter.cs | 31 +- 6 files changed, 346 insertions(+), 394 deletions(-) diff --git a/ExportDXF/Forms/MainForm.Designer.cs b/ExportDXF/Forms/MainForm.Designer.cs index 2e25fd5..bc17a3e 100644 --- a/ExportDXF/Forms/MainForm.Designer.cs +++ b/ExportDXF/Forms/MainForm.Designer.cs @@ -42,6 +42,8 @@ namespace ExportDXF.Forms label1 = new System.Windows.Forms.Label(); label2 = new System.Windows.Forms.Label(); drawingNoBox = new System.Windows.Forms.ComboBox(); + titleLabel = new System.Windows.Forms.Label(); + titleBox = new System.Windows.Forms.TextBox(); mainTabControl.SuspendLayout(); logEventsTab.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)logEventsDataGrid).BeginInit(); @@ -54,10 +56,10 @@ namespace ExportDXF.Forms // runButton // 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.Name = "runButton"; - runButton.Size = new System.Drawing.Size(100, 55); + runButton.Size = new System.Drawing.Size(65, 87); runButton.TabIndex = 11; runButton.Text = "Start"; runButton.UseVisualStyleBackColor = true; @@ -66,7 +68,7 @@ namespace ExportDXF.Forms // label3 // label3.AutoSize = true; - label3.Location = new System.Drawing.Point(26, 46); + label3.Location = new System.Drawing.Point(26, 77); label3.Name = "label3"; label3.Size = new System.Drawing.Size(105, 17); label3.TabIndex = 2; @@ -76,7 +78,7 @@ namespace ExportDXF.Forms // viewFlipDeciderBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; viewFlipDeciderBox.FormattingEnabled = true; - viewFlipDeciderBox.Location = new System.Drawing.Point(137, 43); + viewFlipDeciderBox.Location = new System.Drawing.Point(137, 74); viewFlipDeciderBox.Name = "viewFlipDeciderBox"; viewFlipDeciderBox.Size = new System.Drawing.Size(365, 25); viewFlipDeciderBox.TabIndex = 3; @@ -87,11 +89,11 @@ namespace ExportDXF.Forms mainTabControl.Controls.Add(logEventsTab); mainTabControl.Controls.Add(bomTab); mainTabControl.Controls.Add(cutTemplatesTab); - mainTabControl.Location = new System.Drawing.Point(15, 74); + mainTabControl.Location = new System.Drawing.Point(15, 105); mainTabControl.Name = "mainTabControl"; mainTabControl.Padding = new System.Drawing.Point(20, 5); mainTabControl.SelectedIndex = 0; - mainTabControl.Size = new System.Drawing.Size(910, 441); + mainTabControl.Size = new System.Drawing.Size(910, 492); mainTabControl.TabIndex = 12; // // logEventsTab @@ -100,7 +102,7 @@ namespace ExportDXF.Forms logEventsTab.Location = new System.Drawing.Point(4, 30); logEventsTab.Name = "logEventsTab"; 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.Text = "Log Events"; logEventsTab.UseVisualStyleBackColor = true; @@ -112,16 +114,16 @@ namespace ExportDXF.Forms logEventsDataGrid.GridColor = System.Drawing.Color.WhiteSmoke; logEventsDataGrid.Location = new System.Drawing.Point(6, 6); logEventsDataGrid.Name = "logEventsDataGrid"; - logEventsDataGrid.Size = new System.Drawing.Size(890, 391); + logEventsDataGrid.Size = new System.Drawing.Size(890, 440); logEventsDataGrid.TabIndex = 0; // // bomTab // bomTab.Controls.Add(bomDataGrid); - bomTab.Location = new System.Drawing.Point(4, 30); + bomTab.Location = new System.Drawing.Point(4, 28); bomTab.Name = "bomTab"; 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.Text = "Bill Of Materials"; bomTab.UseVisualStyleBackColor = true; @@ -139,10 +141,10 @@ namespace ExportDXF.Forms // cutTemplatesTab // cutTemplatesTab.Controls.Add(cutTemplatesDataGrid); - cutTemplatesTab.Location = new System.Drawing.Point(4, 30); + cutTemplatesTab.Location = new System.Drawing.Point(4, 28); cutTemplatesTab.Name = "cutTemplatesTab"; 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.Text = "Cut Templates"; cutTemplatesTab.UseVisualStyleBackColor = true; @@ -191,10 +193,28 @@ namespace ExportDXF.Forms drawingNoBox.Size = new System.Drawing.Size(119, 25); 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 // 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(equipmentBox); Controls.Add(mainTabControl); @@ -237,5 +257,7 @@ namespace ExportDXF.Forms private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows.Forms.ComboBox drawingNoBox; + private System.Windows.Forms.Label titleLabel; + private System.Windows.Forms.TextBox titleBox; } } diff --git a/ExportDXF/Forms/MainForm.cs b/ExportDXF/Forms/MainForm.cs index d6a2411..1871506 100644 --- a/ExportDXF/Forms/MainForm.cs +++ b/ExportDXF/Forms/MainForm.cs @@ -1,4 +1,4 @@ -using ExportDXF.Data; +using ExportDXF.ApiClient; using ExportDXF.Extensions; using ExportDXF.Models; using ExportDXF.Services; @@ -18,15 +18,14 @@ namespace ExportDXF.Forms { private readonly ISolidWorksService _solidWorksService; private readonly IDxfExportService _exportService; - private readonly IFileExportService _fileExportService; - private readonly Func _dbContextFactory; + private readonly IFabWorksApiClient _apiClient; private CancellationTokenSource _cancellationTokenSource; private readonly BindingList _logEvents; private readonly BindingList _bomItems; private readonly BindingList _cutTemplates; private List _allDrawings; - public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IFileExportService fileExportService, Func dbContextFactory = null) + public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IFabWorksApiClient apiClient) { InitializeComponent(); _solidWorksService = solidWorksService ?? @@ -34,9 +33,8 @@ namespace ExportDXF.Forms _solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged; _exportService = exportService ?? throw new ArgumentNullException(nameof(exportService)); - _fileExportService = fileExportService ?? - throw new ArgumentNullException(nameof(fileExportService)); - _dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext()); + _apiClient = apiClient ?? + throw new ArgumentNullException(nameof(apiClient)); _logEvents = new BindingList(); _bomItems = new BindingList(); _cutTemplates = new BindingList(); @@ -70,9 +68,10 @@ namespace ExportDXF.Forms LogMessage("Connecting to SolidWorks, this may take a minute..."); await _solidWorksService.ConnectAsync(); _solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged; - LogMessage($"Output folder: {_fileExportService.OutputFolder}"); + LogMessage("Files will be uploaded to FabWorks API"); + await LoadDrawingDropdownsAsync(); LogMessage("Ready"); - UpdateActiveDocumentDisplay(); + await UpdateActiveDocumentDisplayAsync(); runButton.Enabled = true; } catch (Exception ex) @@ -274,74 +273,60 @@ namespace ExportDXF.Forms } private void InitializeDrawingDropdowns() + { + // Wire up event handler; actual data loading happens in LoadDrawingDropdownsAsync + equipmentBox.SelectedIndexChanged += EquipmentBox_SelectedIndexChanged; + } + + private async Task LoadDrawingDropdownsAsync() { 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 - 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; + equipmentBox.Items.Add(eq); } + + // Clear _allDrawings — drawing list is now loaded on equipment selection + _allDrawings = new List(); + await UpdateDrawingDropdownAsync(); } catch (Exception ex) { - // Database might not exist yet - that's OK - System.Diagnostics.Debug.WriteLine($"Failed to load drawings from database: {ex.Message}"); + // API might not be available yet - that's OK + 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 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(""); + + 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) @@ -380,11 +365,13 @@ namespace ExportDXF.Forms // Use equipment/drawing values from the UI dropdowns var equipment = equipmentBox.Text?.Trim(); var drawingNo = drawingNoBox.Text?.Trim(); - var filePrefix = !string.IsNullOrEmpty(equipment) && !string.IsNullOrEmpty(drawingNo) - ? $"{equipment} {drawingNo}" + var filePrefix = !string.IsNullOrEmpty(equipment) + ? (!string.IsNullOrEmpty(drawingNo) ? $"{equipment} {drawingNo}" : equipment) : activeDoc.Title; var viewFlipDecider = GetSelectedViewFlipDecider(); + var title = titleBox.Text?.Trim(); + var exportContext = new ExportContext { ActiveDocument = activeDoc, @@ -392,6 +379,7 @@ namespace ExportDXF.Forms FilePrefix = filePrefix, Equipment = equipment, DrawingNo = drawingNo, + Title = string.IsNullOrEmpty(title) ? null : title, EquipmentId = null, CancellationToken = token, ProgressCallback = (msg, level, file) => LogMessage(msg, level, file), @@ -403,11 +391,11 @@ namespace ExportDXF.Forms _cutTemplates.Clear(); LogMessage($"Started at {DateTime.Now:t}"); - LogMessage($"Exporting to: {_fileExportService.OutputFolder}"); + LogMessage("Exporting (files will be uploaded to API)..."); _solidWorksService.SetCommandInProgress(true); - await Task.Run(() => _exportService.Export(exportContext), token); + await Task.Run(async () => await _exportService.ExportAsync(exportContext), token); LogMessage("Done."); } @@ -454,17 +442,17 @@ namespace ExportDXF.Forms runButton.Enabled = true; } - private void OnActiveDocumentChanged(object sender, EventArgs e) + private async void OnActiveDocumentChanged(object sender, EventArgs e) { if (InvokeRequired) { - Invoke(new Action(() => OnActiveDocumentChanged(sender, e))); + Invoke(new Action(async () => await UpdateActiveDocumentDisplayAsync())); return; } - UpdateActiveDocumentDisplay(); + await UpdateActiveDocumentDisplayAsync(); } - private void UpdateActiveDocumentDisplay() + private async Task UpdateActiveDocumentDisplayAsync() { var activeDoc = _solidWorksService.GetActiveDocument(); var docTitle = activeDoc?.Title ?? "No Document Open"; @@ -473,12 +461,12 @@ namespace ExportDXF.Forms if (activeDoc == null) 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; if (!string.IsNullOrEmpty(activeDoc.FilePath)) { - drawingInfo = LookupDrawingInfoFromHistory(activeDoc.FilePath); + drawingInfo = await LookupDrawingInfoFromHistoryAsync(activeDoc.FilePath); } // Fall back to parsing the document title @@ -489,36 +477,37 @@ namespace ExportDXF.Forms if (drawingInfo != null) { - if (!equipmentBox.Items.Contains(drawingInfo.EquipmentNo)) - equipmentBox.Items.Add(drawingInfo.EquipmentNo); - equipmentBox.Text = drawingInfo.EquipmentNo; + if (!string.IsNullOrEmpty(drawingInfo.EquipmentNo)) + { + if (!equipmentBox.Items.Contains(drawingInfo.EquipmentNo)) + equipmentBox.Items.Add(drawingInfo.EquipmentNo); + equipmentBox.Text = drawingInfo.EquipmentNo; + } - if (!drawingNoBox.Items.Contains(drawingInfo.DrawingNo)) - drawingNoBox.Items.Add(drawingInfo.DrawingNo); - drawingNoBox.Text = drawingInfo.DrawingNo; + if (!string.IsNullOrEmpty(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 LookupDrawingInfoFromHistoryAsync(string filePath) { try { - using (var db = _dbContextFactory()) + var dto = await _apiClient.GetExportBySourceFileAsync(filePath); + if (dto != null && !string.IsNullOrEmpty(dto.DrawingNumber)) { - var drawingNumber = db.ExportRecords - .Where(r => r.SourceFilePath.ToLower() == filePath.ToLower() - && !string.IsNullOrEmpty(r.DrawingNumber)) - .OrderByDescending(r => r.Id) - .Select(r => r.DrawingNumber) - .FirstOrDefault(); - - if (drawingNumber != null) - return DrawingInfo.Parse(drawingNumber); + if (!string.IsNullOrEmpty(dto.Title)) + titleBox.Text = dto.Title; + return DrawingInfo.Parse(dto.DrawingNumber); } } 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; diff --git a/ExportDXF/Models/Item.cs b/ExportDXF/Models/Item.cs index 656446f..629d537 100644 --- a/ExportDXF/Models/Item.cs +++ b/ExportDXF/Models/Item.cs @@ -68,8 +68,9 @@ namespace ExportDXF.Services public string ContentHash { get; set; } /// - /// 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. /// - public string StashedFilePath { get; set; } + public string LocalTempPath { get; set; } } } \ No newline at end of file diff --git a/ExportDXF/Program.cs b/ExportDXF/Program.cs index e773f9d..05b5db5 100644 --- a/ExportDXF/Program.cs +++ b/ExportDXF/Program.cs @@ -1,7 +1,9 @@ +using ExportDXF.ApiClient; using ExportDXF.Forms; using ExportDXF.Services; using System; using System.Configuration; +using System.Net.Http; using System.Windows.Forms; namespace ExportDXF @@ -28,29 +30,35 @@ namespace ExportDXF /// public class ServiceContainer { - private readonly string _outputFolder; + private readonly string _apiBaseUrl; public ServiceContainer() { - _outputFolder = ConfigurationManager.AppSettings["ExportOutputFolder"] ?? @"C:\ExportDXF\Output"; + _apiBaseUrl = ConfigurationManager.AppSettings["FabWorksApiUrl"] ?? "http://localhost:5206"; } public MainForm ResolveMainForm() { var solidWorksService = new SolidWorksService(); var bomExtractor = new BomExtractor(); - var fileExportService = new FileExportService(_outputFolder); - var partExporter = new PartExporter(fileExportService); + var partExporter = new PartExporter(); 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( solidWorksService, bomExtractor, partExporter, drawingExporter, - fileExportService); + apiClient); - return new MainForm(solidWorksService, exportService, fileExportService); + return new MainForm(solidWorksService, exportService, apiClient); } } } diff --git a/ExportDXF/Services/DxfExportService.cs b/ExportDXF/Services/DxfExportService.cs index 791ead6..5f8a67b 100644 --- a/ExportDXF/Services/DxfExportService.cs +++ b/ExportDXF/Services/DxfExportService.cs @@ -1,15 +1,15 @@ -using ExportDXF.Data; +using ExportDXF.ApiClient; using ExportDXF.Extensions; using ExportDXF.ItemExtractors; using ExportDXF.Models; using ExportDXF.Utilities; using ExportDXF; -using Microsoft.EntityFrameworkCore; using SolidWorks.Interop.sldworks; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; namespace ExportDXF.Services { @@ -19,11 +19,12 @@ namespace ExportDXF.Services /// Exports the document specified in the context to DXF format. /// /// The export context containing all necessary information. - void Export(ExportContext context); + Task ExportAsync(ExportContext context); } /// /// 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. /// public class DxfExportService : IDxfExportService { @@ -31,29 +32,26 @@ namespace ExportDXF.Services private readonly IBomExtractor _bomExtractor; private readonly IPartExporter _partExporter; private readonly IDrawingExporter _drawingExporter; - private readonly IFileExportService _fileExportService; - private readonly Func _dbContextFactory; + private readonly IFabWorksApiClient _apiClient; public DxfExportService( ISolidWorksService solidWorksService, IBomExtractor bomExtractor, IPartExporter partExporter, IDrawingExporter drawingExporter, - IFileExportService fileExportService, - Func dbContextFactory = null) + IFabWorksApiClient apiClient) { _solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService)); _bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor)); _partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter)); _drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter)); - _fileExportService = fileExportService ?? throw new ArgumentNullException(nameof(fileExportService)); - _dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext()); + _apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient)); } /// /// Exports the document specified in the context to DXF format. /// - public void Export(ExportContext context) + public async Task ExportAsync(ExportContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); @@ -62,26 +60,26 @@ namespace ExportDXF.Services SetupExportContext(context); var startTime = DateTime.Now; - - var drawingNumber = ParseDrawingNumber(context); - var outputFolder = _fileExportService.GetDrawingOutputFolder(context.Equipment, context.DrawingNo); + var tempDir = CreateTempWorkDir(); try { _solidWorksService.EnableUserControl(false); + var drawingNumber = ParseDrawingNumber(context); + switch (context.ActiveDocument.DocumentType) { case DocumentType.Part: - ExportPart(context, outputFolder, drawingNumber); + await ExportPartAsync(context, tempDir, drawingNumber); break; case DocumentType.Assembly: - ExportAssembly(context, outputFolder, drawingNumber); + await ExportAssemblyAsync(context, tempDir, drawingNumber); break; case DocumentType.Drawing: - ExportDrawing(context, drawingNumber, outputFolder); + await ExportDrawingAsync(context, drawingNumber, tempDir); break; default: @@ -93,6 +91,7 @@ namespace ExportDXF.Services { CleanupExportContext(context); _solidWorksService.EnableUserControl(true); + CleanupTempDir(tempDir); var duration = DateTime.Now - startTime; LogProgress(context, $"Run time: {duration.ToReadableFormat()}"); @@ -101,7 +100,7 @@ namespace ExportDXF.Services #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"); @@ -112,14 +111,14 @@ namespace ExportDXF.Services return; } - var exportRecord = CreateExportRecord(context, drawingNumber, outputFolder); - var item = _partExporter.ExportSinglePart(part, outputFolder, context); + var exportRecord = await CreateExportRecordAsync(context, drawingNumber); + var item = _partExporter.ExportSinglePart(part, tempDir, context); if (item != null) { - // Assign auto-incremented item number - var nextItemNo = GetNextItemNumber(drawingNumber); - item.ItemNo = nextItemNo; + // Check if this part+config already has a BOM item for this drawing + var existingItemNo = await FindExistingItemNoAsync(exportRecord?.Id, item.PartName, item.Configuration); + item.ItemNo = existingItemNo ?? await GetNextItemNumberAsync(drawingNumber); var bomItem = new BomItem { @@ -135,29 +134,31 @@ namespace ExportDXF.Services 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"); - bomItem.CutTemplate = new CutTemplate + var uploadResult = await UploadDxfAsync(item, context); + if (uploadResult != null) { - DxfFilePath = dxfPath, - ContentHash = item.ContentHash, - 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); + bomItem.CutTemplate = new CutTemplate + { + DxfFilePath = uploadResult.StoredFilePath, + ContentHash = item.ContentHash, + Thickness = item.Thickness > 0 ? item.Thickness : null, + KFactor = item.KFactor > 0 ? item.KFactor : null, + DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null + }; + } } context.BomItemCallback?.Invoke(bomItem); 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, "Fetching components..."); @@ -179,23 +180,31 @@ namespace ExportDXF.Services 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 - var nextNum = int.Parse(GetNextItemNumber(drawingNumber)); + // Check existing BOM items and reuse item numbers, or assign new ones + var nextNum = int.Parse(await GetNextItemNumberAsync(drawingNumber)); foreach (var item in items) { if (string.IsNullOrWhiteSpace(item.ItemNo)) { - item.ItemNo = nextNum.ToString(); - nextNum++; + var existingItemNo = await FindExistingItemNoAsync(exportRecord?.Id, item.PartName, item.Configuration); + 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, "Finding BOM tables..."); @@ -217,88 +226,46 @@ namespace ExportDXF.Services LogProgress(context, $"Found {items.Count} component(s)"); - // Export drawing to PDF - var tempDir = CreateTempWorkDir(); + // Export drawing to PDF in temp dir _drawingExporter.ExportToPdf(drawing, tempDir, context); - // Copy PDF to output folder with versioning - string pdfStashPath = null; - string savedPdfPath = null; + // Create export record via API + var exportRecord = await CreateExportRecordAsync(context, drawingNumber); + + // Upload PDF to API with versioning try { var pdfs = Directory.GetFiles(tempDir, "*.pdf"); if (pdfs.Length > 0) { - // Determine the destination path to stash the existing file - var pdfFileName = !string.IsNullOrEmpty(drawingNumber) - ? $"{drawingNumber}.pdf" - : Path.GetFileName(pdfs[0]); - var pdfDestPath = Path.Combine(drawingOutputFolder, pdfFileName); + var pdfTempPath = pdfs[0]; + var pdfHash = ContentHasher.ComputeFileHash(pdfTempPath); - pdfStashPath = _fileExportService.StashFile(pdfDestPath); - savedPdfPath = _fileExportService.SavePdfFile(pdfs[0], drawingNumber, drawingOutputFolder); + var uploadResult = await _apiClient.UploadPdfAsync( + 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) { - LogProgress(context, $"PDF save 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); + LogProgress(context, $"PDF upload error: {ex.Message}", LogLevel.Error); } // Export parts to DXF and save BOM items - ExportItems(items, drawingOutputFolder, context, exportRecord?.Id); + await ExportItemsAsync(items, tempDir, context, exportRecord?.Id); } #endregion @@ -357,7 +324,7 @@ namespace ExportDXF.Services } } - private void ExportItems(List items, string saveDirectory, ExportContext context, int? exportRecordId = null) + private async Task ExportItemsAsync(List items, string tempDir, ExportContext context, int? exportRecordId = null) { int successCount = 0; int skippedCount = 0; @@ -375,7 +342,7 @@ namespace ExportDXF.Services try { // 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) var bomItem = new BomItem @@ -392,23 +359,23 @@ namespace ExportDXF.Services Material = item.Material ?? "" }; - // Only create CutTemplate if DXF was exported successfully - if (!string.IsNullOrEmpty(item.FileName)) + // Only upload and create CutTemplate if DXF was exported successfully + if (!string.IsNullOrEmpty(item.LocalTempPath)) { successCount++; - var dxfPath = Path.Combine(saveDirectory, item.FileName + ".dxf"); - bomItem.CutTemplate = new CutTemplate + var uploadResult = await UploadDxfAsync(item, context); + if (uploadResult != null) { - DxfFilePath = dxfPath, - ContentHash = item.ContentHash, - 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); + bomItem.CutTemplate = new CutTemplate + { + DxfFilePath = uploadResult.StoredFilePath, + ContentHash = item.ContentHash, + Thickness = item.Thickness > 0 ? item.Thickness : null, + KFactor = item.KFactor > 0 ? item.KFactor : null, + DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null + }; + } } else { @@ -418,21 +385,10 @@ namespace ExportDXF.Services // Add to UI 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) { - try - { - using (var db = _dbContextFactory()) - { - db.BomItems.Add(bomItem); - db.SaveChanges(); - } - } - catch (Exception dbEx) - { - LogProgress(context, $"Database error saving BOM item: {dbEx.Message}", LogLevel.Error); - } + await SaveBomItemAsync(exportRecordId.Value, bomItem, context); } } catch (Exception ex) @@ -449,146 +405,103 @@ namespace ExportDXF.Services 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 - #region Versioning + #region File Upload - private void HandleDxfVersioning(Item item, string dxfPath, 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) + private async Task UploadDxfAsync(Item item, ExportContext context) { 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()) - { - var previousRecord = db.ExportRecords - .Where(r => r.DrawingNumber == drawingNumber && r.PdfContentHash != null) - .OrderByDescending(r => r.Id) - .FirstOrDefault(); + if (result.WasUnchanged) + LogProgress(context, $"DXF unchanged: {result.FileName}", LogLevel.Info); + else if (result.IsNewFile) + LogProgress(context, $"Exported: {result.FileName}", LogLevel.Info); + else + LogProgress(context, $"DXF updated: {result.FileName}", LogLevel.Info); - if (previousRecord != null && previousRecord.PdfContentHash == newHash) - { - 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; + return result; } catch (Exception ex) { - LogProgress(context, $"PDF versioning check failed: {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); + LogProgress(context, $"DXF upload failed for {item.FileName}: {ex.Message}", LogLevel.Warning); return null; } } - private string GetNextItemNumber(string drawingNumber) + #endregion + + #region API Helpers + + private async Task 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 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 GetNextItemNumberAsync(string drawingNumber) { if (string.IsNullOrEmpty(drawingNumber)) return "1"; try { - using (var db = _dbContextFactory()) - { - 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(); - } + return await _apiClient.GetNextItemNumberAsync(drawingNumber); } 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 { - using (var db = _dbContextFactory()) + var apiBomItem = new ApiBomItem { - db.BomItems.Add(bomItem); - db.SaveChanges(); + ItemNo = bomItem.ItemNo, + 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; } + private void CleanupTempDir(string tempDir) + { + try + { + if (Directory.Exists(tempDir)) + Directory.Delete(tempDir, recursive: true); + } + catch + { + // Best-effort cleanup + } + } + private string ParseDrawingNumber(ExportContext context) { // Use explicit Equipment/DrawingNo from the UI when available - if (!string.IsNullOrWhiteSpace(context?.Equipment) && !string.IsNullOrWhiteSpace(context?.DrawingNo)) - return $"{context.Equipment} {context.DrawingNo}"; + if (!string.IsNullOrWhiteSpace(context?.Equipment)) + { + return !string.IsNullOrWhiteSpace(context?.DrawingNo) + ? $"{context.Equipment} {context.DrawingNo}" + : context.Equipment; + } // Fallback: parse from prefix or document title var candidate = context?.FilePrefix; @@ -637,7 +588,7 @@ namespace ExportDXF.Services var title = context?.ActiveDocument?.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) diff --git a/ExportDXF/Services/PartExporter.cs b/ExportDXF/Services/PartExporter.cs index 9fecf6e..f7522ad 100644 --- a/ExportDXF/Services/PartExporter.cs +++ b/ExportDXF/Services/PartExporter.cs @@ -1,4 +1,4 @@ -using ExportDXF.Extensions; +using ExportDXF.Extensions; using ExportDXF.Models; using ExportDXF.Utilities; 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. /// /// The part document to export. - /// The directory where the DXF file will be saved. + /// The temp directory where the DXF file will be saved. /// The export 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. /// /// The item to export. - /// The directory where the DXF file will be saved. + /// The temp directory where the DXF file will be saved. /// The export context. void ExportItem(Item item, string saveDirectory, ExportContext context); } public class PartExporter : IPartExporter { - private readonly IFileExportService _fileExportService; - - public PartExporter(IFileExportService fileExportService) + public PartExporter() { - _fileExportService = fileExportService ?? throw new ArgumentNullException(nameof(fileExportService)); } public Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context) @@ -90,25 +87,17 @@ namespace ExportDXF.Services // Get material item.Material = part.GetMaterialPropertyName2(originalConfigName, out _); - // Stash existing file before overwriting - item.StashedFilePath = _fileExportService.StashFile(savePath); - context.GetOrCreateTemplateDrawing(); if (ExportPartToDxf(part, originalConfigName, savePath, context)) { item.FileName = Path.GetFileNameWithoutExtension(savePath); item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath); + item.LocalTempPath = savePath; return item; } 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; } } @@ -155,22 +144,14 @@ namespace ExportDXF.Services var templateDrawing = context.GetOrCreateTemplateDrawing(); - // Stash existing file before overwriting - item.StashedFilePath = _fileExportService.StashFile(savePath); - if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, savePath, context)) { item.FileName = Path.GetFileNameWithoutExtension(savePath); item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath); + item.LocalTempPath = savePath; } 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); } }