refactor: rewrite DxfExportService for local file export with revision tracking

- Replace API client with ExcelExportService and LogFileService
- Export DXFs to Templates folder with template-based naming
- Content hash comparison against existing xlsx for revision tracking
- Changed DXFs get revision suffix (e.g., PT03 Rev2.dxf)
- Unchanged DXFs are skipped
- Raw BOM table copied from SolidWorks drawing to Excel
- PDF exported directly to output folder
- Update PartExporter to remove FilePrefix dependency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 22:16:47 -04:00
parent c6dde6e217
commit 9bc29e98c8
2 changed files with 212 additions and 324 deletions

View File

@@ -1,9 +1,7 @@
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 SolidWorks.Interop.sldworks; using SolidWorks.Interop.sldworks;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -15,42 +13,34 @@ namespace ExportDXF.Services
{ {
public interface IDxfExportService public interface IDxfExportService
{ {
/// <summary>
/// Exports the document specified in the context to DXF format.
/// </summary>
/// <param name="context">The export context containing all necessary information.</param>
Task ExportAsync(ExportContext context); Task ExportAsync(ExportContext context);
} }
/// <summary>
/// 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>
public class DxfExportService : IDxfExportService public class DxfExportService : IDxfExportService
{ {
private readonly ISolidWorksService _solidWorksService; private readonly ISolidWorksService _solidWorksService;
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 IFabWorksApiClient _apiClient; private readonly ExcelExportService _excelExportService;
private readonly LogFileService _logFileService;
public DxfExportService( public DxfExportService(
ISolidWorksService solidWorksService, ISolidWorksService solidWorksService,
IBomExtractor bomExtractor, IBomExtractor bomExtractor,
IPartExporter partExporter, IPartExporter partExporter,
IDrawingExporter drawingExporter, IDrawingExporter drawingExporter,
IFabWorksApiClient apiClient) ExcelExportService excelExportService,
LogFileService logFileService)
{ {
_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));
_apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient)); _excelExportService = excelExportService ?? throw new ArgumentNullException(nameof(excelExportService));
_logFileService = logFileService ?? throw new ArgumentNullException(nameof(logFileService));
} }
/// <summary>
/// Exports the document specified in the context to DXF format.
/// </summary>
public async Task ExportAsync(ExportContext context) public async Task ExportAsync(ExportContext context)
{ {
if (context == null) if (context == null)
@@ -59,6 +49,28 @@ namespace ExportDXF.Services
ValidateContext(context); ValidateContext(context);
SetupExportContext(context); SetupExportContext(context);
var outputFolder = context.OutputFolder;
if (!Directory.Exists(outputFolder))
Directory.CreateDirectory(outputFolder);
var prefix = FilenameTemplateParser.GetPrefix(
context.FilenameTemplate,
context.ActiveDocument.Title);
var xlsxPath = Path.Combine(outputFolder, $"{prefix}.xlsx");
var logPath = Path.Combine(outputFolder, $"{prefix}.log");
_logFileService.StartExportLog(logPath);
_logFileService.LogInfo($"Export started: {context.ActiveDocument.FilePath}");
_logFileService.LogInfo($"Template: {context.FilenameTemplate}");
_logFileService.LogInfo($"Output: {outputFolder}");
// Read existing cut templates for revision comparison
var existingTemplates = _excelExportService.ReadExistingCutTemplates(xlsxPath);
var bomItems = new List<BomItem>();
List<Dictionary<string, string>> rawBomTable = null;
var startTime = DateTime.Now; var startTime = DateTime.Now;
var tempDir = CreateTempWorkDir(); var tempDir = CreateTempWorkDir();
@@ -66,26 +78,39 @@ namespace ExportDXF.Services
{ {
_solidWorksService.EnableUserControl(false); _solidWorksService.EnableUserControl(false);
var drawingNumber = ParseDrawingNumber(context);
switch (context.ActiveDocument.DocumentType) switch (context.ActiveDocument.DocumentType)
{ {
case DocumentType.Part: case DocumentType.Part:
await ExportPartAsync(context, tempDir, drawingNumber); await ExportPartAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
break; break;
case DocumentType.Assembly: case DocumentType.Assembly:
await ExportAssemblyAsync(context, tempDir, drawingNumber); await ExportAssemblyAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
break; break;
case DocumentType.Drawing: case DocumentType.Drawing:
await ExportDrawingAsync(context, drawingNumber, tempDir); rawBomTable = await ExportDrawingAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
break; break;
default: default:
LogProgress(context, "Unknown document type.", LogLevel.Error); LogProgress(context, "Unknown document type.", LogLevel.Error);
break; break;
} }
// Write Excel file
_excelExportService.Write(xlsxPath, rawBomTable, bomItems);
_logFileService.LogInfo($"Wrote {Path.GetFileName(xlsxPath)}");
LogProgress(context, $"Saved {Path.GetFileName(xlsxPath)}");
}
catch (OperationCanceledException)
{
_logFileService.LogWarning("Export cancelled by user");
throw;
}
catch (Exception ex)
{
_logFileService.LogError($"Export failed: {ex.Message}");
throw;
} }
finally finally
{ {
@@ -94,13 +119,19 @@ namespace ExportDXF.Services
CleanupTempDir(tempDir); CleanupTempDir(tempDir);
var duration = DateTime.Now - startTime; var duration = DateTime.Now - startTime;
_logFileService.LogInfo($"Run time: {duration.ToReadableFormat()}");
LogProgress(context, $"Run time: {duration.ToReadableFormat()}"); LogProgress(context, $"Run time: {duration.ToReadableFormat()}");
} }
} }
#region Export Methods by Document Type #region Export Methods by Document Type
private async Task ExportPartAsync(ExportContext context, string tempDir, string drawingNumber) private async Task ExportPartAsync(
ExportContext context,
string tempDir,
string outputFolder,
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
List<BomItem> bomItems)
{ {
LogProgress(context, "Active document is a Part"); LogProgress(context, "Active document is a Part");
@@ -111,54 +142,25 @@ namespace ExportDXF.Services
return; return;
} }
var exportRecord = await CreateExportRecordAsync(context, drawingNumber);
var item = _partExporter.ExportSinglePart(part, tempDir, context); var item = _partExporter.ExportSinglePart(part, tempDir, context);
if (item != null) if (item != null)
{ {
// Check if this part+config already has a BOM item for this drawing item.ItemNo = "1";
var existingItemNo = await FindExistingItemNoAsync(exportRecord?.Id, item.PartName, item.Configuration);
item.ItemNo = existingItemNo ?? await GetNextItemNumberAsync(drawingNumber);
var bomItem = new BomItem
{
ExportRecordId = exportRecord?.Id ?? 0,
ItemNo = item.ItemNo,
PartNo = item.FileName ?? item.PartName ?? "",
SortOrder = 0,
Qty = item.Quantity,
TotalQty = item.Quantity,
Description = item.Description ?? "",
PartName = item.PartName ?? "",
ConfigurationName = item.Configuration ?? "",
Material = item.Material ?? ""
};
// Upload DXF to API and get stored path
if (!string.IsNullOrEmpty(item.LocalTempPath))
{
var uploadResult = await UploadDxfAsync(item, context);
if (uploadResult != null)
{
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
};
}
}
var bomItem = CreateBomItem(item);
PlaceDxfFile(item, context, outputFolder, existingTemplates, bomItem);
bomItems.Add(bomItem);
context.BomItemCallback?.Invoke(bomItem); context.BomItemCallback?.Invoke(bomItem);
if (exportRecord != null)
await SaveBomItemAsync(exportRecord.Id, bomItem, context);
} }
} }
private async Task ExportAssemblyAsync(ExportContext context, string tempDir, string drawingNumber) private async Task ExportAssemblyAsync(
ExportContext context,
string tempDir,
string outputFolder,
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
List<BomItem> bomItems)
{ {
LogProgress(context, "Active document is an Assembly"); LogProgress(context, "Active document is an Assembly");
LogProgress(context, "Fetching components..."); LogProgress(context, "Fetching components...");
@@ -180,31 +182,26 @@ namespace ExportDXF.Services
LogProgress(context, $"Found {items.Count} item(s)."); LogProgress(context, $"Found {items.Count} item(s).");
var exportRecord = await CreateExportRecordAsync(context, drawingNumber); // Assign item numbers
int nextNum = 1;
// Check existing BOM items and reuse item numbers, or assign new ones
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))
{ {
var existingItemNo = await FindExistingItemNoAsync(exportRecord?.Id, item.PartName, item.Configuration); item.ItemNo = nextNum.ToString();
if (existingItemNo != null) nextNum++;
{
item.ItemNo = existingItemNo;
}
else
{
item.ItemNo = nextNum.ToString();
nextNum++;
}
} }
} }
await ExportItemsAsync(items, tempDir, context, exportRecord?.Id); await ExportItemsAsync(items, tempDir, outputFolder, context, existingTemplates, bomItems);
} }
private async Task ExportDrawingAsync(ExportContext context, string drawingNumber, string tempDir) private async Task<List<Dictionary<string, string>>> ExportDrawingAsync(
ExportContext context,
string tempDir,
string outputFolder,
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
List<BomItem> bomItems)
{ {
LogProgress(context, "Active document is a Drawing"); LogProgress(context, "Active document is a Drawing");
LogProgress(context, "Finding BOM tables..."); LogProgress(context, "Finding BOM tables...");
@@ -213,59 +210,35 @@ namespace ExportDXF.Services
if (drawing == null) if (drawing == null)
{ {
LogProgress(context, "Failed to get drawing document.", LogLevel.Error); LogProgress(context, "Failed to get drawing document.", LogLevel.Error);
return; return null;
} }
// Read raw BOM table for Excel output
var rawBomTable = new List<Dictionary<string, string>>();
var bomTables = drawing.GetBomTables();
foreach (var table in bomTables)
{
var rows = RawBomTableReader.Read(table);
rawBomTable.AddRange(rows);
}
// Extract items for DXF export
var items = _bomExtractor.ExtractFromDrawing(drawing, context.ProgressCallback); var items = _bomExtractor.ExtractFromDrawing(drawing, context.ProgressCallback);
if (items == null || items.Count == 0) if (items == null || items.Count == 0)
{ {
LogProgress(context, "Error: Bill of materials not found.", LogLevel.Error); LogProgress(context, "Error: Bill of materials not found.", LogLevel.Error);
return; return rawBomTable;
} }
LogProgress(context, $"Found {items.Count} component(s)"); LogProgress(context, $"Found {items.Count} component(s)");
// Export drawing to PDF in temp dir // Export drawing to PDF in output folder
_drawingExporter.ExportToPdf(drawing, tempDir, context); _drawingExporter.ExportToPdf(drawing, outputFolder, context);
// Create export record via API await ExportItemsAsync(items, tempDir, outputFolder, context, existingTemplates, bomItems);
var exportRecord = await CreateExportRecordAsync(context, drawingNumber);
// Upload PDF to API with versioning return rawBomTable;
try
{
var pdfs = Directory.GetFiles(tempDir, "*.pdf");
if (pdfs.Length > 0)
{
var pdfTempPath = pdfs[0];
var pdfHash = ContentHasher.ComputeFileHash(pdfTempPath);
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 upload error: {ex.Message}", LogLevel.Error);
}
// Export parts to DXF and save BOM items
await ExportItemsAsync(items, tempDir, context, exportRecord?.Id);
} }
#endregion #endregion
@@ -274,17 +247,12 @@ namespace ExportDXF.Services
private void SetupExportContext(ExportContext context) private void SetupExportContext(ExportContext context)
{ {
// Set up SolidWorks application reference
context.SolidWorksApp = _solidWorksService.GetNativeSldWorks(); context.SolidWorksApp = _solidWorksService.GetNativeSldWorks();
if (context.SolidWorksApp == null) if (context.SolidWorksApp == null)
{
throw new InvalidOperationException("SolidWorks service is not connected."); throw new InvalidOperationException("SolidWorks service is not connected.");
}
// Set up drawing template path
context.TemplateDrawing = null; context.TemplateDrawing = null;
LogProgress(context, "Export context initialized"); LogProgress(context, "Export context initialized");
} }
@@ -292,13 +260,11 @@ namespace ExportDXF.Services
{ {
try try
{ {
// Clean up template drawing if it was created
context.CleanupTemplateDrawing(); context.CleanupTemplateDrawing();
} }
catch (Exception ex) catch (Exception ex)
{ {
LogProgress(context, $"Warning: Failed to cleanup template drawing: {ex.Message}", LogLevel.Warning); LogProgress(context, $"Warning: Failed to cleanup template drawing: {ex.Message}", LogLevel.Warning);
// Don't throw - this is cleanup code
} }
} }
@@ -314,7 +280,6 @@ namespace ExportDXF.Services
{ {
TopLevelOnly = false TopLevelOnly = false
}; };
return extractor.GetItems(); return extractor.GetItems();
} }
catch (Exception ex) catch (Exception ex)
@@ -324,10 +289,17 @@ namespace ExportDXF.Services
} }
} }
private async Task ExportItemsAsync(List<Item> items, string tempDir, ExportContext context, int? exportRecordId = null) private async Task ExportItemsAsync(
List<Item> items,
string tempDir,
string outputFolder,
ExportContext context,
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
List<BomItem> bomItems)
{ {
int successCount = 0; int successCount = 0;
int skippedCount = 0; int skippedCount = 0;
int unchangedCount = 0;
int failureCount = 0; int failureCount = 0;
int sortOrder = 0; int sortOrder = 0;
@@ -341,215 +313,150 @@ namespace ExportDXF.Services
try try
{ {
// PartExporter will handle template drawing creation through context
_partExporter.ExportItem(item, tempDir, context); _partExporter.ExportItem(item, tempDir, context);
// Always create BomItem for every item (sheet metal or not) var bomItem = CreateBomItem(item);
var bomItem = new BomItem bomItem.SortOrder = sortOrder++;
{
ExportRecordId = exportRecordId ?? 0,
ItemNo = item.ItemNo ?? "",
PartNo = item.FileName ?? item.PartName ?? "",
SortOrder = sortOrder++,
Qty = item.Quantity,
TotalQty = item.Quantity,
Description = item.Description ?? "",
PartName = item.PartName ?? "",
ConfigurationName = item.Configuration ?? "",
Material = item.Material ?? ""
};
// Only upload and create CutTemplate if DXF was exported successfully
if (!string.IsNullOrEmpty(item.LocalTempPath)) if (!string.IsNullOrEmpty(item.LocalTempPath))
{ {
successCount++; var wasPlaced = PlaceDxfFile(item, context, outputFolder, existingTemplates, bomItem);
if (wasPlaced)
var uploadResult = await UploadDxfAsync(item, context); successCount++;
if (uploadResult != null) else
{ unchangedCount++;
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 else
{ {
skippedCount++; skippedCount++;
} }
// Add to UI bomItems.Add(bomItem);
context.BomItemCallback?.Invoke(bomItem); context.BomItemCallback?.Invoke(bomItem);
// Save BOM item via API if we have an export record
if (exportRecordId.HasValue)
{
await SaveBomItemAsync(exportRecordId.Value, bomItem, context);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", LogLevel.Error); LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", LogLevel.Error);
_logFileService.LogError($"Item {item.ItemNo}: {ex.Message}");
failureCount++; failureCount++;
} }
} }
var summary = $"Export complete: {successCount} exported, {skippedCount} skipped"; var summary = $"Export complete: {successCount} exported";
if (unchangedCount > 0)
summary += $", {unchangedCount} unchanged";
if (skippedCount > 0)
summary += $", {skippedCount} skipped (non-sheet-metal)";
if (failureCount > 0) if (failureCount > 0)
summary += $", {failureCount} failed"; summary += $", {failureCount} failed";
LogProgress(context, summary, failureCount > 0 ? LogLevel.Warning : LogLevel.Info); LogProgress(context, summary, failureCount > 0 ? LogLevel.Warning : LogLevel.Info);
_logFileService.LogInfo(summary);
if (exportRecordId.HasValue)
{
LogProgress(context, $"BOM items saved (ExportRecord ID: {exportRecordId.Value})", LogLevel.Info);
}
} }
#endregion /// <summary>
/// Places a DXF file in the output folder with revision tracking.
#region File Upload /// Returns true if a new file was written, false if unchanged.
/// </summary>
private async Task<ApiFileUploadResponse> UploadDxfAsync(Item item, ExportContext context) private bool PlaceDxfFile(
Item item,
ExportContext context,
string outputFolder,
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
BomItem bomItem)
{ {
try var baseName = FilenameTemplateParser.Evaluate(context.FilenameTemplate, item);
var contentHash = item.ContentHash;
int revision = 1;
string dxfFileName;
// Check existing templates for revision comparison
if (existingTemplates.TryGetValue(item.ItemNo, out var existing))
{ {
var result = await _apiClient.UploadDxfAsync( if (existing.ContentHash == contentHash)
item.LocalTempPath,
context.Equipment,
context.DrawingNo,
item.ItemNo,
item.ContentHash);
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);
return result;
}
catch (Exception ex)
{
LogProgress(context, $"DXF upload failed for {item.FileName}: {ex.Message}", LogLevel.Warning);
return null;
}
}
#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, // Unchanged — skip file write, keep existing
DrawingNumber = dto.DrawingNumber, dxfFileName = existing.FileName;
EquipmentNo = dto.EquipmentNo, revision = existing.Revision;
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); LogProgress(context, $"Unchanged: {dxfFileName}", LogLevel.Info, item.PartName);
return record; _logFileService.LogInfo($"Unchanged: {dxfFileName}");
}
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) bomItem.CutTemplate = new CutTemplate
{
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))
return "1";
try
{
return await _apiClient.GetNextItemNumberAsync(drawingNumber);
}
catch
{
return "1";
}
}
private async Task SaveBomItemAsync(int exportRecordId, BomItem bomItem, ExportContext context)
{
try
{
var apiBomItem = new ApiBomItem
{
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, DxfFilePath = dxfFileName,
ContentHash = bomItem.CutTemplate.ContentHash, ContentHash = contentHash,
Thickness = bomItem.CutTemplate.Thickness, Revision = revision,
KFactor = bomItem.CutTemplate.KFactor, Thickness = item.Thickness > 0 ? item.Thickness : (double?)null,
DefaultBendRadius = bomItem.CutTemplate.DefaultBendRadius KFactor = item.KFactor > 0 ? item.KFactor : (double?)null,
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : (double?)null
}; };
}
await _apiClient.CreateBomItemAsync(exportRecordId, apiBomItem); return false;
}
else
{
// Changed — increment revision
revision = existing.Revision + 1;
dxfFileName = GetRevisionFileName(baseName, revision);
LogProgress(context, $"Updated: {dxfFileName} (Rev{revision})", LogLevel.Info, item.PartName);
_logFileService.LogInfo($"Updated: {dxfFileName} (was {existing.FileName})");
}
} }
catch (Exception ex) else
{ {
LogProgress(context, $"API error saving BOM item: {ex.Message}", LogLevel.Error); // New item
dxfFileName = $"{baseName}.dxf";
LogProgress(context, $"Exported: {dxfFileName}", LogLevel.Info, item.PartName);
_logFileService.LogInfo($"Exported: {dxfFileName}");
} }
// Copy from temp to output
var destPath = Path.Combine(outputFolder, dxfFileName);
File.Copy(item.LocalTempPath, destPath, overwrite: true);
bomItem.CutTemplate = new CutTemplate
{
DxfFilePath = Path.GetFileNameWithoutExtension(dxfFileName),
ContentHash = contentHash,
Revision = revision,
Thickness = item.Thickness > 0 ? item.Thickness : (double?)null,
KFactor = item.KFactor > 0 ? item.KFactor : (double?)null,
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : (double?)null
};
return true;
}
private BomItem CreateBomItem(Item item)
{
return new BomItem
{
ItemNo = item.ItemNo ?? "",
PartNo = item.FileName ?? item.PartName ?? "",
SortOrder = 0,
Qty = item.Quantity,
TotalQty = item.Quantity,
Description = item.Description ?? "",
PartName = item.PartName ?? "",
ConfigurationName = item.Configuration ?? "",
Material = item.Material ?? ""
};
} }
#endregion #endregion
#region Helper Methods #region Helper Methods
private string GetRevisionFileName(string baseName, int revision)
{
if (revision <= 1)
return $"{baseName}.dxf";
return $"{baseName} Rev{revision}.dxf";
}
private string CreateTempWorkDir() private string CreateTempWorkDir()
{ {
var path = Path.Combine(Path.GetTempPath(), "ExportDXF-" + Guid.NewGuid().ToString("N")); var path = Path.Combine(Path.GetTempPath(), "ExportDXF-" + Guid.NewGuid().ToString("N"));
@@ -570,27 +477,6 @@ namespace ExportDXF.Services
} }
} }
private string ParseDrawingNumber(ExportContext context)
{
// Use explicit Equipment/DrawingNo from the UI when available
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;
var info = string.IsNullOrWhiteSpace(candidate) ? null : DrawingInfo.Parse(candidate);
if (info == null)
{
var title = context?.ActiveDocument?.Title;
info = string.IsNullOrWhiteSpace(title) ? null : DrawingInfo.Parse(title);
}
return info?.ToString();
}
private void ValidateContext(ExportContext context) private void ValidateContext(ExportContext context)
{ {
if (context.ActiveDocument == null) if (context.ActiveDocument == null)
@@ -598,6 +484,12 @@ namespace ExportDXF.Services
if (context.ProgressCallback == null) if (context.ProgressCallback == null)
throw new ArgumentException("ProgressCallback cannot be null.", nameof(context)); throw new ArgumentException("ProgressCallback cannot be null.", nameof(context));
if (string.IsNullOrWhiteSpace(context.FilenameTemplate))
throw new ArgumentException("FilenameTemplate cannot be null or empty.", nameof(context));
if (string.IsNullOrWhiteSpace(context.OutputFolder))
throw new ArgumentException("OutputFolder cannot be null or empty.", nameof(context));
} }
private void LogProgress(ExportContext context, string message, LogLevel level = LogLevel.Info, string file = null) private void LogProgress(ExportContext context, string message, LogLevel level = LogLevel.Info, string file = null)

