diff --git a/ExportDXF/Services/ExcelExportService.cs b/ExportDXF/Services/ExcelExportService.cs new file mode 100644 index 0000000..d097b49 --- /dev/null +++ b/ExportDXF/Services/ExcelExportService.cs @@ -0,0 +1,139 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ClosedXML.Excel; +using ExportDXF.Models; + +namespace ExportDXF.Services +{ + public class ExcelExportService + { + /// + /// 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. + /// Key = Item #, Value = (ContentHash, Revision, FileName) + /// + public Dictionary ReadExistingCutTemplates(string xlsxPath) + { + var result = new Dictionary(); + + if (!File.Exists(xlsxPath)) + return result; + + using (var workbook = new XLWorkbook(xlsxPath)) + { + if (!workbook.TryGetWorksheet("Cut Templates", out var ws)) + return result; + + var lastCol = ws.LastColumnUsed()?.ColumnNumber() ?? 0; + var lastRow = ws.LastRowUsed()?.RowNumber() ?? 1; + + if (lastCol == 0 || lastRow <= 1) + return result; + + var headers = new Dictionary(); + for (int col = 1; col <= lastCol; col++) + { + var header = ws.Cell(1, col).GetString(); + if (!string.IsNullOrEmpty(header)) + headers[header] = col; + } + + if (!headers.ContainsKey("Item #") || !headers.ContainsKey("Content Hash")) + return result; + + for (int row = 2; row <= lastRow; row++) + { + var itemNo = ws.Cell(row, headers["Item #"]).GetString(); + var hash = ws.Cell(row, headers["Content Hash"]).GetString(); + var revision = headers.ContainsKey("Revision") + ? ws.Cell(row, headers["Revision"]).GetValue() + : 1; + var fileName = headers.ContainsKey("File Name") + ? ws.Cell(row, headers["File Name"]).GetString() + : ""; + + if (!string.IsNullOrEmpty(itemNo)) + result[itemNo] = (hash, revision, fileName); + } + } + + return result; + } + + /// + /// Writes or updates the xlsx file with BOM and Cut Templates sheets. + /// rawBomTable: list of rows where each row is a dictionary of column name → value. + /// If null or empty, the BOM sheet is not written (Part/Assembly exports). + /// + public void Write( + string xlsxPath, + List> rawBomTable, + List bomItems) + { + using (var workbook = File.Exists(xlsxPath) + ? new XLWorkbook(xlsxPath) + : new XLWorkbook()) + { + WriteBomSheet(workbook, rawBomTable); + WriteCutTemplatesSheet(workbook, bomItems); + workbook.SaveAs(xlsxPath); + } + } + + private void WriteBomSheet(XLWorkbook workbook, List> rawBomTable) + { + if (rawBomTable == null || rawBomTable.Count == 0) + return; + + if (workbook.TryGetWorksheet("BOM", out _)) + workbook.Worksheets.Delete("BOM"); + + var sheet = workbook.Worksheets.Add("BOM"); + var columns = rawBomTable[0].Keys.ToList(); + + for (int col = 0; col < columns.Count; col++) + sheet.Cell(1, col + 1).Value = columns[col]; + + for (int row = 0; row < rawBomTable.Count; row++) + { + for (int col = 0; col < columns.Count; col++) + { + string value; + rawBomTable[row].TryGetValue(columns[col], out value); + sheet.Cell(row + 2, col + 1).Value = value ?? ""; + } + } + + sheet.Columns().AdjustToContents(); + } + + private void WriteCutTemplatesSheet(XLWorkbook workbook, List bomItems) + { + if (workbook.TryGetWorksheet("Cut Templates", out _)) + workbook.Worksheets.Delete("Cut Templates"); + + var sheet = workbook.Worksheets.Add("Cut Templates"); + + var headers = new[] { "Item #", "File Name", "Revision", "Thickness", "K-Factor", "Bend Radius", "Content Hash" }; + for (int col = 0; col < headers.Length; col++) + sheet.Cell(1, col + 1).Value = headers[col]; + + int row = 2; + foreach (var item in bomItems.Where(b => b.CutTemplate != null).OrderBy(b => b.ItemNo)) + { + var ct = item.CutTemplate; + sheet.Cell(row, 1).Value = item.ItemNo; + sheet.Cell(row, 2).Value = ct.DxfFilePath; + sheet.Cell(row, 3).Value = ct.Revision; + sheet.Cell(row, 4).Value = ct.Thickness ?? 0; + sheet.Cell(row, 5).Value = ct.KFactor ?? 0; + sheet.Cell(row, 6).Value = ct.DefaultBendRadius ?? 0; + sheet.Cell(row, 7).Value = ct.ContentHash ?? ""; + row++; + } + + sheet.Columns().AdjustToContents(); + } + } +} diff --git a/ExportDXF/Services/LogFileService.cs b/ExportDXF/Services/LogFileService.cs new file mode 100644 index 0000000..2381af7 --- /dev/null +++ b/ExportDXF/Services/LogFileService.cs @@ -0,0 +1,60 @@ +using System; +using System.IO; + +namespace ExportDXF.Services +{ + public class LogFileService : IDisposable + { + private StreamWriter _exportLog; + private static readonly string AppLogPath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "ExportDXF", "ExportDXF.log"); + + public void StartExportLog(string logFilePath) + { + _exportLog?.Dispose(); + + var dir = Path.GetDirectoryName(logFilePath); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + _exportLog = new StreamWriter(logFilePath, append: true); + } + + public void Log(string level, string message) + { + var line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] {message}"; + + _exportLog?.WriteLine(line); + _exportLog?.Flush(); + + WriteAppLog(line); + } + + public void LogInfo(string message) => Log("INFO", message); + public void LogWarning(string message) => Log("WARNING", message); + public void LogError(string message) => Log("ERROR", message); + + private void WriteAppLog(string line) + { + try + { + var dir = Path.GetDirectoryName(AppLogPath); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + File.AppendAllText(AppLogPath, line + Environment.NewLine); + } + catch + { + // Best-effort app log — don't fail exports if log write fails + } + } + + public void Dispose() + { + _exportLog?.Dispose(); + _exportLog = null; + } + } +} diff --git a/ExportDXF/Services/RawBomTableReader.cs b/ExportDXF/Services/RawBomTableReader.cs new file mode 100644 index 0000000..9c0c986 --- /dev/null +++ b/ExportDXF/Services/RawBomTableReader.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using SolidWorks.Interop.sldworks; + +namespace ExportDXF.Services +{ + /// + /// Reads all visible columns and rows from a SolidWorks BOM table annotation + /// as raw string data for direct copy into an Excel sheet. + /// + public static class RawBomTableReader + { + public static List> Read(BomTableAnnotation bomTable) + { + var table = (TableAnnotation)bomTable; + var rows = new List>(); + + int colCount = table.ColumnCount; + int rowCount = table.RowCount; + + // Build visible column headers + var columns = new List<(int Index, string Header)>(); + for (int col = 0; col < colCount; col++) + { + if (table.ColumnHidden[col]) + continue; + + var header = table.get_Text(0, col)?.Trim() ?? $"Column{col}"; + columns.Add((col, header)); + } + + // Read data rows (skip header row 0, skip hidden rows) + for (int row = 1; row < rowCount; row++) + { + if (table.RowHidden[row]) + continue; + + var rowData = new Dictionary(); + foreach (var (colIdx, header) in columns) + { + rowData[header] = table.get_Text(row, colIdx)?.Trim() ?? ""; + } + rows.Add(rowData); + } + + return rows; + } + } +}