Compare commits
55 Commits
4eb13a1aca
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 14fa1e6906 | |||
| 41022a93cc | |||
| 0fd117da92 | |||
| 5f28a6ce2b | |||
| a4db71f074 | |||
| 4d01b2654d | |||
| 1d3b6b8f0f | |||
| 9bc29e98c8 | |||
| c6dde6e217 | |||
| cf17e71b80 | |||
| 742d86ab8a | |||
| ba782b99db | |||
| 9a33d405e2 | |||
| e0d4563cc6 | |||
| a4f6dffe12 | |||
| b7d35bbe78 | |||
| 5e5c6ab72f | |||
| 036ab2a55a | |||
| f9e7ace35d | |||
| 622cbf1170 | |||
| 4a3f33db33 | |||
| 77d0157370 | |||
| 26e9233b30 | |||
| e59584a5c0 | |||
| dcc508d479 | |||
| 1266378b51 | |||
| 5de40ebafd | |||
| e072919a59 | |||
| 7db44640ca | |||
| 0d5742124e | |||
| 463916c75c | |||
| c06d834e05 | |||
| d3c154b875 | |||
| 2721c33a39 | |||
| 5ec66f9039 | |||
| cf76ca8bb1 | |||
| 696bf2f72c | |||
| 8de441e126 | |||
| 8b6950ef28 | |||
| dba68ecc71 | |||
| f75b83d483 | |||
| 2273a83e42 | |||
| e10a7ed0ed | |||
| 16dc74c35d | |||
| 9e5e44c1ed | |||
| ab76fa61c9 | |||
| 28c9f715be | |||
| 2bef75f548 | |||
| 78a8a2197d | |||
| 719dca1ca5 | |||
| a17d8cac49 | |||
| 32e8379e9b | |||
| 0ace378eff | |||
| 697463f61e | |||
| f418573908 |
@@ -245,3 +245,6 @@ ModelManifest.xml
|
|||||||
|
|
||||||
# Test documents
|
# Test documents
|
||||||
TestDocs/
|
TestDocs/
|
||||||
|
|
||||||
|
# Superpowers specs and plans
|
||||||
|
docs/superpowers/
|
||||||
|
|||||||
+1
-1
Submodule EtchBendLines updated: 89d987f6c6...da4d3228b0
+20
-6
@@ -7,26 +7,40 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExportDXF", "ExportDXF\Expo
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EtchBendLines", "EtchBendLines\EtchBendLines\EtchBendLines.csproj", "{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EtchBendLines", "EtchBendLines\EtchBendLines\EtchBendLines.csproj", "{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "netDxf", "EtchBendLines\netDxf\netDxf\netDxf.csproj", "{785380E0-CEB9-4C34-82E5-60D0E33E848E}"
|
|
||||||
EndProject
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.Build.0 = Release|Any CPU
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
using ExportDXF.Models;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using System.Configuration;
|
|
||||||
|
|
||||||
namespace ExportDXF.Data
|
|
||||||
{
|
|
||||||
public class ExportDxfDbContext : DbContext
|
|
||||||
{
|
|
||||||
public DbSet<ExportRecord> ExportRecords { get; set; }
|
|
||||||
public DbSet<BomItem> BomItems { get; set; }
|
|
||||||
|
|
||||||
public ExportDxfDbContext() : base()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExportDxfDbContext(DbContextOptions<ExportDxfDbContext> options) : base(options)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
||||||
{
|
|
||||||
if (!optionsBuilder.IsConfigured)
|
|
||||||
{
|
|
||||||
var connectionString = ConfigurationManager.ConnectionStrings["ExportDxfDb"]?.ConnectionString
|
|
||||||
?? "Server=localhost;Database=ExportDxfDb;Trusted_Connection=True;TrustServerCertificate=True;";
|
|
||||||
optionsBuilder.UseSqlServer(connectionString);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
base.OnModelCreating(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity<ExportRecord>(entity =>
|
|
||||||
{
|
|
||||||
entity.HasKey(e => e.Id);
|
|
||||||
entity.Property(e => e.DrawingNumber).HasMaxLength(100);
|
|
||||||
entity.Property(e => e.SourceFilePath).HasMaxLength(500);
|
|
||||||
entity.Property(e => e.OutputFolder).HasMaxLength(500);
|
|
||||||
entity.Property(e => e.ExportedBy).HasMaxLength(100);
|
|
||||||
|
|
||||||
entity.HasMany(e => e.BomItems)
|
|
||||||
.WithOne(b => b.ExportRecord)
|
|
||||||
.HasForeignKey(b => b.ExportRecordId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity<BomItem>(entity =>
|
|
||||||
{
|
|
||||||
entity.HasKey(e => e.ID);
|
|
||||||
entity.Property(e => e.ItemNo).HasMaxLength(50);
|
|
||||||
entity.Property(e => e.PartNo).HasMaxLength(100);
|
|
||||||
entity.Property(e => e.Description).HasMaxLength(500);
|
|
||||||
entity.Property(e => e.PartName).HasMaxLength(200);
|
|
||||||
entity.Property(e => e.ConfigurationName).HasMaxLength(100);
|
|
||||||
entity.Property(e => e.Material).HasMaxLength(100);
|
|
||||||
entity.Property(e => e.CutTemplateName).HasMaxLength(100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace ExportDXF
|
namespace ExportDXF
|
||||||
{
|
{
|
||||||
public class DrawingInfo
|
public class DrawingInfo
|
||||||
{
|
{
|
||||||
private static Regex drawingFormatRegex = new Regex(@"(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\s?(?<dwgNo>[ABEP]\d+(-?(\d+[A-Z]?))?)", RegexOptions.IgnoreCase);
|
private static Regex drawingFormatRegex = new Regex(@"(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\s?(?<dwgNo>[ABEP]\d+(-?(\d+[A-Z]?))?)", RegexOptions.IgnoreCase);
|
||||||
|
private static Regex equipmentOnlyRegex = new Regex(@"^(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\b", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
public string EquipmentNo { get; set; }
|
public string EquipmentNo { get; set; }
|
||||||
|
|
||||||
@@ -14,6 +15,8 @@ namespace ExportDXF
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(DrawingNo))
|
||||||
|
return EquipmentNo ?? string.Empty;
|
||||||
return $"{EquipmentNo} {DrawingNo}";
|
return $"{EquipmentNo} {DrawingNo}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +38,21 @@ namespace ExportDXF
|
|||||||
var match = drawingFormatRegex.Match(input);
|
var match = drawingFormatRegex.Match(input);
|
||||||
|
|
||||||
if (match.Success == false)
|
if (match.Success == false)
|
||||||
|
{
|
||||||
|
// Try matching just the equipment number (e.g. "5028 Prox switch bracket")
|
||||||
|
var eqMatch = equipmentOnlyRegex.Match(input);
|
||||||
|
if (eqMatch.Success)
|
||||||
|
{
|
||||||
|
return new DrawingInfo
|
||||||
|
{
|
||||||
|
EquipmentNo = eqMatch.Groups["equipmentNo"].Value,
|
||||||
|
DrawingNo = null,
|
||||||
|
Source = input
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var dwg = new DrawingInfo();
|
var dwg = new DrawingInfo();
|
||||||
|
|
||||||
|
|||||||
@@ -14,11 +14,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
<PackageReference Include="ClosedXML" Version="0.104.2" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Generated
+60
-54
@@ -36,29 +36,47 @@ namespace ExportDXF.Forms
|
|||||||
logEventsDataGrid = new System.Windows.Forms.DataGridView();
|
logEventsDataGrid = new System.Windows.Forms.DataGridView();
|
||||||
bomTab = new System.Windows.Forms.TabPage();
|
bomTab = new System.Windows.Forms.TabPage();
|
||||||
bomDataGrid = new System.Windows.Forms.DataGridView();
|
bomDataGrid = new System.Windows.Forms.DataGridView();
|
||||||
equipmentBox = new System.Windows.Forms.ComboBox();
|
cutTemplatesTab = new System.Windows.Forms.TabPage();
|
||||||
label1 = new System.Windows.Forms.Label();
|
cutTemplatesDataGrid = new System.Windows.Forms.DataGridView();
|
||||||
label2 = new System.Windows.Forms.Label();
|
templateLabel = new System.Windows.Forms.Label();
|
||||||
drawingNoBox = new System.Windows.Forms.ComboBox();
|
txtFilenameTemplate = new System.Windows.Forms.TextBox();
|
||||||
mainTabControl.SuspendLayout();
|
mainTabControl.SuspendLayout();
|
||||||
logEventsTab.SuspendLayout();
|
logEventsTab.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).BeginInit();
|
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).BeginInit();
|
||||||
bomTab.SuspendLayout();
|
bomTab.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)bomDataGrid).BeginInit();
|
((System.ComponentModel.ISupportInitialize)bomDataGrid).BeginInit();
|
||||||
|
cutTemplatesTab.SuspendLayout();
|
||||||
|
((System.ComponentModel.ISupportInitialize)cutTemplatesDataGrid).BeginInit();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// 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(656, 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, 30);
|
runButton.Size = new System.Drawing.Size(65, 57);
|
||||||
runButton.TabIndex = 11;
|
runButton.TabIndex = 11;
|
||||||
runButton.Text = "Start";
|
runButton.Text = "Start";
|
||||||
runButton.UseVisualStyleBackColor = true;
|
runButton.UseVisualStyleBackColor = true;
|
||||||
runButton.Click += button1_Click;
|
runButton.Click += button1_Click;
|
||||||
//
|
//
|
||||||
|
// templateLabel
|
||||||
|
//
|
||||||
|
templateLabel.AutoSize = true;
|
||||||
|
templateLabel.Location = new System.Drawing.Point(15, 15);
|
||||||
|
templateLabel.Name = "templateLabel";
|
||||||
|
templateLabel.Size = new System.Drawing.Size(116, 17);
|
||||||
|
templateLabel.TabIndex = 2;
|
||||||
|
templateLabel.Text = "Filename Template";
|
||||||
|
//
|
||||||
|
// txtFilenameTemplate
|
||||||
|
//
|
||||||
|
txtFilenameTemplate.Location = new System.Drawing.Point(137, 12);
|
||||||
|
txtFilenameTemplate.Name = "txtFilenameTemplate";
|
||||||
|
txtFilenameTemplate.Size = new System.Drawing.Size(365, 25);
|
||||||
|
txtFilenameTemplate.TabIndex = 1;
|
||||||
|
//
|
||||||
// label3
|
// label3
|
||||||
//
|
//
|
||||||
label3.AutoSize = true;
|
label3.AutoSize = true;
|
||||||
@@ -74,7 +92,7 @@ namespace ExportDXF.Forms
|
|||||||
viewFlipDeciderBox.FormattingEnabled = true;
|
viewFlipDeciderBox.FormattingEnabled = true;
|
||||||
viewFlipDeciderBox.Location = new System.Drawing.Point(137, 43);
|
viewFlipDeciderBox.Location = new System.Drawing.Point(137, 43);
|
||||||
viewFlipDeciderBox.Name = "viewFlipDeciderBox";
|
viewFlipDeciderBox.Name = "viewFlipDeciderBox";
|
||||||
viewFlipDeciderBox.Size = new System.Drawing.Size(502, 25);
|
viewFlipDeciderBox.Size = new System.Drawing.Size(365, 25);
|
||||||
viewFlipDeciderBox.TabIndex = 3;
|
viewFlipDeciderBox.TabIndex = 3;
|
||||||
//
|
//
|
||||||
// mainTabControl
|
// mainTabControl
|
||||||
@@ -82,11 +100,12 @@ namespace ExportDXF.Forms
|
|||||||
mainTabControl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
mainTabControl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||||
mainTabControl.Controls.Add(logEventsTab);
|
mainTabControl.Controls.Add(logEventsTab);
|
||||||
mainTabControl.Controls.Add(bomTab);
|
mainTabControl.Controls.Add(bomTab);
|
||||||
mainTabControl.Location = new System.Drawing.Point(15, 74);
|
mainTabControl.Controls.Add(cutTemplatesTab);
|
||||||
|
mainTabControl.Location = new System.Drawing.Point(15, 75);
|
||||||
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(741, 586);
|
mainTabControl.Size = new System.Drawing.Size(910, 522);
|
||||||
mainTabControl.TabIndex = 12;
|
mainTabControl.TabIndex = 12;
|
||||||
//
|
//
|
||||||
// logEventsTab
|
// logEventsTab
|
||||||
@@ -95,7 +114,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(733, 552);
|
logEventsTab.Size = new System.Drawing.Size(902, 488);
|
||||||
logEventsTab.TabIndex = 0;
|
logEventsTab.TabIndex = 0;
|
||||||
logEventsTab.Text = "Log Events";
|
logEventsTab.Text = "Log Events";
|
||||||
logEventsTab.UseVisualStyleBackColor = true;
|
logEventsTab.UseVisualStyleBackColor = true;
|
||||||
@@ -107,16 +126,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(721, 540);
|
logEventsDataGrid.Size = new System.Drawing.Size(890, 476);
|
||||||
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(733, 552);
|
bomTab.Size = new System.Drawing.Size(902, 490);
|
||||||
bomTab.TabIndex = 1;
|
bomTab.TabIndex = 1;
|
||||||
bomTab.Text = "Bill Of Materials";
|
bomTab.Text = "Bill Of Materials";
|
||||||
bomTab.UseVisualStyleBackColor = true;
|
bomTab.UseVisualStyleBackColor = true;
|
||||||
@@ -128,59 +147,44 @@ namespace ExportDXF.Forms
|
|||||||
bomDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
bomDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||||
bomDataGrid.Location = new System.Drawing.Point(6, 6);
|
bomDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||||
bomDataGrid.Name = "bomDataGrid";
|
bomDataGrid.Name = "bomDataGrid";
|
||||||
bomDataGrid.Size = new System.Drawing.Size(721, 540);
|
bomDataGrid.Size = new System.Drawing.Size(890, 478);
|
||||||
bomDataGrid.TabIndex = 1;
|
bomDataGrid.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// equipmentBox
|
// cutTemplatesTab
|
||||||
//
|
//
|
||||||
equipmentBox.FormattingEnabled = true;
|
cutTemplatesTab.Controls.Add(cutTemplatesDataGrid);
|
||||||
equipmentBox.Location = new System.Drawing.Point(137, 12);
|
cutTemplatesTab.Location = new System.Drawing.Point(4, 28);
|
||||||
equipmentBox.Name = "equipmentBox";
|
cutTemplatesTab.Name = "cutTemplatesTab";
|
||||||
equipmentBox.Size = new System.Drawing.Size(166, 25);
|
cutTemplatesTab.Padding = new System.Windows.Forms.Padding(3);
|
||||||
equipmentBox.TabIndex = 13;
|
cutTemplatesTab.Size = new System.Drawing.Size(902, 490);
|
||||||
|
cutTemplatesTab.TabIndex = 2;
|
||||||
|
cutTemplatesTab.Text = "Cut Templates";
|
||||||
|
cutTemplatesTab.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
// label1
|
// cutTemplatesDataGrid
|
||||||
//
|
//
|
||||||
label1.AutoSize = true;
|
cutTemplatesDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||||
label1.Location = new System.Drawing.Point(61, 15);
|
cutTemplatesDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||||
label1.Name = "label1";
|
cutTemplatesDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||||
label1.Size = new System.Drawing.Size(70, 17);
|
cutTemplatesDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||||
label1.TabIndex = 2;
|
cutTemplatesDataGrid.Name = "cutTemplatesDataGrid";
|
||||||
label1.Text = "Equipment";
|
cutTemplatesDataGrid.Size = new System.Drawing.Size(890, 478);
|
||||||
//
|
cutTemplatesDataGrid.TabIndex = 2;
|
||||||
// label2
|
|
||||||
//
|
|
||||||
label2.AutoSize = true;
|
|
||||||
label2.Location = new System.Drawing.Point(354, 15);
|
|
||||||
label2.Name = "label2";
|
|
||||||
label2.Size = new System.Drawing.Size(56, 17);
|
|
||||||
label2.TabIndex = 2;
|
|
||||||
label2.Text = "Drawing";
|
|
||||||
//
|
|
||||||
// drawingNoBox
|
|
||||||
//
|
|
||||||
drawingNoBox.FormattingEnabled = true;
|
|
||||||
drawingNoBox.Location = new System.Drawing.Point(416, 12);
|
|
||||||
drawingNoBox.Name = "drawingNoBox";
|
|
||||||
drawingNoBox.Size = new System.Drawing.Size(223, 25);
|
|
||||||
drawingNoBox.TabIndex = 13;
|
|
||||||
//
|
//
|
||||||
// MainForm
|
// MainForm
|
||||||
//
|
//
|
||||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||||
ClientSize = new System.Drawing.Size(768, 672);
|
ClientSize = new System.Drawing.Size(937, 609);
|
||||||
Controls.Add(drawingNoBox);
|
Controls.Add(txtFilenameTemplate);
|
||||||
Controls.Add(equipmentBox);
|
|
||||||
Controls.Add(mainTabControl);
|
Controls.Add(mainTabControl);
|
||||||
Controls.Add(viewFlipDeciderBox);
|
Controls.Add(viewFlipDeciderBox);
|
||||||
Controls.Add(label2);
|
Controls.Add(templateLabel);
|
||||||
Controls.Add(label1);
|
|
||||||
Controls.Add(label3);
|
Controls.Add(label3);
|
||||||
Controls.Add(runButton);
|
Controls.Add(runButton);
|
||||||
Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
||||||
Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||||
MaximizeBox = false;
|
MaximizeBox = false;
|
||||||
MinimumSize = new System.Drawing.Size(643, 355);
|
MinimumSize = new System.Drawing.Size(642, 455);
|
||||||
Name = "MainForm";
|
Name = "MainForm";
|
||||||
StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||||
Text = "ExportDXF";
|
Text = "ExportDXF";
|
||||||
@@ -189,6 +193,8 @@ namespace ExportDXF.Forms
|
|||||||
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).EndInit();
|
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).EndInit();
|
||||||
bomTab.ResumeLayout(false);
|
bomTab.ResumeLayout(false);
|
||||||
((System.ComponentModel.ISupportInitialize)bomDataGrid).EndInit();
|
((System.ComponentModel.ISupportInitialize)bomDataGrid).EndInit();
|
||||||
|
cutTemplatesTab.ResumeLayout(false);
|
||||||
|
((System.ComponentModel.ISupportInitialize)cutTemplatesDataGrid).EndInit();
|
||||||
ResumeLayout(false);
|
ResumeLayout(false);
|
||||||
PerformLayout();
|
PerformLayout();
|
||||||
}
|
}
|
||||||
@@ -203,9 +209,9 @@ namespace ExportDXF.Forms
|
|||||||
private System.Windows.Forms.TabPage bomTab;
|
private System.Windows.Forms.TabPage bomTab;
|
||||||
private System.Windows.Forms.DataGridView logEventsDataGrid;
|
private System.Windows.Forms.DataGridView logEventsDataGrid;
|
||||||
private System.Windows.Forms.DataGridView bomDataGrid;
|
private System.Windows.Forms.DataGridView bomDataGrid;
|
||||||
private System.Windows.Forms.ComboBox equipmentBox;
|
private System.Windows.Forms.TabPage cutTemplatesTab;
|
||||||
private System.Windows.Forms.Label label1;
|
private System.Windows.Forms.DataGridView cutTemplatesDataGrid;
|
||||||
private System.Windows.Forms.Label label2;
|
private System.Windows.Forms.Label templateLabel;
|
||||||
private System.Windows.Forms.ComboBox drawingNoBox;
|
private System.Windows.Forms.TextBox txtFilenameTemplate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+93
-96
@@ -1,12 +1,11 @@
|
|||||||
using ExportDXF.Data;
|
|
||||||
using ExportDXF.Extensions;
|
using ExportDXF.Extensions;
|
||||||
using ExportDXF.Models;
|
using ExportDXF.Models;
|
||||||
using ExportDXF.Services;
|
using ExportDXF.Services;
|
||||||
using ExportDXF.ViewFlipDeciders;
|
using ExportDXF.ViewFlipDeciders;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -18,14 +17,13 @@ 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 IDrawingInfoExtractor[] _extractors;
|
||||||
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 List<DrawingInfo> _allDrawings;
|
private readonly BindingList<CutTemplate> _cutTemplates;
|
||||||
|
|
||||||
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IFileExportService fileExportService, Func<ExportDxfDbContext> dbContextFactory = null)
|
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IDrawingInfoExtractor[] extractors)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_solidWorksService = solidWorksService ??
|
_solidWorksService = solidWorksService ??
|
||||||
@@ -33,16 +31,15 @@ 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 ??
|
_extractors = extractors ??
|
||||||
throw new ArgumentNullException(nameof(fileExportService));
|
throw new ArgumentNullException(nameof(extractors));
|
||||||
_dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext());
|
|
||||||
_logEvents = new BindingList<LogEvent>();
|
_logEvents = new BindingList<LogEvent>();
|
||||||
_bomItems = new BindingList<BomItem>();
|
_bomItems = new BindingList<BomItem>();
|
||||||
_allDrawings = new List<DrawingInfo>();
|
_cutTemplates = new BindingList<CutTemplate>();
|
||||||
InitializeViewFlipDeciders();
|
InitializeViewFlipDeciders();
|
||||||
InitializeLogEventsGrid();
|
InitializeLogEventsGrid();
|
||||||
InitializeBomGrid();
|
InitializeBomGrid();
|
||||||
InitializeDrawingDropdowns();
|
InitializeCutTemplatesGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
~MainForm()
|
~MainForm()
|
||||||
@@ -67,7 +64,6 @@ 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("Ready");
|
LogMessage("Ready");
|
||||||
UpdateActiveDocumentDisplay();
|
UpdateActiveDocumentDisplay();
|
||||||
runButton.Enabled = true;
|
runButton.Enabled = true;
|
||||||
@@ -89,7 +85,7 @@ namespace ExportDXF.Forms
|
|||||||
ViewFlipDecider = d
|
ViewFlipDecider = d
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
// Move "Automatic" to the top if it exists
|
|
||||||
var automatic = items.FirstOrDefault(i => i.Name == "Automatic");
|
var automatic = items.FirstOrDefault(i => i.Name == "Automatic");
|
||||||
if (automatic != null)
|
if (automatic != null)
|
||||||
{
|
{
|
||||||
@@ -102,17 +98,13 @@ namespace ExportDXF.Forms
|
|||||||
|
|
||||||
private void InitializeLogEventsGrid()
|
private void InitializeLogEventsGrid()
|
||||||
{
|
{
|
||||||
// Clear any existing columns first
|
|
||||||
logEventsDataGrid.Columns.Clear();
|
logEventsDataGrid.Columns.Clear();
|
||||||
|
|
||||||
// Configure grid settings
|
|
||||||
logEventsDataGrid.AutoGenerateColumns = false;
|
logEventsDataGrid.AutoGenerateColumns = false;
|
||||||
logEventsDataGrid.AllowUserToAddRows = false;
|
logEventsDataGrid.AllowUserToAddRows = false;
|
||||||
logEventsDataGrid.AllowUserToDeleteRows = false;
|
logEventsDataGrid.AllowUserToDeleteRows = false;
|
||||||
logEventsDataGrid.ReadOnly = true;
|
logEventsDataGrid.ReadOnly = true;
|
||||||
logEventsDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
logEventsDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||||
|
|
||||||
// Add columns
|
|
||||||
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
{
|
{
|
||||||
DataPropertyName = nameof(LogEvent.Time),
|
DataPropertyName = nameof(LogEvent.Time),
|
||||||
@@ -142,26 +134,19 @@ namespace ExportDXF.Forms
|
|||||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
|
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add row coloring based on log level
|
|
||||||
logEventsDataGrid.CellFormatting += LogEventsDataGrid_CellFormatting;
|
logEventsDataGrid.CellFormatting += LogEventsDataGrid_CellFormatting;
|
||||||
|
|
||||||
// Set the data source AFTER adding columns
|
|
||||||
logEventsDataGrid.DataSource = _logEvents;
|
logEventsDataGrid.DataSource = _logEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeBomGrid()
|
private void InitializeBomGrid()
|
||||||
{
|
{
|
||||||
// Clear any existing columns first
|
|
||||||
bomDataGrid.Columns.Clear();
|
bomDataGrid.Columns.Clear();
|
||||||
|
|
||||||
// Configure grid settings
|
|
||||||
bomDataGrid.AutoGenerateColumns = false;
|
bomDataGrid.AutoGenerateColumns = false;
|
||||||
bomDataGrid.AllowUserToAddRows = false;
|
bomDataGrid.AllowUserToAddRows = false;
|
||||||
bomDataGrid.AllowUserToDeleteRows = false;
|
bomDataGrid.AllowUserToDeleteRows = false;
|
||||||
bomDataGrid.ReadOnly = true;
|
bomDataGrid.ReadOnly = true;
|
||||||
bomDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
bomDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||||
|
|
||||||
// Add columns
|
|
||||||
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
{
|
{
|
||||||
DataPropertyName = nameof(BomItem.ItemNo),
|
DataPropertyName = nameof(BomItem.ItemNo),
|
||||||
@@ -211,85 +196,68 @@ namespace ExportDXF.Forms
|
|||||||
Width = 120
|
Width = 120
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set the data source AFTER adding columns
|
|
||||||
bomDataGrid.DataSource = _bomItems;
|
bomDataGrid.DataSource = _bomItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeDrawingDropdowns()
|
private void InitializeCutTemplatesGrid()
|
||||||
{
|
{
|
||||||
try
|
cutTemplatesDataGrid.Columns.Clear();
|
||||||
|
cutTemplatesDataGrid.AutoGenerateColumns = false;
|
||||||
|
cutTemplatesDataGrid.AllowUserToAddRows = false;
|
||||||
|
cutTemplatesDataGrid.AllowUserToDeleteRows = false;
|
||||||
|
cutTemplatesDataGrid.ReadOnly = true;
|
||||||
|
cutTemplatesDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||||
|
|
||||||
|
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
{
|
{
|
||||||
using (var db = _dbContextFactory())
|
DataPropertyName = nameof(CutTemplate.CutTemplateName),
|
||||||
|
HeaderText = "Template Name",
|
||||||
|
Width = 150
|
||||||
|
});
|
||||||
|
|
||||||
|
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
{
|
{
|
||||||
// Get all drawing numbers from the database
|
DataPropertyName = nameof(CutTemplate.DxfFilePath),
|
||||||
var drawingNumbers = db.ExportRecords
|
HeaderText = "DXF File",
|
||||||
.Select(r => r.DrawingNumber)
|
Width = 250
|
||||||
.Where(d => !string.IsNullOrEmpty(d))
|
});
|
||||||
.Distinct()
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Parse into DrawingInfo objects
|
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
_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);
|
DataPropertyName = nameof(CutTemplate.Revision),
|
||||||
}
|
HeaderText = "Rev",
|
||||||
|
Width = 50
|
||||||
|
});
|
||||||
|
|
||||||
// Populate drawing dropdown with all drawings initially
|
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
UpdateDrawingDropdown();
|
|
||||||
|
|
||||||
// Wire up event handler for equipment selection change
|
|
||||||
equipmentBox.SelectedIndexChanged += EquipmentBox_SelectedIndexChanged;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
// Database might not exist yet - that's OK
|
DataPropertyName = nameof(CutTemplate.Thickness),
|
||||||
System.Diagnostics.Debug.WriteLine($"Failed to load drawings from database: {ex.Message}");
|
HeaderText = "Thickness",
|
||||||
}
|
Width = 80
|
||||||
}
|
});
|
||||||
|
|
||||||
private void EquipmentBox_SelectedIndexChanged(object sender, EventArgs e)
|
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
{
|
{
|
||||||
UpdateDrawingDropdown();
|
DataPropertyName = nameof(CutTemplate.KFactor),
|
||||||
}
|
HeaderText = "K-Factor",
|
||||||
|
Width = 80
|
||||||
|
});
|
||||||
|
|
||||||
private void UpdateDrawingDropdown()
|
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
{
|
{
|
||||||
var selectedEquipment = equipmentBox.SelectedItem?.ToString();
|
DataPropertyName = nameof(CutTemplate.DefaultBendRadius),
|
||||||
|
HeaderText = "Bend Radius",
|
||||||
|
Width = 90
|
||||||
|
});
|
||||||
|
|
||||||
var filteredDrawings = string.IsNullOrEmpty(selectedEquipment)
|
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
? _allDrawings
|
|
||||||
: _allDrawings.Where(d => d.EquipmentNo == selectedEquipment).ToList();
|
|
||||||
|
|
||||||
drawingNoBox.Items.Clear();
|
|
||||||
drawingNoBox.Items.Add(""); // Empty option
|
|
||||||
foreach (var drawing in filteredDrawings)
|
|
||||||
{
|
{
|
||||||
drawingNoBox.Items.Add(drawing.DrawingNo);
|
DataPropertyName = nameof(CutTemplate.ContentHash),
|
||||||
}
|
HeaderText = "Content Hash",
|
||||||
|
Width = 150
|
||||||
|
});
|
||||||
|
|
||||||
if (drawingNoBox.Items.Count > 0)
|
cutTemplatesDataGrid.DataSource = _cutTemplates;
|
||||||
{
|
|
||||||
drawingNoBox.SelectedIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void button1_Click(object sender, EventArgs e)
|
private async void button1_Click(object sender, EventArgs e)
|
||||||
@@ -308,6 +276,13 @@ namespace ExportDXF.Forms
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var template = txtFilenameTemplate.Text.Trim();
|
||||||
|
if (!FilenameTemplateParser.Validate(template, out var error))
|
||||||
|
{
|
||||||
|
MessageBox.Show(error, "Invalid Template", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
var token = _cancellationTokenSource.Token;
|
var token = _cancellationTokenSource.Token;
|
||||||
UpdateUIForExportStart();
|
UpdateUIForExportStart();
|
||||||
@@ -319,29 +294,31 @@ namespace ExportDXF.Forms
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse drawing number from active document title
|
var sourceDir = Path.GetDirectoryName(activeDoc.FilePath);
|
||||||
var drawingInfo = DrawingInfo.Parse(activeDoc.Title);
|
var outputFolder = Path.Combine(sourceDir, "Templates");
|
||||||
var filePrefix = drawingInfo != null ? $"{drawingInfo.EquipmentNo} {drawingInfo.DrawingNo}" : activeDoc.Title;
|
|
||||||
|
|
||||||
var viewFlipDecider = GetSelectedViewFlipDecider();
|
var viewFlipDecider = GetSelectedViewFlipDecider();
|
||||||
|
|
||||||
var exportContext = new ExportContext
|
var exportContext = new ExportContext
|
||||||
{
|
{
|
||||||
ActiveDocument = activeDoc,
|
ActiveDocument = activeDoc,
|
||||||
ViewFlipDecider = viewFlipDecider,
|
ViewFlipDecider = viewFlipDecider,
|
||||||
FilePrefix = filePrefix,
|
FilenameTemplate = template,
|
||||||
EquipmentId = null,
|
OutputFolder = outputFolder,
|
||||||
CancellationToken = token,
|
CancellationToken = token,
|
||||||
ProgressCallback = (msg, level, file) => LogMessage(msg, level, file),
|
ProgressCallback = (msg, level, file) => LogMessage(msg, level, file),
|
||||||
BomItemCallback = AddBomItem
|
BomItemCallback = AddBomItem
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clear previous BOM items
|
|
||||||
_bomItems.Clear();
|
_bomItems.Clear();
|
||||||
|
_cutTemplates.Clear();
|
||||||
|
|
||||||
LogMessage($"Started at {DateTime.Now:t}");
|
LogMessage($"Started at {DateTime.Now:t}");
|
||||||
LogMessage($"Exporting to: {_fileExportService.OutputFolder}");
|
LogMessage($"Output: {outputFolder}");
|
||||||
|
|
||||||
await Task.Run(() => _exportService.Export(exportContext), token);
|
_solidWorksService.SetCommandInProgress(true);
|
||||||
|
|
||||||
|
await Task.Run(async () => await _exportService.ExportAsync(exportContext), token);
|
||||||
|
|
||||||
LogMessage("Done.");
|
LogMessage("Done.");
|
||||||
}
|
}
|
||||||
@@ -356,6 +333,7 @@ namespace ExportDXF.Forms
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
_solidWorksService.SetCommandInProgress(false);
|
||||||
UpdateUIForExportComplete();
|
UpdateUIForExportComplete();
|
||||||
_cancellationTokenSource?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
_cancellationTokenSource = null;
|
_cancellationTokenSource = null;
|
||||||
@@ -377,12 +355,14 @@ namespace ExportDXF.Forms
|
|||||||
private void UpdateUIForExportStart()
|
private void UpdateUIForExportStart()
|
||||||
{
|
{
|
||||||
viewFlipDeciderBox.Enabled = false;
|
viewFlipDeciderBox.Enabled = false;
|
||||||
|
txtFilenameTemplate.Enabled = false;
|
||||||
runButton.Text = "Stop";
|
runButton.Text = "Stop";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateUIForExportComplete()
|
private void UpdateUIForExportComplete()
|
||||||
{
|
{
|
||||||
viewFlipDeciderBox.Enabled = true;
|
viewFlipDeciderBox.Enabled = true;
|
||||||
|
txtFilenameTemplate.Enabled = true;
|
||||||
runButton.Text = "Start";
|
runButton.Text = "Start";
|
||||||
runButton.Enabled = true;
|
runButton.Enabled = true;
|
||||||
}
|
}
|
||||||
@@ -391,7 +371,7 @@ namespace ExportDXF.Forms
|
|||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action(() => OnActiveDocumentChanged(sender, e)));
|
Invoke(new Action(() => UpdateActiveDocumentDisplay()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UpdateActiveDocumentDisplay();
|
UpdateActiveDocumentDisplay();
|
||||||
@@ -402,6 +382,19 @@ namespace ExportDXF.Forms
|
|||||||
var activeDoc = _solidWorksService.GetActiveDocument();
|
var activeDoc = _solidWorksService.GetActiveDocument();
|
||||||
var docTitle = activeDoc?.Title ?? "No Document Open";
|
var docTitle = activeDoc?.Title ?? "No Document Open";
|
||||||
this.Text = $"ExportDXF - {docTitle}";
|
this.Text = $"ExportDXF - {docTitle}";
|
||||||
|
|
||||||
|
if (activeDoc == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Try each extractor to auto-fill the template
|
||||||
|
foreach (var extractor in _extractors)
|
||||||
|
{
|
||||||
|
if (extractor.TryExtract(activeDoc.Title, out var info))
|
||||||
|
{
|
||||||
|
txtFilenameTemplate.Text = info.DefaultTemplate;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogMessage(string message, LogLevel level = LogLevel.Info, string file = null)
|
private void LogMessage(string message, LogLevel level = LogLevel.Info, string file = null)
|
||||||
@@ -433,7 +426,6 @@ namespace ExportDXF.Forms
|
|||||||
|
|
||||||
_logEvents.Add(logEvent);
|
_logEvents.Add(logEvent);
|
||||||
|
|
||||||
// Auto-scroll to the last row
|
|
||||||
if (logEventsDataGrid.Rows.Count > 0)
|
if (logEventsDataGrid.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
logEventsDataGrid.FirstDisplayedScrollingRowIndex = logEventsDataGrid.Rows.Count - 1;
|
logEventsDataGrid.FirstDisplayedScrollingRowIndex = logEventsDataGrid.Rows.Count - 1;
|
||||||
@@ -448,6 +440,11 @@ namespace ExportDXF.Forms
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_bomItems.Add(item);
|
_bomItems.Add(item);
|
||||||
|
|
||||||
|
if (item.CutTemplate != null)
|
||||||
|
{
|
||||||
|
_cutTemplates.Add(item.CutTemplate);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogEventsDataGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
private void LogEventsDataGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace ExportDXF.Models
|
namespace ExportDXF.Models
|
||||||
{
|
{
|
||||||
public class BomItem
|
public class BomItem
|
||||||
{
|
{
|
||||||
public int ID { get; set; }
|
|
||||||
public string ItemNo { get; set; } = "";
|
public string ItemNo { get; set; } = "";
|
||||||
public string PartNo { get; set; } = "";
|
public string PartNo { get; set; } = "";
|
||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
@@ -14,29 +11,7 @@ namespace ExportDXF.Models
|
|||||||
public string PartName { get; set; } = "";
|
public string PartName { get; set; } = "";
|
||||||
public string ConfigurationName { get; set; } = "";
|
public string ConfigurationName { get; set; } = "";
|
||||||
public string Material { get; set; } = "";
|
public string Material { get; set; } = "";
|
||||||
public string CutTemplateName { get; set; } = "";
|
public CutTemplate CutTemplate { get; set; }
|
||||||
public string DxfFilePath { get; set; } = "";
|
|
||||||
|
|
||||||
// Sheet metal properties
|
|
||||||
private double? _thickness;
|
|
||||||
public double? Thickness
|
|
||||||
{
|
|
||||||
get => _thickness;
|
|
||||||
set => _thickness = value.HasValue ? Math.Round(value.Value, 8) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double? KFactor { get; set; }
|
|
||||||
|
|
||||||
private double? _defaultBendRadius;
|
|
||||||
public double? DefaultBendRadius
|
|
||||||
{
|
|
||||||
get => _defaultBendRadius;
|
|
||||||
set => _defaultBendRadius = value.HasValue ? Math.Round(value.Value, 8) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// EF Core relationship to ExportRecord
|
|
||||||
public int ExportRecordId { get; set; }
|
|
||||||
public virtual ExportRecord ExportRecord { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Size
|
public struct Size
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace ExportDXF.Models
|
||||||
|
{
|
||||||
|
public class CutTemplate
|
||||||
|
{
|
||||||
|
public string DxfFilePath { get; set; } = "";
|
||||||
|
public string ContentHash { get; set; }
|
||||||
|
public string CutTemplateName { get; set; } = "";
|
||||||
|
public int Revision { get; set; } = 1;
|
||||||
|
|
||||||
|
private double? _thickness;
|
||||||
|
public double? Thickness
|
||||||
|
{
|
||||||
|
get => _thickness;
|
||||||
|
set => _thickness = value.HasValue ? Math.Round(value.Value, 8) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double? KFactor { get; set; }
|
||||||
|
|
||||||
|
private double? _defaultBendRadius;
|
||||||
|
public double? DefaultBendRadius
|
||||||
|
{
|
||||||
|
get => _defaultBendRadius;
|
||||||
|
set => _defaultBendRadius = value.HasValue ? Math.Round(value.Value, 8) : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using ExportDXF.Models;
|
using ExportDXF.Models;
|
||||||
using ExportDXF.ViewFlipDeciders;
|
using ExportDXF.ViewFlipDeciders;
|
||||||
using SolidWorks.Interop.sldworks;
|
using SolidWorks.Interop.sldworks;
|
||||||
using SolidWorks.Interop.swconst;
|
using SolidWorks.Interop.swconst;
|
||||||
@@ -28,14 +28,14 @@ namespace ExportDXF.Services
|
|||||||
public IViewFlipDecider ViewFlipDecider { get; set; }
|
public IViewFlipDecider ViewFlipDecider { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prefix to prepend to exported filenames.
|
/// Filename template with placeholders (e.g., "4321 A01 PT{item_no:2}").
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FilePrefix { get; set; }
|
public string FilenameTemplate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Selected Equipment ID for API operations (optional).
|
/// Output folder for DXF files and Excel workbook.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? EquipmentId { get; set; }
|
public string OutputFolder { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancellation token for canceling the export operation.
|
/// Cancellation token for canceling the export operation.
|
||||||
@@ -108,23 +108,17 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(title))
|
if (!string.IsNullOrEmpty(title))
|
||||||
{
|
{
|
||||||
// Close the document without saving
|
|
||||||
SolidWorksApp.CloseDoc(title);
|
SolidWorksApp.CloseDoc(title);
|
||||||
ProgressCallback?.Invoke("Closed template drawing", LogLevel.Info, null);
|
ProgressCallback?.Invoke("Closed template drawing", LogLevel.Info, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the reference regardless of success/failure
|
|
||||||
TemplateDrawing = null;
|
TemplateDrawing = null;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ProgressCallback?.Invoke($"Failed to close template drawing: {ex.Message}", LogLevel.Error, null);
|
ProgressCallback?.Invoke($"Failed to close template drawing: {ex.Message}", LogLevel.Error, null);
|
||||||
|
|
||||||
// Still clear the reference to prevent further issues
|
|
||||||
TemplateDrawing = null;
|
TemplateDrawing = null;
|
||||||
|
|
||||||
// Don't throw here as this is cleanup code - log the error but continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace ExportDXF.Models
|
|
||||||
{
|
|
||||||
public class ExportRecord
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string DrawingNumber { get; set; }
|
|
||||||
public string SourceFilePath { get; set; }
|
|
||||||
public string OutputFolder { get; set; }
|
|
||||||
public DateTime ExportedAt { get; set; }
|
|
||||||
public string ExportedBy { get; set; }
|
|
||||||
|
|
||||||
public virtual ICollection<BomItem> BomItems { get; set; } = new List<BomItem>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -61,5 +61,16 @@ namespace ExportDXF.Services
|
|||||||
/// The SolidWorks component reference.
|
/// The SolidWorks component reference.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Component2 Component { get; set; }
|
public Component2 Component { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SHA256 content hash of the exported DXF (transient, not persisted).
|
||||||
|
/// </summary>
|
||||||
|
public string ContentHash { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full path to the locally-exported DXF temp file (transient, not persisted).
|
||||||
|
/// Set after successful export; used for upload to the API.
|
||||||
|
/// </summary>
|
||||||
|
public string LocalTempPath { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+11
-17
@@ -1,16 +1,12 @@
|
|||||||
using ExportDXF.Forms;
|
using ExportDXF.Forms;
|
||||||
using ExportDXF.Services;
|
using ExportDXF.Services;
|
||||||
using System;
|
using System;
|
||||||
using System.Configuration;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace ExportDXF
|
namespace ExportDXF
|
||||||
{
|
{
|
||||||
static class Program
|
static class Program
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The main entry point for the application.
|
|
||||||
/// </summary>
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
{
|
{
|
||||||
@@ -23,34 +19,32 @@ namespace ExportDXF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Simple dependency injection container.
|
|
||||||
/// </summary>
|
|
||||||
public class ServiceContainer
|
public class ServiceContainer
|
||||||
{
|
{
|
||||||
private readonly string _outputFolder;
|
|
||||||
|
|
||||||
public ServiceContainer()
|
|
||||||
{
|
|
||||||
_outputFolder = ConfigurationManager.AppSettings["ExportOutputFolder"] ?? @"C:\ExportDXF\Output";
|
|
||||||
}
|
|
||||||
|
|
||||||
public MainForm ResolveMainForm()
|
public MainForm ResolveMainForm()
|
||||||
{
|
{
|
||||||
var solidWorksService = new SolidWorksService();
|
var solidWorksService = new SolidWorksService();
|
||||||
var bomExtractor = new BomExtractor();
|
var bomExtractor = new BomExtractor();
|
||||||
var partExporter = new PartExporter();
|
var partExporter = new PartExporter();
|
||||||
var drawingExporter = new DrawingExporter();
|
var drawingExporter = new DrawingExporter();
|
||||||
var fileExportService = new FileExportService(_outputFolder);
|
var excelExportService = new ExcelExportService();
|
||||||
|
var logFileService = new LogFileService();
|
||||||
|
|
||||||
var exportService = new DxfExportService(
|
var exportService = new DxfExportService(
|
||||||
solidWorksService,
|
solidWorksService,
|
||||||
bomExtractor,
|
bomExtractor,
|
||||||
partExporter,
|
partExporter,
|
||||||
drawingExporter,
|
drawingExporter,
|
||||||
fileExportService);
|
excelExportService,
|
||||||
|
logFileService);
|
||||||
|
|
||||||
return new MainForm(solidWorksService, exportService, fileExportService);
|
var extractors = new IDrawingInfoExtractor[]
|
||||||
|
{
|
||||||
|
new EquipmentDrawingInfoExtractor(),
|
||||||
|
new DefaultDrawingInfoExtractor()
|
||||||
|
};
|
||||||
|
|
||||||
|
return new MainForm(solidWorksService, exportService, extractors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Configuration;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fallback extractor that uses the document name as prefix
|
||||||
|
/// and appends the configured default suffix.
|
||||||
|
/// Always returns true — this is the catch-all.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultDrawingInfoExtractor : IDrawingInfoExtractor
|
||||||
|
{
|
||||||
|
public bool TryExtract(string documentName, out DrawingInfoResult info)
|
||||||
|
{
|
||||||
|
var name = Path.GetFileNameWithoutExtension(documentName);
|
||||||
|
var suffix = ConfigurationManager.AppSettings["DefaultSuffix"] ?? "PT{item_no:2}";
|
||||||
|
|
||||||
|
info = new DrawingInfoResult
|
||||||
|
{
|
||||||
|
EquipmentNumber = null,
|
||||||
|
DrawingNumber = null,
|
||||||
|
DefaultTemplate = $"{name} {suffix}"
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,56 +1,47 @@
|
|||||||
using ExportDXF.Data;
|
|
||||||
using ExportDXF.Extensions;
|
using ExportDXF.Extensions;
|
||||||
using ExportDXF.ItemExtractors;
|
using ExportDXF.ItemExtractors;
|
||||||
using ExportDXF.Models;
|
using ExportDXF.Models;
|
||||||
using ExportDXF;
|
using ExportDXF.Utilities;
|
||||||
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.Threading.Tasks;
|
||||||
|
|
||||||
namespace ExportDXF.Services
|
namespace ExportDXF.Services
|
||||||
{
|
{
|
||||||
public interface IDxfExportService
|
public interface IDxfExportService
|
||||||
{
|
{
|
||||||
/// <summary>
|
Task ExportAsync(ExportContext context);
|
||||||
/// Exports the document specified in the context to DXF format.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The export context containing all necessary information.</param>
|
|
||||||
void Export(ExportContext context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Service responsible for orchestrating the export of SolidWorks documents to DXF format.
|
|
||||||
/// </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 IFileExportService _fileExportService;
|
private readonly ExcelExportService _excelExportService;
|
||||||
private readonly Func<ExportDxfDbContext> _dbContextFactory;
|
private readonly LogFileService _logFileService;
|
||||||
|
|
||||||
public DxfExportService(
|
public DxfExportService(
|
||||||
ISolidWorksService solidWorksService,
|
ISolidWorksService solidWorksService,
|
||||||
IBomExtractor bomExtractor,
|
IBomExtractor bomExtractor,
|
||||||
IPartExporter partExporter,
|
IPartExporter partExporter,
|
||||||
IDrawingExporter drawingExporter,
|
IDrawingExporter drawingExporter,
|
||||||
IFileExportService fileExportService,
|
ExcelExportService excelExportService,
|
||||||
Func<ExportDxfDbContext> dbContextFactory = null)
|
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));
|
||||||
_fileExportService = fileExportService ?? throw new ArgumentNullException(nameof(fileExportService));
|
_excelExportService = excelExportService ?? throw new ArgumentNullException(nameof(excelExportService));
|
||||||
_dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext());
|
_logFileService = logFileService ?? throw new ArgumentNullException(nameof(logFileService));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public async Task ExportAsync(ExportContext context)
|
||||||
/// Exports the document specified in the context to DXF format.
|
|
||||||
/// </summary>
|
|
||||||
public void Export(ExportContext context)
|
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
@@ -58,7 +49,30 @@ 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();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -67,35 +81,57 @@ namespace ExportDXF.Services
|
|||||||
switch (context.ActiveDocument.DocumentType)
|
switch (context.ActiveDocument.DocumentType)
|
||||||
{
|
{
|
||||||
case DocumentType.Part:
|
case DocumentType.Part:
|
||||||
ExportPart(context);
|
await ExportPartAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DocumentType.Assembly:
|
case DocumentType.Assembly:
|
||||||
ExportAssembly(context);
|
await ExportAssemblyAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DocumentType.Drawing:
|
case DocumentType.Drawing:
|
||||||
ExportDrawing(context);
|
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
|
||||||
{
|
{
|
||||||
CleanupExportContext(context);
|
CleanupExportContext(context);
|
||||||
_solidWorksService.EnableUserControl(true);
|
_solidWorksService.EnableUserControl(true);
|
||||||
|
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 void ExportPart(ExportContext context)
|
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");
|
||||||
|
|
||||||
@@ -106,11 +142,25 @@ namespace ExportDXF.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export directly to the output folder
|
var item = _partExporter.ExportSinglePart(part, tempDir, context);
|
||||||
_partExporter.ExportSinglePart(part, _fileExportService.OutputFolder, context);
|
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
item.ItemNo = "1";
|
||||||
|
|
||||||
|
var bomItem = CreateBomItem(item);
|
||||||
|
PlaceDxfFile(item, context, outputFolder, existingTemplates, bomItem);
|
||||||
|
bomItems.Add(bomItem);
|
||||||
|
context.BomItemCallback?.Invoke(bomItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExportAssembly(ExportContext context)
|
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...");
|
||||||
@@ -132,11 +182,26 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
LogProgress(context, $"Found {items.Count} item(s).");
|
LogProgress(context, $"Found {items.Count} item(s).");
|
||||||
|
|
||||||
// Export directly to the output folder
|
// Assign item numbers
|
||||||
ExportItems(items, _fileExportService.OutputFolder, context);
|
int nextNum = 1;
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||||
|
{
|
||||||
|
item.ItemNo = nextNum.ToString();
|
||||||
|
nextNum++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExportDrawing(ExportContext context)
|
await ExportItemsAsync(items, tempDir, outputFolder, context, existingTemplates, bomItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
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...");
|
||||||
@@ -145,68 +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)");
|
||||||
|
|
||||||
// Determine drawing number for file naming
|
// Export drawing to PDF in output folder
|
||||||
var drawingNumber = ParseDrawingNumber(context);
|
_drawingExporter.ExportToPdf(drawing, outputFolder, context);
|
||||||
|
|
||||||
// Export drawing to PDF
|
await ExportItemsAsync(items, tempDir, outputFolder, context, existingTemplates, bomItems);
|
||||||
var tempDir = CreateTempWorkDir();
|
|
||||||
_drawingExporter.ExportToPdf(drawing, tempDir, context);
|
|
||||||
|
|
||||||
// Copy PDF to output folder
|
return rawBomTable;
|
||||||
try
|
|
||||||
{
|
|
||||||
var pdfs = Directory.GetFiles(tempDir, "*.pdf");
|
|
||||||
if (pdfs.Length > 0)
|
|
||||||
{
|
|
||||||
var savedPath = _fileExportService.SavePdfFile(pdfs[0], drawingNumber);
|
|
||||||
LogProgress(context, $"Saved PDF: {Path.GetFileName(savedPath)}", LogLevel.Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogProgress(context, $"PDF save error: {ex.Message}", LogLevel.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create export record in database
|
|
||||||
ExportRecord exportRecord = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var db = _dbContextFactory())
|
|
||||||
{
|
|
||||||
db.Database.EnsureCreated();
|
|
||||||
exportRecord = new ExportRecord
|
|
||||||
{
|
|
||||||
DrawingNumber = drawingNumber ?? context.ActiveDocument.Title,
|
|
||||||
SourceFilePath = context.ActiveDocument.FilePath,
|
|
||||||
OutputFolder = _fileExportService.OutputFolder,
|
|
||||||
ExportedAt = DateTime.Now,
|
|
||||||
ExportedBy = System.Environment.UserName
|
|
||||||
};
|
|
||||||
db.ExportRecords.Add(exportRecord);
|
|
||||||
db.SaveChanges();
|
|
||||||
LogProgress(context, $"Created export record (ID: {exportRecord.Id})", LogLevel.Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogProgress(context, $"Database error creating export record: {ex.Message}", LogLevel.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export parts to DXF (directly to output folder) and save BOM items
|
|
||||||
ExportItems(items, _fileExportService.OutputFolder, context, exportRecord?.Id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -215,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");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +280,6 @@ namespace ExportDXF.Services
|
|||||||
{
|
{
|
||||||
TopLevelOnly = false
|
TopLevelOnly = false
|
||||||
};
|
};
|
||||||
|
|
||||||
return extractor.GetItems();
|
return extractor.GetItems();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -265,9 +289,17 @@ 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,
|
||||||
|
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 unchangedCount = 0;
|
||||||
int failureCount = 0;
|
int failureCount = 0;
|
||||||
int sortOrder = 0;
|
int sortOrder = 0;
|
||||||
|
|
||||||
@@ -281,79 +313,150 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// PartExporter will handle template drawing creation through context
|
_partExporter.ExportItem(item, tempDir, context);
|
||||||
_partExporter.ExportItem(item, saveDirectory, context);
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(item.FileName))
|
var bomItem = CreateBomItem(item);
|
||||||
|
bomItem.SortOrder = sortOrder++;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(item.LocalTempPath))
|
||||||
{
|
{
|
||||||
|
var wasPlaced = PlaceDxfFile(item, context, outputFolder, existingTemplates, bomItem);
|
||||||
|
if (wasPlaced)
|
||||||
successCount++;
|
successCount++;
|
||||||
LogProgress(context, $"Exported: {item.FileName}.dxf", LogLevel.Info);
|
else
|
||||||
|
unchangedCount++;
|
||||||
// Create BOM item
|
}
|
||||||
var dxfPath = Path.Combine(saveDirectory, item.FileName + ".dxf");
|
else
|
||||||
var bomItem = new BomItem
|
{
|
||||||
|
skippedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
bomItems.Add(bomItem);
|
||||||
|
context.BomItemCallback?.Invoke(bomItem);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", LogLevel.Error);
|
||||||
|
_logFileService.LogError($"Item {item.ItemNo}: {ex.Message}");
|
||||||
|
failureCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var summary = $"Export complete: {successCount} exported";
|
||||||
|
if (unchangedCount > 0)
|
||||||
|
summary += $", {unchangedCount} unchanged";
|
||||||
|
if (skippedCount > 0)
|
||||||
|
summary += $", {skippedCount} skipped (non-sheet-metal)";
|
||||||
|
if (failureCount > 0)
|
||||||
|
summary += $", {failureCount} failed";
|
||||||
|
|
||||||
|
LogProgress(context, summary, failureCount > 0 ? LogLevel.Warning : LogLevel.Info);
|
||||||
|
_logFileService.LogInfo(summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Places a DXF file in the output folder with revision tracking.
|
||||||
|
/// Returns true if a new file was written, false if unchanged.
|
||||||
|
/// </summary>
|
||||||
|
private bool PlaceDxfFile(
|
||||||
|
Item item,
|
||||||
|
ExportContext context,
|
||||||
|
string outputFolder,
|
||||||
|
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
||||||
|
BomItem bomItem)
|
||||||
|
{
|
||||||
|
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))
|
||||||
|
{
|
||||||
|
if (existing.ContentHash == contentHash)
|
||||||
|
{
|
||||||
|
// Unchanged — skip file write, keep existing
|
||||||
|
dxfFileName = existing.FileName;
|
||||||
|
revision = existing.Revision;
|
||||||
|
|
||||||
|
LogProgress(context, $"Unchanged: {dxfFileName}", LogLevel.Info, item.PartName);
|
||||||
|
_logFileService.LogInfo($"Unchanged: {dxfFileName}");
|
||||||
|
|
||||||
|
bomItem.CutTemplate = new CutTemplate
|
||||||
|
{
|
||||||
|
DxfFilePath = 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 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})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 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
|
||||||
{
|
{
|
||||||
ExportRecordId = exportRecordId ?? 0,
|
|
||||||
ItemNo = item.ItemNo ?? "",
|
ItemNo = item.ItemNo ?? "",
|
||||||
PartNo = item.FileName ?? item.PartName ?? "",
|
PartNo = item.FileName ?? item.PartName ?? "",
|
||||||
SortOrder = sortOrder++,
|
SortOrder = 0,
|
||||||
Qty = item.Quantity,
|
Qty = item.Quantity,
|
||||||
TotalQty = item.Quantity,
|
TotalQty = item.Quantity,
|
||||||
Description = item.Description ?? "",
|
Description = item.Description ?? "",
|
||||||
PartName = item.PartName ?? "",
|
PartName = item.PartName ?? "",
|
||||||
ConfigurationName = item.Configuration ?? "",
|
ConfigurationName = item.Configuration ?? "",
|
||||||
Material = item.Material ?? "",
|
Material = item.Material ?? ""
|
||||||
Thickness = item.Thickness > 0 ? item.Thickness : null,
|
|
||||||
KFactor = item.KFactor > 0 ? item.KFactor : null,
|
|
||||||
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null,
|
|
||||||
DxfFilePath = dxfPath
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add to UI
|
|
||||||
context.BomItemCallback?.Invoke(bomItem);
|
|
||||||
|
|
||||||
// Save BOM item to database 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
failureCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", LogLevel.Error);
|
|
||||||
failureCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
LogProgress(context, $"Export complete: {successCount} succeeded, {failureCount} failed",
|
|
||||||
failureCount > 0 ? LogLevel.Warning : LogLevel.Info);
|
|
||||||
|
|
||||||
if (exportRecordId.HasValue)
|
|
||||||
{
|
|
||||||
LogProgress(context, $"BOM items saved to database (ExportRecord ID: {exportRecordId.Value})", LogLevel.Info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#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"));
|
||||||
@@ -361,17 +464,17 @@ namespace ExportDXF.Services
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ParseDrawingNumber(ExportContext context)
|
private void CleanupTempDir(string tempDir)
|
||||||
{
|
{
|
||||||
// Prefer prefix (e.g., "5007 A02 PT"), fallback to active document title
|
try
|
||||||
var candidate = context?.FilePrefix;
|
|
||||||
var info = string.IsNullOrWhiteSpace(candidate) ? null : DrawingInfo.Parse(candidate);
|
|
||||||
if (info == null)
|
|
||||||
{
|
{
|
||||||
var title = context?.ActiveDocument?.Title;
|
if (Directory.Exists(tempDir))
|
||||||
info = string.IsNullOrWhiteSpace(title) ? null : DrawingInfo.Parse(title);
|
Directory.Delete(tempDir, recursive: true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Best-effort cleanup
|
||||||
}
|
}
|
||||||
return info != null ? ($"{info.EquipmentNo} {info.DrawingNo}") : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateContext(ExportContext context)
|
private void ValidateContext(ExportContext context)
|
||||||
@@ -381,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)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts equipment/drawing numbers from document names matching the
|
||||||
|
/// workplace format (e.g., "4321 A01.SLDDRW" → equipment "4321", drawing "A01").
|
||||||
|
/// Uses the existing DrawingInfo.Parse() logic.
|
||||||
|
/// </summary>
|
||||||
|
public class EquipmentDrawingInfoExtractor : IDrawingInfoExtractor
|
||||||
|
{
|
||||||
|
public bool TryExtract(string documentName, out DrawingInfoResult info)
|
||||||
|
{
|
||||||
|
info = null;
|
||||||
|
|
||||||
|
var name = Path.GetFileNameWithoutExtension(documentName);
|
||||||
|
var parsed = DrawingInfo.Parse(name);
|
||||||
|
|
||||||
|
if (parsed == null || string.IsNullOrEmpty(parsed.EquipmentNo))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var template = !string.IsNullOrEmpty(parsed.DrawingNo)
|
||||||
|
? $"{parsed.EquipmentNo} {parsed.DrawingNo} PT{{item_no:2}}"
|
||||||
|
: $"{parsed.EquipmentNo} PT{{item_no:2}}";
|
||||||
|
|
||||||
|
info = new DrawingInfoResult
|
||||||
|
{
|
||||||
|
EquipmentNumber = parsed.EquipmentNo,
|
||||||
|
DrawingNumber = parsed.DrawingNo,
|
||||||
|
DefaultTemplate = template
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 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)
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, (string ContentHash, int Revision, string FileName)> ReadExistingCutTemplates(string xlsxPath)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, (string, int, string)>();
|
||||||
|
|
||||||
|
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<string, int>();
|
||||||
|
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<int>()
|
||||||
|
: 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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).
|
||||||
|
/// </summary>
|
||||||
|
public void Write(
|
||||||
|
string xlsxPath,
|
||||||
|
List<Dictionary<string, string>> rawBomTable,
|
||||||
|
List<BomItem> 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<Dictionary<string, string>> 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<BomItem> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace ExportDXF.Services
|
|
||||||
{
|
|
||||||
public interface IFileExportService
|
|
||||||
{
|
|
||||||
string OutputFolder { get; }
|
|
||||||
string SaveDxfFile(string sourcePath, string drawingNumber, string itemNo);
|
|
||||||
string SavePdfFile(string sourcePath, string drawingNumber);
|
|
||||||
void EnsureOutputFolderExists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FileExportService : IFileExportService
|
|
||||||
{
|
|
||||||
public string OutputFolder { get; }
|
|
||||||
|
|
||||||
public FileExportService(string outputFolder)
|
|
||||||
{
|
|
||||||
OutputFolder = outputFolder ?? throw new ArgumentNullException(nameof(outputFolder));
|
|
||||||
EnsureOutputFolderExists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnsureOutputFolderExists()
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(OutputFolder))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(OutputFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SaveDxfFile(string sourcePath, string drawingNumber, string itemNo)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(sourcePath))
|
|
||||||
throw new ArgumentNullException(nameof(sourcePath));
|
|
||||||
|
|
||||||
var fileName = !string.IsNullOrEmpty(drawingNumber) && !string.IsNullOrEmpty(itemNo)
|
|
||||||
? $"{drawingNumber} PT{itemNo}.dxf"
|
|
||||||
: Path.GetFileName(sourcePath);
|
|
||||||
|
|
||||||
var destPath = Path.Combine(OutputFolder, fileName);
|
|
||||||
|
|
||||||
// If source and dest are the same, skip copy
|
|
||||||
if (!string.Equals(sourcePath, destPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
File.Copy(sourcePath, destPath, overwrite: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return destPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SavePdfFile(string sourcePath, string drawingNumber)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(sourcePath))
|
|
||||||
throw new ArgumentNullException(nameof(sourcePath));
|
|
||||||
|
|
||||||
var fileName = !string.IsNullOrEmpty(drawingNumber)
|
|
||||||
? $"{drawingNumber}.pdf"
|
|
||||||
: Path.GetFileName(sourcePath);
|
|
||||||
|
|
||||||
var destPath = Path.Combine(OutputFolder, fileName);
|
|
||||||
|
|
||||||
// If source and dest are the same, skip copy
|
|
||||||
if (!string.Equals(sourcePath, destPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
File.Copy(sourcePath, destPath, overwrite: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return destPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
public static class FilenameTemplateParser
|
||||||
|
{
|
||||||
|
private static readonly Regex PlaceholderPattern = new Regex(
|
||||||
|
@"\{(?<name>\w+)(?::(?<pad>\d+))?\}",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates a template string for a given item.
|
||||||
|
/// e.g. "4321 A01 PT{item_no:2}" with item_no=3 → "4321 A01 PT03"
|
||||||
|
/// </summary>
|
||||||
|
public static string Evaluate(string template, Item item)
|
||||||
|
{
|
||||||
|
return PlaceholderPattern.Replace(template, match =>
|
||||||
|
{
|
||||||
|
var name = match.Groups["name"].Value.ToLowerInvariant();
|
||||||
|
var padStr = match.Groups["pad"].Value;
|
||||||
|
int pad = string.IsNullOrEmpty(padStr) ? 0 : int.Parse(padStr);
|
||||||
|
|
||||||
|
string value;
|
||||||
|
switch (name)
|
||||||
|
{
|
||||||
|
case "item_no":
|
||||||
|
value = item.ItemNo ?? "0";
|
||||||
|
if (pad > 0)
|
||||||
|
value = value.PadLeft(pad, '0');
|
||||||
|
break;
|
||||||
|
case "part_name":
|
||||||
|
value = item.PartName ?? "";
|
||||||
|
break;
|
||||||
|
case "config":
|
||||||
|
value = item.Configuration ?? "";
|
||||||
|
break;
|
||||||
|
case "material":
|
||||||
|
value = item.Material ?? "";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = match.Value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the literal prefix before the first placeholder.
|
||||||
|
/// Used for naming the xlsx and log files.
|
||||||
|
/// Falls back to documentName if prefix is empty.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetPrefix(string template, string documentName)
|
||||||
|
{
|
||||||
|
var match = PlaceholderPattern.Match(template);
|
||||||
|
if (!match.Success)
|
||||||
|
return template.Trim();
|
||||||
|
|
||||||
|
var prefix = template.Substring(0, match.Index).Trim();
|
||||||
|
if (string.IsNullOrEmpty(prefix))
|
||||||
|
return Path.GetFileNameWithoutExtension(documentName);
|
||||||
|
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that the template contains {item_no} to prevent filename collisions.
|
||||||
|
/// </summary>
|
||||||
|
public static bool Validate(string template, out string error)
|
||||||
|
{
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(template))
|
||||||
|
{
|
||||||
|
error = "Template cannot be empty.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasItemNo = PlaceholderPattern.Matches(template)
|
||||||
|
.Cast<Match>()
|
||||||
|
.Any(m => m.Groups["name"].Value.Equals("item_no", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (!hasItemNo)
|
||||||
|
{
|
||||||
|
error = "Template must contain {item_no} or {item_no:N} to avoid filename collisions.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
public interface IDrawingInfoExtractor
|
||||||
|
{
|
||||||
|
bool TryExtract(string documentName, out DrawingInfoResult info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DrawingInfoResult
|
||||||
|
{
|
||||||
|
public string EquipmentNumber { get; set; }
|
||||||
|
public string DrawingNumber { get; set; }
|
||||||
|
public string DefaultTemplate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -15,24 +15,29 @@ namespace ExportDXF.Services
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exports a single part document to DXF.
|
/// Exports a single part document to DXF.
|
||||||
|
/// 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>
|
||||||
void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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
|
||||||
{
|
{
|
||||||
public void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
|
public PartExporter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
|
||||||
{
|
{
|
||||||
if (part == null)
|
if (part == null)
|
||||||
throw new ArgumentNullException(nameof(part));
|
throw new ArgumentNullException(nameof(part));
|
||||||
@@ -49,12 +54,52 @@ 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
|
||||||
|
var item = new Item
|
||||||
|
{
|
||||||
|
PartName = model.GetTitle()?.Replace(".SLDPRT", "") ?? "",
|
||||||
|
Configuration = originalConfigName ?? "",
|
||||||
|
Quantity = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enrich with sheet metal properties and description
|
||||||
|
var sheetMetalProps = SolidWorksHelper.GetSheetMetalProperties(model);
|
||||||
|
if (sheetMetalProps != null)
|
||||||
|
{
|
||||||
|
item.Thickness = sheetMetalProps.Thickness;
|
||||||
|
item.KFactor = sheetMetalProps.KFactor;
|
||||||
|
item.BendRadius = sheetMetalProps.BendRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get description from custom properties
|
||||||
|
var configPropMgr = model.Extension.CustomPropertyManager[originalConfigName];
|
||||||
|
item.Description = configPropMgr?.Get("Description");
|
||||||
|
if (string.IsNullOrEmpty(item.Description))
|
||||||
|
{
|
||||||
|
var docPropMgr = model.Extension.CustomPropertyManager[""];
|
||||||
|
item.Description = docPropMgr?.Get("Description");
|
||||||
|
}
|
||||||
|
item.Description = TextHelper.RemoveXmlTags(item.Description);
|
||||||
|
|
||||||
|
// Get material
|
||||||
|
item.Material = part.GetMaterialPropertyName2(originalConfigName, out _);
|
||||||
|
|
||||||
context.GetOrCreateTemplateDrawing();
|
context.GetOrCreateTemplateDrawing();
|
||||||
|
|
||||||
ExportPartToDxf(part, originalConfigName, savePath, context);
|
if (ExportPartToDxf(part, originalConfigName, savePath, context))
|
||||||
|
{
|
||||||
|
item.FileName = Path.GetFileNameWithoutExtension(savePath);
|
||||||
|
item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath);
|
||||||
|
item.LocalTempPath = savePath;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -94,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();
|
||||||
@@ -102,6 +147,8 @@ namespace ExportDXF.Services
|
|||||||
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.LocalTempPath = savePath;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -260,6 +307,7 @@ namespace ExportDXF.Services
|
|||||||
{
|
{
|
||||||
var etcher = new EtchBendLines.Etcher();
|
var etcher = new EtchBendLines.Etcher();
|
||||||
etcher.AddEtchLines(dxfPath);
|
etcher.AddEtchLines(dxfPath);
|
||||||
|
FixDegreeSymbol(dxfPath);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -267,30 +315,39 @@ namespace ExportDXF.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetSinglePartFileName(ModelDoc2 model, string prefix)
|
/// <summary>
|
||||||
|
/// Workaround for ACadSharp encoding bug (no upstream fix as of v3.4.9).
|
||||||
|
/// ACadSharp's DxfReader uses $DWGCODEPAGE (ANSI_1252) to decode text, but
|
||||||
|
/// AC1018+ DXF files use UTF-8. The degree symbol ° (UTF-8: C2 B0) gets
|
||||||
|
/// misread as two ANSI_1252 characters: Â (C2) and ° (B0).
|
||||||
|
/// See: https://github.com/DomCR/ACadSharp/issues?q=encoding
|
||||||
|
/// </summary>
|
||||||
|
private static void FixDegreeSymbol(string path)
|
||||||
|
{
|
||||||
|
var text = System.IO.File.ReadAllText(path);
|
||||||
|
if (text.Contains("\u00C2\u00B0"))
|
||||||
|
{
|
||||||
|
text = text.Replace("\u00C2\u00B0", "\u00B0");
|
||||||
|
System.IO.File.WriteAllText(path, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
var isDefaultConfig = string.Equals(config, "default", StringComparison.OrdinalIgnoreCase);
|
var isDefaultConfig = string.Equals(config, "default", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var name = isDefaultConfig ? title : $"{title} [{config}]";
|
return isDefaultConfig ? title : $"{title} [{config}]";
|
||||||
return prefix + name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetItemFileName(Item item, string prefix)
|
private string GetItemFileName(Item item)
|
||||||
{
|
{
|
||||||
prefix = prefix?.Replace("\"", "''") ?? string.Empty;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||||
{
|
return item.PartName ?? "unknown";
|
||||||
return prefix + item.PartName;
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using SolidWorks.Interop.sldworks;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads all visible columns and rows from a SolidWorks BOM table annotation
|
||||||
|
/// as raw string data for direct copy into an Excel sheet.
|
||||||
|
/// </summary>
|
||||||
|
public static class RawBomTableReader
|
||||||
|
{
|
||||||
|
public static List<Dictionary<string, string>> Read(BomTableAnnotation bomTable)
|
||||||
|
{
|
||||||
|
var table = (TableAnnotation)bomTable;
|
||||||
|
var rows = new List<Dictionary<string, string>>();
|
||||||
|
|
||||||
|
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<string, string>();
|
||||||
|
foreach (var (colIdx, header) in columns)
|
||||||
|
{
|
||||||
|
rowData[header] = table.get_Text(row, colIdx)?.Trim() ?? "";
|
||||||
|
}
|
||||||
|
rows.Add(rowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -88,6 +88,13 @@ namespace ExportDXF.Services
|
|||||||
/// <param name="enable">True to enable user control, false to disable.</param>
|
/// <param name="enable">True to enable user control, false to disable.</param>
|
||||||
void EnableUserControl(bool enable);
|
void EnableUserControl(bool enable);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets whether a command is in progress. When true, user input to
|
||||||
|
/// SolidWorks is disabled and interactive dialogs are suppressed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="inProgress">True to block user input, false to re-enable.</param>
|
||||||
|
void SetCommandInProgress(bool inProgress);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the SolidWorks application instance.
|
/// Gets the SolidWorks application instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -188,6 +195,15 @@ namespace ExportDXF.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void SetCommandInProgress(bool inProgress)
|
||||||
|
{
|
||||||
|
if (_sldWorks != null)
|
||||||
|
{
|
||||||
|
_sldWorks.CommandInProgress = inProgress;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the native SolidWorks application instance.
|
/// Gets the native SolidWorks application instance.
|
||||||
/// Use this when you need direct access to the SolidWorks API.
|
/// Use this when you need direct access to the SolidWorks API.
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ExportDXF.Utilities
|
||||||
|
{
|
||||||
|
public static class ContentHasher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a SHA256 hash of DXF file content, skipping the HEADER section
|
||||||
|
/// which contains timestamps that change on every save.
|
||||||
|
/// </summary>
|
||||||
|
public static string ComputeDxfContentHash(string filePath)
|
||||||
|
{
|
||||||
|
var text = File.ReadAllText(filePath);
|
||||||
|
var contentStart = FindEndOfHeader(text);
|
||||||
|
var content = contentStart >= 0 ? text.Substring(contentStart) : text;
|
||||||
|
|
||||||
|
using (var sha = SHA256.Create())
|
||||||
|
{
|
||||||
|
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
|
||||||
|
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a SHA256 hash of the entire file contents (for PDFs and other binary files).
|
||||||
|
/// </summary>
|
||||||
|
public static string ComputeFileHash(string filePath)
|
||||||
|
{
|
||||||
|
using (var sha = SHA256.Create())
|
||||||
|
using (var stream = File.OpenRead(filePath))
|
||||||
|
{
|
||||||
|
var bytes = sha.ComputeHash(stream);
|
||||||
|
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the position immediately after the HEADER section's ENDSEC marker.
|
||||||
|
/// DXF HEADER format:
|
||||||
|
/// 0\nSECTION\n2\nHEADER\n...variables...\n0\nENDSEC\n
|
||||||
|
/// Returns -1 if no HEADER section is found.
|
||||||
|
/// </summary>
|
||||||
|
private static int FindEndOfHeader(string text)
|
||||||
|
{
|
||||||
|
// Find the HEADER section start
|
||||||
|
var headerIndex = FindGroupCode(text, 0, "2", "HEADER");
|
||||||
|
if (headerIndex < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Advance past the HEADER value line so pair scanning stays aligned
|
||||||
|
var headerLineEnd = text.IndexOf('\n', headerIndex);
|
||||||
|
if (headerLineEnd < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Find the ENDSEC that closes the HEADER section
|
||||||
|
var pos = headerLineEnd + 1;
|
||||||
|
while (pos < text.Length)
|
||||||
|
{
|
||||||
|
var endsecIndex = FindGroupCode(text, pos, "0", "ENDSEC");
|
||||||
|
if (endsecIndex < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Move past the ENDSEC line
|
||||||
|
var lineEnd = text.IndexOf('\n', endsecIndex);
|
||||||
|
return lineEnd >= 0 ? lineEnd + 1 : text.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds a DXF group code pair (code line followed by value line) starting from the given position.
|
||||||
|
/// Returns the position of the value line, or -1 if not found.
|
||||||
|
/// </summary>
|
||||||
|
private static int FindGroupCode(string text, int startIndex, string groupCode, string value)
|
||||||
|
{
|
||||||
|
var pos = startIndex;
|
||||||
|
while (pos < text.Length)
|
||||||
|
{
|
||||||
|
// Skip whitespace/newlines to find the group code
|
||||||
|
while (pos < text.Length && (text[pos] == '\r' || text[pos] == '\n' || text[pos] == ' '))
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
if (pos >= text.Length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Read the group code line
|
||||||
|
var codeLineEnd = text.IndexOf('\n', pos);
|
||||||
|
if (codeLineEnd < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var codeLine = text.Substring(pos, codeLineEnd - pos).Trim();
|
||||||
|
|
||||||
|
// Move to the value line
|
||||||
|
var valueStart = codeLineEnd + 1;
|
||||||
|
if (valueStart >= text.Length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var valueLineEnd = text.IndexOf('\n', valueStart);
|
||||||
|
if (valueLineEnd < 0)
|
||||||
|
valueLineEnd = text.Length;
|
||||||
|
|
||||||
|
var valueLine = text.Substring(valueStart, valueLineEnd - valueStart).Trim();
|
||||||
|
|
||||||
|
if (codeLine == groupCode && string.Equals(valueLine, value, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return valueStart;
|
||||||
|
|
||||||
|
// Move to the next pair
|
||||||
|
pos = valueLineEnd + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup>
|
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
|
|
||||||
</startup>
|
|
||||||
<appSettings>
|
<appSettings>
|
||||||
<add key="MaxBendRadius" value="2.0"/>
|
<add key="MaxBendRadius" value="2.0"/>
|
||||||
<add key="ExportOutputFolder" value="C:\ExportDXF\Output"/>
|
<add key="DefaultSuffix" value="PT{item_no:2}"/>
|
||||||
</appSettings>
|
</appSettings>
|
||||||
<connectionStrings>
|
|
||||||
<add name="ExportDxfDb"
|
|
||||||
connectionString="Server=localhost;Database=ExportDxfDb;Trusted_Connection=True;TrustServerCertificate=True;"
|
|
||||||
providerName="Microsoft.Data.SqlClient"/>
|
|
||||||
</connectionStrings>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -1,74 +1,104 @@
|
|||||||
# ExportDXF
|
# ExportDXF
|
||||||
|
|
||||||
A Windows desktop application that automates exporting flat pattern DXF files from SolidWorks drawings, assemblies, and parts. Built for sheet metal fabrication workflows, it extracts BOM data, generates DXF flat patterns with etch/bend line markings, exports drawing PDFs, and uploads everything to a CutFab API for downstream cut programming.
|
A Windows desktop application that automates exporting flat pattern DXF files from SolidWorks drawings, assemblies, and parts. Built for sheet metal fabrication workflows, it extracts BOM data, generates DXF flat patterns with etch/bend line markings, and exports everything to a local folder with an Excel workbook for downstream tools like [OpenNest](https://github.com/ajisaacs/OpenNest).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Batch DXF export** from SolidWorks drawings, assemblies, or individual parts
|
- **Batch DXF export** from SolidWorks drawings, assemblies, or individual parts
|
||||||
- **Flat pattern generation** with automatic sheet metal detection
|
- **Flat pattern generation** with automatic sheet metal detection
|
||||||
- **Etch line insertion** on bend-up lines for fabrication reference (via EtchBendLines library)
|
- **Etch line insertion** on bend-up lines for fabrication reference (via EtchBendLines/ACadSharp)
|
||||||
- **PDF export** of SolidWorks drawings
|
- **PDF export** of SolidWorks drawings
|
||||||
- **CutFab API integration** -- uploads DXFs, PDFs, and BOM data with sheet metal properties (thickness, K-factor, bend radius, material)
|
- **Excel BOM output** -- generates an xlsx workbook with a BOM sheet (direct copy of the SolidWorks BOM table) and a Cut Templates sheet (DXF filenames, thicknesses, K-factors, bend radii, content hashes)
|
||||||
|
- **Revision tracking** -- content hashing detects unchanged DXFs across re-exports; changed files get revision suffixes (e.g., `PT03 Rev2.dxf`)
|
||||||
|
- **Configurable filename template** -- format like `4321 A01 PT{item_no:2}` with placeholders for item number, part name, configuration, and material
|
||||||
|
- **Pluggable drawing info extraction** -- auto-fills the filename template from the document name; extensible via `IDrawingInfoExtractor`
|
||||||
- **View flip control** with automatic, manual, and prefer-up strategies
|
- **View flip control** with automatic, manual, and prefer-up strategies
|
||||||
- **Drawing selection UI** that connects to SolidWorks and displays the active document
|
- **Active document tracking** -- connects to SolidWorks and updates the UI when the active document changes
|
||||||
- **BOM extraction** from drawing BOM tables or assembly component trees
|
- **Per-export and app-level logging** to plain text log files
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- Windows 10/11
|
- Windows 10/11
|
||||||
- .NET Framework 4.8
|
- .NET 8.0
|
||||||
- SolidWorks (installed and licensed)
|
- SolidWorks (installed and licensed)
|
||||||
- CutFab API server (default: `http://localhost:7027`)
|
|
||||||
|
|
||||||
## Solution Structure
|
## Solution Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
ExportDXF.sln
|
ExportDXF.sln
|
||||||
ExportDXF/ Main WinForms application
|
ExportDXF/ Main WinForms application
|
||||||
EtchBendLines/ Library for adding etch lines to DXF files (git submodule)
|
EtchBendLines/ Library for adding etch lines to DXF files (git submodule, uses ACadSharp)
|
||||||
netDxf/ DXF file read/write library
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Key Namespaces
|
### Key Namespaces
|
||||||
|
|
||||||
| Namespace | Purpose |
|
| Namespace | Purpose |
|
||||||
|-----------|---------|
|
|-----------|---------|
|
||||||
| `ExportDXF.Services` | Core services -- SolidWorks connection, DXF export, BOM extraction, PDF export, API client |
|
| `ExportDXF.Services` | Core services -- SolidWorks connection, DXF export, BOM extraction, PDF export, Excel output, logging, filename template parsing, drawing info extraction |
|
||||||
| `ExportDXF.Forms` | WinForms UI -- drawing selection and main export form |
|
| `ExportDXF.Forms` | WinForms UI -- main export form with log, BOM, and cut templates grids |
|
||||||
| `ExportDXF.Models` | Data models -- BomItem, ExportContext, SolidWorksDocument |
|
| `ExportDXF.Models` | Data models -- BomItem, CutTemplate, ExportContext, SolidWorksDocument |
|
||||||
| `ExportDXF.ViewFlipDeciders` | Strategies for determining if a flat pattern view should be flipped |
|
| `ExportDXF.ViewFlipDeciders` | Strategies for determining if a flat pattern view should be flipped |
|
||||||
| `ExportDXF.ItemExtractors` | Extract component items from BOM tables and assemblies |
|
| `ExportDXF.ItemExtractors` | Extract component items from BOM tables and assemblies |
|
||||||
| `ExportDXF.Utilities` | SolidWorks helpers, sheet metal property extraction, text utilities |
|
| `ExportDXF.Utilities` | SolidWorks helpers, content hashing, sheet metal property extraction, text utilities |
|
||||||
|
| `ExportDXF.Extensions` | Extension methods for SolidWorks, UI, unit conversion, strings, TimeSpan |
|
||||||
| `EtchBendLines` | Post-processes DXF files to add etch marks on bend-up lines |
|
| `EtchBendLines` | Post-processes DXF files to add etch marks on bend-up lines |
|
||||||
|
|
||||||
## How It Works
|
## How It Works
|
||||||
|
|
||||||
1. **Startup** -- Connects to a running SolidWorks instance (or launches one). Shows a drawing selection form that pulls equipment/drawing data from the CutFab API and displays the active SolidWorks document.
|
1. **Startup** -- Connects to a running SolidWorks instance (or launches one). Auto-fills the filename template from the active document name using pluggable extractors.
|
||||||
|
|
||||||
2. **Export** -- Based on the active document type:
|
2. **Export** -- Based on the active document type:
|
||||||
- **Drawing**: Extracts BOM items from BOM tables, exports the drawing as PDF, then iterates through each component to generate flat pattern DXFs.
|
- **Drawing**: Copies the raw BOM table to the Excel BOM sheet, exports the drawing as PDF, then generates flat pattern DXFs for each sheet metal component.
|
||||||
- **Assembly**: Extracts components from the assembly tree and generates flat pattern DXFs for each sheet metal part.
|
- **Assembly**: Extracts components from the assembly tree and generates flat pattern DXFs for each sheet metal part.
|
||||||
- **Part**: Generates a single flat pattern DXF for the active part.
|
- **Part**: Generates a single flat pattern DXF for the active part.
|
||||||
|
|
||||||
3. **Post-processing** -- Each exported DXF is run through the EtchBendLines library, which identifies bend-up lines and adds short etch marks at their endpoints on a dedicated `ETCH` layer. Only up bends are etched because SolidWorks automatically flips the flat pattern so the shortest flange is the first bend. The etch marks let the press brake operator verify part orientation right off the laser table without flipping -- the first bend (shortest flange) will always have an etch line.
|
3. **Post-processing** -- Each exported DXF is run through the EtchBendLines library, which identifies bend-up lines and adds short etch marks at their endpoints on a dedicated `ETCH` layer. Only up bends are etched because SolidWorks automatically flips the flat pattern so the shortest flange is the first bend. The etch marks let the press brake operator verify part orientation right off the laser table without flipping.
|
||||||
|
|
||||||
4. **Upload** -- DXF files (zipped), PDFs, and BOM item data are uploaded to the CutFab API along with sheet metal properties. The API auto-links cut templates based on material and thickness.
|
4. **Save** -- DXF and PDF files are saved to a `Templates/` folder next to the source file. An Excel workbook is written with the BOM and Cut Templates sheets. If re-exporting, content hashes are compared against the existing workbook -- unchanged DXFs are skipped, changed DXFs get a revision suffix.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\Projects\4321\
|
||||||
|
├── 4321 A01.SLDDRW
|
||||||
|
└── Templates\
|
||||||
|
├── 4321 A01 PT01.dxf
|
||||||
|
├── 4321 A01 PT02.dxf
|
||||||
|
├── 4321 A01 PT03.dxf
|
||||||
|
├── 4321 A01 PT03 Rev2.dxf (revised, original kept)
|
||||||
|
├── 4321 A01.pdf
|
||||||
|
├── 4321 A01.xlsx
|
||||||
|
└── 4321 A01.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Excel Workbook
|
||||||
|
|
||||||
|
**BOM sheet** -- exact copy of all visible columns and rows from the SolidWorks BOM table (Drawing exports only).
|
||||||
|
|
||||||
|
**Cut Templates sheet**:
|
||||||
|
|
||||||
|
| Item # | File Name | Revision | Thickness | K-Factor | Bend Radius | Content Hash |
|
||||||
|
|--------|-----------|----------|-----------|----------|-------------|--------------|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The API base URL is configured in `App.config`:
|
Settings are in `App.config`:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<appSettings>
|
<appSettings>
|
||||||
<add key="CutFab.ApiBaseUrl" value="http://localhost:7027" />
|
<add key="MaxBendRadius" value="2.0" />
|
||||||
|
<add key="DefaultSuffix" value="PT{item_no:2}" />
|
||||||
</appSettings>
|
</appSettings>
|
||||||
```
|
```
|
||||||
|
|
||||||
## DXF Filename Format
|
### Filename Template Placeholders
|
||||||
|
|
||||||
Exported files follow the pattern: `{EquipmentNo} {DrawingNo} PT{ItemNo}.dxf`
|
| Placeholder | Description | Example |
|
||||||
|
|-------------|-------------|---------|
|
||||||
Example: `5007 A02 PT01.dxf`
|
| `{item_no:N}` | Item number, zero-padded to N digits | `{item_no:2}` → `03` |
|
||||||
|
| `{part_name}` | SolidWorks part name | `Bracket` |
|
||||||
|
| `{config}` | Configuration name | `Default` |
|
||||||
|
| `{material}` | Material name | `AISI 304` |
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user