View File

@@ -54,7 +54,7 @@ namespace ExportDXF.Services
try try
{ {
var fileName = GetSinglePartFileName(model, context.FilePrefix); var fileName = GetSinglePartFileName(model);
var savePath = Path.Combine(saveDirectory, fileName + ".dxf"); var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
// Build result item with metadata // Build result item with metadata
@@ -139,7 +139,7 @@ namespace ExportDXF.Services
EnrichItemWithMetadata(item, model, part); EnrichItemWithMetadata(item, model, part);
var fileName = GetItemFileName(item, context.FilePrefix); var fileName = GetItemFileName(item);
var savePath = Path.Combine(saveDirectory, fileName + ".dxf"); var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
var templateDrawing = context.GetOrCreateTemplateDrawing(); var templateDrawing = context.GetOrCreateTemplateDrawing();
@@ -332,7 +332,7 @@ namespace ExportDXF.Services
} }
} }
private string GetSinglePartFileName(ModelDoc2 model, string prefix) private string GetSinglePartFileName(ModelDoc2 model)
{ {
var title = model.GetTitle().Replace(".SLDPRT", ""); var title = model.GetTitle().Replace(".SLDPRT", "");
var config = model.ConfigurationManager.ActiveConfiguration.Name; var config = model.ConfigurationManager.ActiveConfiguration.Name;
@@ -341,17 +341,13 @@ namespace ExportDXF.Services
return isDefaultConfig ? title : $"{title} [{config}]"; return isDefaultConfig ? title : $"{title} [{config}]";
} }
private string GetItemFileName(Item item, string prefix) private string GetItemFileName(Item item)
{ {
if (string.IsNullOrWhiteSpace(item.ItemNo)) if (string.IsNullOrWhiteSpace(item.ItemNo))
return item.PartName; return item.PartName ?? "unknown";
prefix = prefix?.Replace("\"", "''") ?? string.Empty;
var num = item.ItemNo.PadLeft(2, '0'); var num = item.ItemNo.PadLeft(2, '0');
// Expected format: {DrawingNo} PT{ItemNo} return $"PT{num}";
return string.IsNullOrWhiteSpace(prefix)
? $"PT{num}"
: $"{prefix} PT{num}";
} }
private void LogExportFailure(Item item, ExportContext context) private void LogExportFailure(Item item, ExportContext context)