feat: switch BOM export to a formatted xlsx template instead of generated sheets
Copy a pre-built "BOM Template.xlsx" (with headers/formatting) and write into it rather than deleting/recreating worksheets from scratch, so output keeps consistent styling. Cut Templates sheet no longer stores a File Name column; the DXF filename is now derived from the stored revision, simplifying the existing-templates map and removing redundant state. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
## Ignore Visual Studio temporary files, build results, and
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
## files generated by popular Visual Studio add-ons.
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
|
||||||
|
# Stray Windows redirect artifact
|
||||||
|
/nul
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.suo
|
*.suo
|
||||||
*.user
|
*.user
|
||||||
|
|||||||
@@ -37,6 +37,9 @@
|
|||||||
<Content Include="Templates\Blank.drwdot">
|
<Content Include="Templates\Blank.drwdot">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Templates\BOM Template.xlsx">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ namespace ExportDXF.Services
|
|||||||
ExportContext context,
|
ExportContext context,
|
||||||
string tempDir,
|
string tempDir,
|
||||||
string outputFolder,
|
string outputFolder,
|
||||||
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
Dictionary<string, (string ContentHash, int Revision)> existingTemplates,
|
||||||
List<BomItem> bomItems)
|
List<BomItem> bomItems)
|
||||||
{
|
{
|
||||||
LogProgress(context, "Active document is a Part");
|
LogProgress(context, "Active document is a Part");
|
||||||
@@ -159,7 +159,7 @@ namespace ExportDXF.Services
|
|||||||
ExportContext context,
|
ExportContext context,
|
||||||
string tempDir,
|
string tempDir,
|
||||||
string outputFolder,
|
string outputFolder,
|
||||||
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
Dictionary<string, (string ContentHash, int Revision)> existingTemplates,
|
||||||
List<BomItem> bomItems)
|
List<BomItem> bomItems)
|
||||||
{
|
{
|
||||||
LogProgress(context, "Active document is an Assembly");
|
LogProgress(context, "Active document is an Assembly");
|
||||||
@@ -200,7 +200,7 @@ namespace ExportDXF.Services
|
|||||||
ExportContext context,
|
ExportContext context,
|
||||||
string tempDir,
|
string tempDir,
|
||||||
string outputFolder,
|
string outputFolder,
|
||||||
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
Dictionary<string, (string ContentHash, int Revision)> existingTemplates,
|
||||||
List<BomItem> bomItems)
|
List<BomItem> bomItems)
|
||||||
{
|
{
|
||||||
LogProgress(context, "Active document is a Drawing");
|
LogProgress(context, "Active document is a Drawing");
|
||||||
@@ -294,7 +294,7 @@ namespace ExportDXF.Services
|
|||||||
string tempDir,
|
string tempDir,
|
||||||
string outputFolder,
|
string outputFolder,
|
||||||
ExportContext context,
|
ExportContext context,
|
||||||
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
Dictionary<string, (string ContentHash, int Revision)> existingTemplates,
|
||||||
List<BomItem> bomItems)
|
List<BomItem> bomItems)
|
||||||
{
|
{
|
||||||
int successCount = 0;
|
int successCount = 0;
|
||||||
@@ -362,7 +362,7 @@ namespace ExportDXF.Services
|
|||||||
Item item,
|
Item item,
|
||||||
ExportContext context,
|
ExportContext context,
|
||||||
string outputFolder,
|
string outputFolder,
|
||||||
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
Dictionary<string, (string ContentHash, int Revision)> existingTemplates,
|
||||||
BomItem bomItem)
|
BomItem bomItem)
|
||||||
{
|
{
|
||||||
var baseName = FilenameTemplateParser.Evaluate(context.FilenameTemplate, item);
|
var baseName = FilenameTemplateParser.Evaluate(context.FilenameTemplate, item);
|
||||||
@@ -377,15 +377,15 @@ namespace ExportDXF.Services
|
|||||||
if (existing.ContentHash == contentHash)
|
if (existing.ContentHash == contentHash)
|
||||||
{
|
{
|
||||||
// Unchanged — skip file write, keep existing
|
// Unchanged — skip file write, keep existing
|
||||||
dxfFileName = existing.FileName;
|
|
||||||
revision = existing.Revision;
|
revision = existing.Revision;
|
||||||
|
dxfFileName = GetRevisionFileName(baseName, revision);
|
||||||
|
|
||||||
LogProgress(context, $"Unchanged: {dxfFileName}", LogLevel.Info, item.PartName);
|
LogProgress(context, $"Unchanged: {dxfFileName}", LogLevel.Info, item.PartName);
|
||||||
_logFileService.LogInfo($"Unchanged: {dxfFileName}");
|
_logFileService.LogInfo($"Unchanged: {dxfFileName}");
|
||||||
|
|
||||||
bomItem.CutTemplate = new CutTemplate
|
bomItem.CutTemplate = new CutTemplate
|
||||||
{
|
{
|
||||||
DxfFilePath = dxfFileName,
|
DxfFilePath = Path.GetFileNameWithoutExtension(dxfFileName),
|
||||||
ContentHash = contentHash,
|
ContentHash = contentHash,
|
||||||
Revision = revision,
|
Revision = revision,
|
||||||
Thickness = item.Thickness > 0 ? item.Thickness : (double?)null,
|
Thickness = item.Thickness > 0 ? item.Thickness : (double?)null,
|
||||||
@@ -402,7 +402,7 @@ namespace ExportDXF.Services
|
|||||||
dxfFileName = GetRevisionFileName(baseName, revision);
|
dxfFileName = GetRevisionFileName(baseName, revision);
|
||||||
|
|
||||||
LogProgress(context, $"Updated: {dxfFileName} (Rev{revision})", LogLevel.Info, item.PartName);
|
LogProgress(context, $"Updated: {dxfFileName} (Rev{revision})", LogLevel.Info, item.PartName);
|
||||||
_logFileService.LogInfo($"Updated: {dxfFileName} (was {existing.FileName})");
|
_logFileService.LogInfo($"Updated: {dxfFileName}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
using ClosedXML.Excel;
|
using ClosedXML.Excel;
|
||||||
using ExportDXF.Models;
|
using ExportDXF.Models;
|
||||||
|
|
||||||
@@ -8,14 +10,18 @@ namespace ExportDXF.Services
|
|||||||
{
|
{
|
||||||
public class ExcelExportService
|
public class ExcelExportService
|
||||||
{
|
{
|
||||||
|
private static readonly string TemplatePath = Path.Combine(
|
||||||
|
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
|
||||||
|
"Templates",
|
||||||
|
"BOM Template.xlsx");
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reads existing Cut Templates from an xlsx file to compare content hashes.
|
/// Reads existing Cut Templates from an xlsx file to compare content hashes.
|
||||||
/// Returns empty dictionary if file doesn't exist or has no Cut Templates sheet.
|
/// Returns empty dictionary if file doesn't exist or has no Cut Templates sheet.
|
||||||
/// Key = Item #, Value = (ContentHash, Revision, FileName)
|
/// Key = Item #, Value = (ContentHash, Revision)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Dictionary<string, (string ContentHash, int Revision, string FileName)> ReadExistingCutTemplates(string xlsxPath)
|
public Dictionary<string, (string ContentHash, int Revision)> ReadExistingCutTemplates(string xlsxPath)
|
||||||
{
|
{
|
||||||
var result = new Dictionary<string, (string, int, string)>();
|
var result = new Dictionary<string, (string, int)>();
|
||||||
|
|
||||||
if (!File.Exists(xlsxPath))
|
if (!File.Exists(xlsxPath))
|
||||||
return result;
|
return result;
|
||||||
@@ -49,12 +55,9 @@ namespace ExportDXF.Services
|
|||||||
var revision = headers.ContainsKey("Revision")
|
var revision = headers.ContainsKey("Revision")
|
||||||
? ws.Cell(row, headers["Revision"]).GetValue<int>()
|
? ws.Cell(row, headers["Revision"]).GetValue<int>()
|
||||||
: 1;
|
: 1;
|
||||||
var fileName = headers.ContainsKey("File Name")
|
|
||||||
? ws.Cell(row, headers["File Name"]).GetString()
|
|
||||||
: "";
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(itemNo))
|
if (!string.IsNullOrEmpty(itemNo))
|
||||||
result[itemNo] = (hash, revision, fileName);
|
result[itemNo] = (hash, revision);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,9 +74,12 @@ namespace ExportDXF.Services
|
|||||||
List<Dictionary<string, string>> rawBomTable,
|
List<Dictionary<string, string>> rawBomTable,
|
||||||
List<BomItem> bomItems)
|
List<BomItem> bomItems)
|
||||||
{
|
{
|
||||||
using (var workbook = File.Exists(xlsxPath)
|
if (!File.Exists(TemplatePath))
|
||||||
? new XLWorkbook(xlsxPath)
|
throw new FileNotFoundException("Excel template not found.", TemplatePath);
|
||||||
: new XLWorkbook())
|
|
||||||
|
File.Copy(TemplatePath, xlsxPath, overwrite: true);
|
||||||
|
|
||||||
|
using (var workbook = new XLWorkbook(xlsxPath))
|
||||||
{
|
{
|
||||||
WriteBomSheet(workbook, rawBomTable);
|
WriteBomSheet(workbook, rawBomTable);
|
||||||
WriteCutTemplatesSheet(workbook, bomItems);
|
WriteCutTemplatesSheet(workbook, bomItems);
|
||||||
@@ -86,10 +92,9 @@ namespace ExportDXF.Services
|
|||||||
if (rawBomTable == null || rawBomTable.Count == 0)
|
if (rawBomTable == null || rawBomTable.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (workbook.TryGetWorksheet("BOM", out _))
|
var sheet = GetOrCreateSheet(workbook, "BOM");
|
||||||
workbook.Worksheets.Delete("BOM");
|
sheet.Clear();
|
||||||
|
|
||||||
var sheet = workbook.Worksheets.Add("BOM");
|
|
||||||
var columns = rawBomTable[0].Keys.ToList();
|
var columns = rawBomTable[0].Keys.ToList();
|
||||||
|
|
||||||
for (int col = 0; col < columns.Count; col++)
|
for (int col = 0; col < columns.Count; col++)
|
||||||
@@ -110,30 +115,34 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
private void WriteCutTemplatesSheet(XLWorkbook workbook, List<BomItem> bomItems)
|
private void WriteCutTemplatesSheet(XLWorkbook workbook, List<BomItem> bomItems)
|
||||||
{
|
{
|
||||||
if (workbook.TryGetWorksheet("Cut Templates", out _))
|
var sheet = GetOrCreateSheet(workbook, "Cut Templates");
|
||||||
workbook.Worksheets.Delete("Cut Templates");
|
|
||||||
|
|
||||||
var sheet = workbook.Worksheets.Add("Cut Templates");
|
// Clear data rows only (preserve template headers/formatting in row 1)
|
||||||
|
var lastRow = sheet.LastRowUsed()?.RowNumber() ?? 1;
|
||||||
var headers = new[] { "Item #", "File Name", "Revision", "Thickness", "K-Factor", "Bend Radius", "Content Hash" };
|
if (lastRow > 1)
|
||||||
for (int col = 0; col < headers.Length; col++)
|
sheet.Range(2, 1, lastRow, sheet.LastColumnUsed()?.ColumnNumber() ?? 1).Clear();
|
||||||
sheet.Cell(1, col + 1).Value = headers[col];
|
|
||||||
|
|
||||||
int row = 2;
|
int row = 2;
|
||||||
foreach (var item in bomItems.Where(b => b.CutTemplate != null).OrderBy(b => b.ItemNo))
|
foreach (var item in bomItems.Where(b => b.CutTemplate != null).OrderBy(b => b.ItemNo))
|
||||||
{
|
{
|
||||||
var ct = item.CutTemplate;
|
var ct = item.CutTemplate;
|
||||||
sheet.Cell(row, 1).Value = item.ItemNo;
|
sheet.Cell(row, 1).Value = item.ItemNo;
|
||||||
sheet.Cell(row, 2).Value = ct.DxfFilePath;
|
sheet.Cell(row, 2).Value = ct.Revision;
|
||||||
sheet.Cell(row, 3).Value = ct.Revision;
|
sheet.Cell(row, 3).Value = ct.Thickness ?? 0;
|
||||||
sheet.Cell(row, 4).Value = ct.Thickness ?? 0;
|
sheet.Cell(row, 4).Value = ct.KFactor ?? 0;
|
||||||
sheet.Cell(row, 5).Value = ct.KFactor ?? 0;
|
sheet.Cell(row, 5).Value = ct.DefaultBendRadius ?? 0;
|
||||||
sheet.Cell(row, 6).Value = ct.DefaultBendRadius ?? 0;
|
sheet.Cell(row, 6).Value = ct.ContentHash ?? "";
|
||||||
sheet.Cell(row, 7).Value = ct.ContentHash ?? "";
|
|
||||||
row++;
|
row++;
|
||||||
}
|
}
|
||||||
|
|
||||||
sheet.Columns().AdjustToContents();
|
sheet.Columns().AdjustToContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IXLWorksheet GetOrCreateSheet(XLWorkbook workbook, string name)
|
||||||
|
{
|
||||||
|
if (workbook.TryGetWorksheet(name, out var sheet))
|
||||||
|
return sheet;
|
||||||
|
return workbook.Worksheets.Add(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user