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
|
||||
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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EtchBendLines", "EtchBendLines\EtchBendLines\EtchBendLines.csproj", "{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "netDxf", "EtchBendLines\netDxf\netDxf\netDxf.csproj", "{785380E0-CEB9-4C34-82E5-60D0E33E848E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{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|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.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.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.Build.0 = Release|Any CPU
|
||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x64.Build.0 = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
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
|
||||
{
|
||||
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 equipmentOnlyRegex = new Regex(@"^(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\b", RegexOptions.IgnoreCase);
|
||||
|
||||
public string EquipmentNo { get; set; }
|
||||
|
||||
@@ -14,6 +15,8 @@ namespace ExportDXF
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (string.IsNullOrEmpty(DrawingNo))
|
||||
return EquipmentNo ?? string.Empty;
|
||||
return $"{EquipmentNo} {DrawingNo}";
|
||||
}
|
||||
|
||||
@@ -35,7 +38,21 @@ namespace ExportDXF
|
||||
var match = drawingFormatRegex.Match(input);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
var dwg = new DrawingInfo();
|
||||
|
||||
@@ -46,4 +63,4 @@ namespace ExportDXF
|
||||
return dwg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="ClosedXML" Version="0.104.2" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
Generated
+82
-76
@@ -36,151 +36,155 @@ namespace ExportDXF.Forms
|
||||
logEventsDataGrid = new System.Windows.Forms.DataGridView();
|
||||
bomTab = new System.Windows.Forms.TabPage();
|
||||
bomDataGrid = new System.Windows.Forms.DataGridView();
|
||||
equipmentBox = new System.Windows.Forms.ComboBox();
|
||||
label1 = new System.Windows.Forms.Label();
|
||||
label2 = new System.Windows.Forms.Label();
|
||||
drawingNoBox = new System.Windows.Forms.ComboBox();
|
||||
cutTemplatesTab = new System.Windows.Forms.TabPage();
|
||||
cutTemplatesDataGrid = new System.Windows.Forms.DataGridView();
|
||||
templateLabel = new System.Windows.Forms.Label();
|
||||
txtFilenameTemplate = new System.Windows.Forms.TextBox();
|
||||
mainTabControl.SuspendLayout();
|
||||
logEventsTab.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).BeginInit();
|
||||
bomTab.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)bomDataGrid).BeginInit();
|
||||
cutTemplatesTab.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)cutTemplatesDataGrid).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
//
|
||||
// runButton
|
||||
//
|
||||
//
|
||||
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.Name = "runButton";
|
||||
runButton.Size = new System.Drawing.Size(100, 30);
|
||||
runButton.Size = new System.Drawing.Size(65, 57);
|
||||
runButton.TabIndex = 11;
|
||||
runButton.Text = "Start";
|
||||
runButton.UseVisualStyleBackColor = true;
|
||||
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.AutoSize = true;
|
||||
label3.Location = new System.Drawing.Point(26, 46);
|
||||
label3.Name = "label3";
|
||||
label3.Size = new System.Drawing.Size(105, 17);
|
||||
label3.TabIndex = 2;
|
||||
label3.Text = "View flip decider";
|
||||
//
|
||||
//
|
||||
// viewFlipDeciderBox
|
||||
//
|
||||
//
|
||||
viewFlipDeciderBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
viewFlipDeciderBox.FormattingEnabled = true;
|
||||
viewFlipDeciderBox.Location = new System.Drawing.Point(137, 43);
|
||||
viewFlipDeciderBox.Name = "viewFlipDeciderBox";
|
||||
viewFlipDeciderBox.Size = new System.Drawing.Size(502, 25);
|
||||
viewFlipDeciderBox.Size = new System.Drawing.Size(365, 25);
|
||||
viewFlipDeciderBox.TabIndex = 3;
|
||||
//
|
||||
//
|
||||
// mainTabControl
|
||||
//
|
||||
//
|
||||
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(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.Padding = new System.Drawing.Point(20, 5);
|
||||
mainTabControl.SelectedIndex = 0;
|
||||
mainTabControl.Size = new System.Drawing.Size(741, 586);
|
||||
mainTabControl.Size = new System.Drawing.Size(910, 522);
|
||||
mainTabControl.TabIndex = 12;
|
||||
//
|
||||
//
|
||||
// logEventsTab
|
||||
//
|
||||
//
|
||||
logEventsTab.Controls.Add(logEventsDataGrid);
|
||||
logEventsTab.Location = new System.Drawing.Point(4, 30);
|
||||
logEventsTab.Name = "logEventsTab";
|
||||
logEventsTab.Padding = new System.Windows.Forms.Padding(3);
|
||||
logEventsTab.Size = new System.Drawing.Size(733, 552);
|
||||
logEventsTab.Size = new System.Drawing.Size(902, 488);
|
||||
logEventsTab.TabIndex = 0;
|
||||
logEventsTab.Text = "Log Events";
|
||||
logEventsTab.UseVisualStyleBackColor = true;
|
||||
//
|
||||
//
|
||||
// logEventsDataGrid
|
||||
//
|
||||
//
|
||||
logEventsDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
logEventsDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
logEventsDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||
logEventsDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||
logEventsDataGrid.Name = "logEventsDataGrid";
|
||||
logEventsDataGrid.Size = new System.Drawing.Size(721, 540);
|
||||
logEventsDataGrid.Size = new System.Drawing.Size(890, 476);
|
||||
logEventsDataGrid.TabIndex = 0;
|
||||
//
|
||||
//
|
||||
// bomTab
|
||||
//
|
||||
//
|
||||
bomTab.Controls.Add(bomDataGrid);
|
||||
bomTab.Location = new System.Drawing.Point(4, 30);
|
||||
bomTab.Location = new System.Drawing.Point(4, 28);
|
||||
bomTab.Name = "bomTab";
|
||||
bomTab.Padding = new System.Windows.Forms.Padding(3);
|
||||
bomTab.Size = new System.Drawing.Size(733, 552);
|
||||
bomTab.Size = new System.Drawing.Size(902, 490);
|
||||
bomTab.TabIndex = 1;
|
||||
bomTab.Text = "Bill Of Materials";
|
||||
bomTab.UseVisualStyleBackColor = true;
|
||||
//
|
||||
//
|
||||
// bomDataGrid
|
||||
//
|
||||
//
|
||||
bomDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
bomDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
bomDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||
bomDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||
bomDataGrid.Name = "bomDataGrid";
|
||||
bomDataGrid.Size = new System.Drawing.Size(721, 540);
|
||||
bomDataGrid.Size = new System.Drawing.Size(890, 478);
|
||||
bomDataGrid.TabIndex = 1;
|
||||
//
|
||||
// equipmentBox
|
||||
//
|
||||
equipmentBox.FormattingEnabled = true;
|
||||
equipmentBox.Location = new System.Drawing.Point(137, 12);
|
||||
equipmentBox.Name = "equipmentBox";
|
||||
equipmentBox.Size = new System.Drawing.Size(166, 25);
|
||||
equipmentBox.TabIndex = 13;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
label1.AutoSize = true;
|
||||
label1.Location = new System.Drawing.Point(61, 15);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new System.Drawing.Size(70, 17);
|
||||
label1.TabIndex = 2;
|
||||
label1.Text = "Equipment";
|
||||
//
|
||||
// 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;
|
||||
//
|
||||
//
|
||||
// cutTemplatesTab
|
||||
//
|
||||
cutTemplatesTab.Controls.Add(cutTemplatesDataGrid);
|
||||
cutTemplatesTab.Location = new System.Drawing.Point(4, 28);
|
||||
cutTemplatesTab.Name = "cutTemplatesTab";
|
||||
cutTemplatesTab.Padding = new System.Windows.Forms.Padding(3);
|
||||
cutTemplatesTab.Size = new System.Drawing.Size(902, 490);
|
||||
cutTemplatesTab.TabIndex = 2;
|
||||
cutTemplatesTab.Text = "Cut Templates";
|
||||
cutTemplatesTab.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// cutTemplatesDataGrid
|
||||
//
|
||||
cutTemplatesDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
cutTemplatesDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
cutTemplatesDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||
cutTemplatesDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||
cutTemplatesDataGrid.Name = "cutTemplatesDataGrid";
|
||||
cutTemplatesDataGrid.Size = new System.Drawing.Size(890, 478);
|
||||
cutTemplatesDataGrid.TabIndex = 2;
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
//
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
ClientSize = new System.Drawing.Size(768, 672);
|
||||
Controls.Add(drawingNoBox);
|
||||
Controls.Add(equipmentBox);
|
||||
ClientSize = new System.Drawing.Size(937, 609);
|
||||
Controls.Add(txtFilenameTemplate);
|
||||
Controls.Add(mainTabControl);
|
||||
Controls.Add(viewFlipDeciderBox);
|
||||
Controls.Add(label2);
|
||||
Controls.Add(label1);
|
||||
Controls.Add(templateLabel);
|
||||
Controls.Add(label3);
|
||||
Controls.Add(runButton);
|
||||
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);
|
||||
MaximizeBox = false;
|
||||
MinimumSize = new System.Drawing.Size(643, 355);
|
||||
MinimumSize = new System.Drawing.Size(642, 455);
|
||||
Name = "MainForm";
|
||||
StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
Text = "ExportDXF";
|
||||
@@ -189,6 +193,8 @@ namespace ExportDXF.Forms
|
||||
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).EndInit();
|
||||
bomTab.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)bomDataGrid).EndInit();
|
||||
cutTemplatesTab.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)cutTemplatesDataGrid).EndInit();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
@@ -203,9 +209,9 @@ namespace ExportDXF.Forms
|
||||
private System.Windows.Forms.TabPage bomTab;
|
||||
private System.Windows.Forms.DataGridView logEventsDataGrid;
|
||||
private System.Windows.Forms.DataGridView bomDataGrid;
|
||||
private System.Windows.Forms.ComboBox equipmentBox;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.ComboBox drawingNoBox;
|
||||
private System.Windows.Forms.TabPage cutTemplatesTab;
|
||||
private System.Windows.Forms.DataGridView cutTemplatesDataGrid;
|
||||
private System.Windows.Forms.Label templateLabel;
|
||||
private System.Windows.Forms.TextBox txtFilenameTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
+99
-102
@@ -1,12 +1,11 @@
|
||||
using ExportDXF.Data;
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.Models;
|
||||
using ExportDXF.Services;
|
||||
using ExportDXF.ViewFlipDeciders;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -18,14 +17,13 @@ namespace ExportDXF.Forms
|
||||
{
|
||||
private readonly ISolidWorksService _solidWorksService;
|
||||
private readonly IDxfExportService _exportService;
|
||||
private readonly IFileExportService _fileExportService;
|
||||
private readonly Func<ExportDxfDbContext> _dbContextFactory;
|
||||
private readonly IDrawingInfoExtractor[] _extractors;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private readonly BindingList<LogEvent> _logEvents;
|
||||
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();
|
||||
_solidWorksService = solidWorksService ??
|
||||
@@ -33,16 +31,15 @@ namespace ExportDXF.Forms
|
||||
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
||||
_exportService = exportService ??
|
||||
throw new ArgumentNullException(nameof(exportService));
|
||||
_fileExportService = fileExportService ??
|
||||
throw new ArgumentNullException(nameof(fileExportService));
|
||||
_dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext());
|
||||
_extractors = extractors ??
|
||||
throw new ArgumentNullException(nameof(extractors));
|
||||
_logEvents = new BindingList<LogEvent>();
|
||||
_bomItems = new BindingList<BomItem>();
|
||||
_allDrawings = new List<DrawingInfo>();
|
||||
_cutTemplates = new BindingList<CutTemplate>();
|
||||
InitializeViewFlipDeciders();
|
||||
InitializeLogEventsGrid();
|
||||
InitializeBomGrid();
|
||||
InitializeDrawingDropdowns();
|
||||
InitializeCutTemplatesGrid();
|
||||
}
|
||||
|
||||
~MainForm()
|
||||
@@ -67,7 +64,6 @@ namespace ExportDXF.Forms
|
||||
LogMessage("Connecting to SolidWorks, this may take a minute...");
|
||||
await _solidWorksService.ConnectAsync();
|
||||
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
||||
LogMessage($"Output folder: {_fileExportService.OutputFolder}");
|
||||
LogMessage("Ready");
|
||||
UpdateActiveDocumentDisplay();
|
||||
runButton.Enabled = true;
|
||||
@@ -89,7 +85,7 @@ namespace ExportDXF.Forms
|
||||
ViewFlipDecider = d
|
||||
})
|
||||
.ToList();
|
||||
// Move "Automatic" to the top if it exists
|
||||
|
||||
var automatic = items.FirstOrDefault(i => i.Name == "Automatic");
|
||||
if (automatic != null)
|
||||
{
|
||||
@@ -102,17 +98,13 @@ namespace ExportDXF.Forms
|
||||
|
||||
private void InitializeLogEventsGrid()
|
||||
{
|
||||
// Clear any existing columns first
|
||||
logEventsDataGrid.Columns.Clear();
|
||||
|
||||
// Configure grid settings
|
||||
logEventsDataGrid.AutoGenerateColumns = false;
|
||||
logEventsDataGrid.AllowUserToAddRows = false;
|
||||
logEventsDataGrid.AllowUserToDeleteRows = false;
|
||||
logEventsDataGrid.ReadOnly = true;
|
||||
logEventsDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
|
||||
// Add columns
|
||||
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(LogEvent.Time),
|
||||
@@ -142,26 +134,19 @@ namespace ExportDXF.Forms
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
|
||||
});
|
||||
|
||||
// Add row coloring based on log level
|
||||
logEventsDataGrid.CellFormatting += LogEventsDataGrid_CellFormatting;
|
||||
|
||||
// Set the data source AFTER adding columns
|
||||
logEventsDataGrid.DataSource = _logEvents;
|
||||
}
|
||||
|
||||
private void InitializeBomGrid()
|
||||
{
|
||||
// Clear any existing columns first
|
||||
bomDataGrid.Columns.Clear();
|
||||
|
||||
// Configure grid settings
|
||||
bomDataGrid.AutoGenerateColumns = false;
|
||||
bomDataGrid.AllowUserToAddRows = false;
|
||||
bomDataGrid.AllowUserToDeleteRows = false;
|
||||
bomDataGrid.ReadOnly = true;
|
||||
bomDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
|
||||
// Add columns
|
||||
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(BomItem.ItemNo),
|
||||
@@ -211,85 +196,68 @@ namespace ExportDXF.Forms
|
||||
Width = 120
|
||||
});
|
||||
|
||||
// Set the data source AFTER adding columns
|
||||
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())
|
||||
{
|
||||
// Get all drawing numbers from the database
|
||||
var drawingNumbers = db.ExportRecords
|
||||
.Select(r => r.DrawingNumber)
|
||||
.Where(d => !string.IsNullOrEmpty(d))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
DataPropertyName = nameof(CutTemplate.CutTemplateName),
|
||||
HeaderText = "Template Name",
|
||||
Width = 150
|
||||
});
|
||||
|
||||
// Parse into DrawingInfo objects
|
||||
_allDrawings = drawingNumbers
|
||||
.Select(DrawingInfo.Parse)
|
||||
.Where(d => d != null)
|
||||
.Distinct()
|
||||
.OrderBy(d => d.EquipmentNo)
|
||||
.ThenBy(d => d.DrawingNo)
|
||||
.ToList();
|
||||
|
||||
// Get distinct equipment numbers
|
||||
var equipmentNumbers = _allDrawings
|
||||
.Select(d => d.EquipmentNo)
|
||||
.Distinct()
|
||||
.OrderBy(e => e)
|
||||
.ToList();
|
||||
|
||||
// Populate equipment dropdown
|
||||
equipmentBox.Items.Clear();
|
||||
equipmentBox.Items.Add(""); // Empty option for "all"
|
||||
foreach (var eq in equipmentNumbers)
|
||||
{
|
||||
equipmentBox.Items.Add(eq);
|
||||
}
|
||||
|
||||
// Populate drawing dropdown with all drawings initially
|
||||
UpdateDrawingDropdown();
|
||||
|
||||
// Wire up event handler for equipment selection change
|
||||
equipmentBox.SelectedIndexChanged += EquipmentBox_SelectedIndexChanged;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
// Database might not exist yet - that's OK
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to load drawings from database: {ex.Message}");
|
||||
}
|
||||
}
|
||||
DataPropertyName = nameof(CutTemplate.DxfFilePath),
|
||||
HeaderText = "DXF File",
|
||||
Width = 250
|
||||
});
|
||||
|
||||
private void EquipmentBox_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateDrawingDropdown();
|
||||
}
|
||||
|
||||
private void UpdateDrawingDropdown()
|
||||
{
|
||||
var selectedEquipment = equipmentBox.SelectedItem?.ToString();
|
||||
|
||||
var filteredDrawings = string.IsNullOrEmpty(selectedEquipment)
|
||||
? _allDrawings
|
||||
: _allDrawings.Where(d => d.EquipmentNo == selectedEquipment).ToList();
|
||||
|
||||
drawingNoBox.Items.Clear();
|
||||
drawingNoBox.Items.Add(""); // Empty option
|
||||
foreach (var drawing in filteredDrawings)
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
drawingNoBox.Items.Add(drawing.DrawingNo);
|
||||
}
|
||||
DataPropertyName = nameof(CutTemplate.Revision),
|
||||
HeaderText = "Rev",
|
||||
Width = 50
|
||||
});
|
||||
|
||||
if (drawingNoBox.Items.Count > 0)
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
drawingNoBox.SelectedIndex = 0;
|
||||
}
|
||||
DataPropertyName = nameof(CutTemplate.Thickness),
|
||||
HeaderText = "Thickness",
|
||||
Width = 80
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutTemplate.KFactor),
|
||||
HeaderText = "K-Factor",
|
||||
Width = 80
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutTemplate.DefaultBendRadius),
|
||||
HeaderText = "Bend Radius",
|
||||
Width = 90
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutTemplate.ContentHash),
|
||||
HeaderText = "Content Hash",
|
||||
Width = 150
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.DataSource = _cutTemplates;
|
||||
}
|
||||
|
||||
private async void button1_Click(object sender, EventArgs e)
|
||||
@@ -308,6 +276,13 @@ namespace ExportDXF.Forms
|
||||
{
|
||||
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();
|
||||
var token = _cancellationTokenSource.Token;
|
||||
UpdateUIForExportStart();
|
||||
@@ -319,29 +294,31 @@ namespace ExportDXF.Forms
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse drawing number from active document title
|
||||
var drawingInfo = DrawingInfo.Parse(activeDoc.Title);
|
||||
var filePrefix = drawingInfo != null ? $"{drawingInfo.EquipmentNo} {drawingInfo.DrawingNo}" : activeDoc.Title;
|
||||
var sourceDir = Path.GetDirectoryName(activeDoc.FilePath);
|
||||
var outputFolder = Path.Combine(sourceDir, "Templates");
|
||||
|
||||
var viewFlipDecider = GetSelectedViewFlipDecider();
|
||||
|
||||
var exportContext = new ExportContext
|
||||
{
|
||||
ActiveDocument = activeDoc,
|
||||
ViewFlipDecider = viewFlipDecider,
|
||||
FilePrefix = filePrefix,
|
||||
EquipmentId = null,
|
||||
FilenameTemplate = template,
|
||||
OutputFolder = outputFolder,
|
||||
CancellationToken = token,
|
||||
ProgressCallback = (msg, level, file) => LogMessage(msg, level, file),
|
||||
BomItemCallback = AddBomItem
|
||||
};
|
||||
|
||||
// Clear previous BOM items
|
||||
_bomItems.Clear();
|
||||
_cutTemplates.Clear();
|
||||
|
||||
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.");
|
||||
}
|
||||
@@ -356,6 +333,7 @@ namespace ExportDXF.Forms
|
||||
}
|
||||
finally
|
||||
{
|
||||
_solidWorksService.SetCommandInProgress(false);
|
||||
UpdateUIForExportComplete();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
@@ -377,12 +355,14 @@ namespace ExportDXF.Forms
|
||||
private void UpdateUIForExportStart()
|
||||
{
|
||||
viewFlipDeciderBox.Enabled = false;
|
||||
txtFilenameTemplate.Enabled = false;
|
||||
runButton.Text = "Stop";
|
||||
}
|
||||
|
||||
private void UpdateUIForExportComplete()
|
||||
{
|
||||
viewFlipDeciderBox.Enabled = true;
|
||||
txtFilenameTemplate.Enabled = true;
|
||||
runButton.Text = "Start";
|
||||
runButton.Enabled = true;
|
||||
}
|
||||
@@ -391,7 +371,7 @@ namespace ExportDXF.Forms
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action(() => OnActiveDocumentChanged(sender, e)));
|
||||
Invoke(new Action(() => UpdateActiveDocumentDisplay()));
|
||||
return;
|
||||
}
|
||||
UpdateActiveDocumentDisplay();
|
||||
@@ -402,6 +382,19 @@ namespace ExportDXF.Forms
|
||||
var activeDoc = _solidWorksService.GetActiveDocument();
|
||||
var docTitle = activeDoc?.Title ?? "No Document Open";
|
||||
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)
|
||||
@@ -433,7 +426,6 @@ namespace ExportDXF.Forms
|
||||
|
||||
_logEvents.Add(logEvent);
|
||||
|
||||
// Auto-scroll to the last row
|
||||
if (logEventsDataGrid.Rows.Count > 0)
|
||||
{
|
||||
logEventsDataGrid.FirstDisplayedScrollingRowIndex = logEventsDataGrid.Rows.Count - 1;
|
||||
@@ -448,6 +440,11 @@ namespace ExportDXF.Forms
|
||||
return;
|
||||
}
|
||||
_bomItems.Add(item);
|
||||
|
||||
if (item.CutTemplate != null)
|
||||
{
|
||||
_cutTemplates.Add(item.CutTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogEventsDataGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace ExportDXF.Models
|
||||
{
|
||||
public class BomItem
|
||||
{
|
||||
public int ID { get; set; }
|
||||
public string ItemNo { get; set; } = "";
|
||||
public string PartNo { get; set; } = "";
|
||||
public int SortOrder { get; set; }
|
||||
@@ -14,29 +11,7 @@ namespace ExportDXF.Models
|
||||
public string PartName { get; set; } = "";
|
||||
public string ConfigurationName { get; set; } = "";
|
||||
public string Material { get; set; } = "";
|
||||
public string CutTemplateName { 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 CutTemplate CutTemplate { get; set; }
|
||||
}
|
||||
|
||||
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 SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
@@ -28,14 +28,14 @@ namespace ExportDXF.Services
|
||||
public IViewFlipDecider ViewFlipDecider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Prefix to prepend to exported filenames.
|
||||
/// Filename template with placeholders (e.g., "4321 A01 PT{item_no:2}").
|
||||
/// </summary>
|
||||
public string FilePrefix { get; set; }
|
||||
public string FilenameTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Selected Equipment ID for API operations (optional).
|
||||
/// Output folder for DXF files and Excel workbook.
|
||||
/// </summary>
|
||||
public int? EquipmentId { get; set; }
|
||||
public string OutputFolder { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cancellation token for canceling the export operation.
|
||||
@@ -67,8 +67,8 @@ namespace ExportDXF.Services
|
||||
get
|
||||
{
|
||||
return Path.Combine(
|
||||
Application.StartupPath,
|
||||
DRAWING_TEMPLATE_FOLDER,
|
||||
Application.StartupPath,
|
||||
DRAWING_TEMPLATE_FOLDER,
|
||||
DRAWING_TEMPLATE_FILE);
|
||||
}
|
||||
}
|
||||
@@ -108,23 +108,17 @@ namespace ExportDXF.Services
|
||||
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
{
|
||||
// Close the document without saving
|
||||
SolidWorksApp.CloseDoc(title);
|
||||
ProgressCallback?.Invoke("Closed template drawing", LogLevel.Info, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the reference regardless of success/failure
|
||||
TemplateDrawing = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ProgressCallback?.Invoke($"Failed to close template drawing: {ex.Message}", LogLevel.Error, null);
|
||||
|
||||
// Still clear the reference to prevent further issues
|
||||
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.
|
||||
/// </summary>
|
||||
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.Services;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ExportDXF
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
@@ -23,34 +19,32 @@ namespace ExportDXF
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple dependency injection container.
|
||||
/// </summary>
|
||||
public class ServiceContainer
|
||||
{
|
||||
private readonly string _outputFolder;
|
||||
|
||||
public ServiceContainer()
|
||||
{
|
||||
_outputFolder = ConfigurationManager.AppSettings["ExportOutputFolder"] ?? @"C:\ExportDXF\Output";
|
||||
}
|
||||
|
||||
public MainForm ResolveMainForm()
|
||||
{
|
||||
var solidWorksService = new SolidWorksService();
|
||||
var bomExtractor = new BomExtractor();
|
||||
var partExporter = new PartExporter();
|
||||
var drawingExporter = new DrawingExporter();
|
||||
var fileExportService = new FileExportService(_outputFolder);
|
||||
var excelExportService = new ExcelExportService();
|
||||
var logFileService = new LogFileService();
|
||||
|
||||
var exportService = new DxfExportService(
|
||||
solidWorksService,
|
||||
bomExtractor,
|
||||
partExporter,
|
||||
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.ItemExtractors;
|
||||
using ExportDXF.Models;
|
||||
using ExportDXF;
|
||||
using ExportDXF.Utilities;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
public interface IDxfExportService
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports the document specified in the context to DXF format.
|
||||
/// </summary>
|
||||
/// <param name="context">The export context containing all necessary information.</param>
|
||||
void Export(ExportContext context);
|
||||
Task ExportAsync(ExportContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for orchestrating the export of SolidWorks documents to DXF format.
|
||||
/// </summary>
|
||||
public class DxfExportService : IDxfExportService
|
||||
{
|
||||
private readonly ISolidWorksService _solidWorksService;
|
||||
private readonly IBomExtractor _bomExtractor;
|
||||
private readonly IPartExporter _partExporter;
|
||||
private readonly IDrawingExporter _drawingExporter;
|
||||
private readonly IFileExportService _fileExportService;
|
||||
private readonly Func<ExportDxfDbContext> _dbContextFactory;
|
||||
private readonly ExcelExportService _excelExportService;
|
||||
private readonly LogFileService _logFileService;
|
||||
|
||||
public DxfExportService(
|
||||
ISolidWorksService solidWorksService,
|
||||
IBomExtractor bomExtractor,
|
||||
IPartExporter partExporter,
|
||||
IDrawingExporter drawingExporter,
|
||||
IFileExportService fileExportService,
|
||||
Func<ExportDxfDbContext> dbContextFactory = null)
|
||||
ExcelExportService excelExportService,
|
||||
LogFileService logFileService)
|
||||
{
|
||||
_solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService));
|
||||
_bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor));
|
||||
_partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter));
|
||||
_drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter));
|
||||
_fileExportService = fileExportService ?? throw new ArgumentNullException(nameof(fileExportService));
|
||||
_dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext());
|
||||
_excelExportService = excelExportService ?? throw new ArgumentNullException(nameof(excelExportService));
|
||||
_logFileService = logFileService ?? throw new ArgumentNullException(nameof(logFileService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports the document specified in the context to DXF format.
|
||||
/// </summary>
|
||||
public void Export(ExportContext context)
|
||||
public async Task ExportAsync(ExportContext context)
|
||||
{
|
||||
if (context == null)
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
@@ -58,7 +49,30 @@ namespace ExportDXF.Services
|
||||
ValidateContext(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 tempDir = CreateTempWorkDir();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -67,35 +81,57 @@ namespace ExportDXF.Services
|
||||
switch (context.ActiveDocument.DocumentType)
|
||||
{
|
||||
case DocumentType.Part:
|
||||
ExportPart(context);
|
||||
await ExportPartAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
|
||||
break;
|
||||
|
||||
case DocumentType.Assembly:
|
||||
ExportAssembly(context);
|
||||
await ExportAssemblyAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
|
||||
break;
|
||||
|
||||
case DocumentType.Drawing:
|
||||
ExportDrawing(context);
|
||||
rawBomTable = await ExportDrawingAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
|
||||
break;
|
||||
|
||||
default:
|
||||
LogProgress(context, "Unknown document type.", LogLevel.Error);
|
||||
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
|
||||
{
|
||||
CleanupExportContext(context);
|
||||
_solidWorksService.EnableUserControl(true);
|
||||
CleanupTempDir(tempDir);
|
||||
|
||||
var duration = DateTime.Now - startTime;
|
||||
_logFileService.LogInfo($"Run time: {duration.ToReadableFormat()}");
|
||||
LogProgress(context, $"Run time: {duration.ToReadableFormat()}");
|
||||
}
|
||||
}
|
||||
|
||||
#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");
|
||||
|
||||
@@ -106,11 +142,25 @@ namespace ExportDXF.Services
|
||||
return;
|
||||
}
|
||||
|
||||
// Export directly to the output folder
|
||||
_partExporter.ExportSinglePart(part, _fileExportService.OutputFolder, context);
|
||||
var item = _partExporter.ExportSinglePart(part, tempDir, 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, "Fetching components...");
|
||||
@@ -132,11 +182,26 @@ namespace ExportDXF.Services
|
||||
|
||||
LogProgress(context, $"Found {items.Count} item(s).");
|
||||
|
||||
// Export directly to the output folder
|
||||
ExportItems(items, _fileExportService.OutputFolder, context);
|
||||
// Assign item numbers
|
||||
int nextNum = 1;
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||
{
|
||||
item.ItemNo = nextNum.ToString();
|
||||
nextNum++;
|
||||
}
|
||||
}
|
||||
|
||||
await ExportItemsAsync(items, tempDir, outputFolder, context, existingTemplates, bomItems);
|
||||
}
|
||||
|
||||
private void ExportDrawing(ExportContext context)
|
||||
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, "Finding BOM tables...");
|
||||
@@ -145,68 +210,35 @@ namespace ExportDXF.Services
|
||||
if (drawing == null)
|
||||
{
|
||||
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);
|
||||
|
||||
if (items == null || items.Count == 0)
|
||||
{
|
||||
LogProgress(context, "Error: Bill of materials not found.", LogLevel.Error);
|
||||
return;
|
||||
return rawBomTable;
|
||||
}
|
||||
|
||||
LogProgress(context, $"Found {items.Count} component(s)");
|
||||
|
||||
// Determine drawing number for file naming
|
||||
var drawingNumber = ParseDrawingNumber(context);
|
||||
// Export drawing to PDF in output folder
|
||||
_drawingExporter.ExportToPdf(drawing, outputFolder, context);
|
||||
|
||||
// Export drawing to PDF
|
||||
var tempDir = CreateTempWorkDir();
|
||||
_drawingExporter.ExportToPdf(drawing, tempDir, context);
|
||||
await ExportItemsAsync(items, tempDir, outputFolder, context, existingTemplates, bomItems);
|
||||
|
||||
// Copy PDF to output folder
|
||||
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);
|
||||
return rawBomTable;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -215,17 +247,12 @@ namespace ExportDXF.Services
|
||||
|
||||
private void SetupExportContext(ExportContext context)
|
||||
{
|
||||
// Set up SolidWorks application reference
|
||||
context.SolidWorksApp = _solidWorksService.GetNativeSldWorks();
|
||||
|
||||
if (context.SolidWorksApp == null)
|
||||
{
|
||||
throw new InvalidOperationException("SolidWorks service is not connected.");
|
||||
}
|
||||
|
||||
// Set up drawing template path
|
||||
context.TemplateDrawing = null;
|
||||
|
||||
LogProgress(context, "Export context initialized");
|
||||
}
|
||||
|
||||
@@ -233,13 +260,11 @@ namespace ExportDXF.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
// Clean up template drawing if it was created
|
||||
context.CleanupTemplateDrawing();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
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
|
||||
};
|
||||
|
||||
return extractor.GetItems();
|
||||
}
|
||||
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 skippedCount = 0;
|
||||
int unchangedCount = 0;
|
||||
int failureCount = 0;
|
||||
int sortOrder = 0;
|
||||
|
||||
@@ -281,79 +313,150 @@ namespace ExportDXF.Services
|
||||
|
||||
try
|
||||
{
|
||||
// PartExporter will handle template drawing creation through context
|
||||
_partExporter.ExportItem(item, saveDirectory, context);
|
||||
_partExporter.ExportItem(item, tempDir, context);
|
||||
|
||||
if (!string.IsNullOrEmpty(item.FileName))
|
||||
var bomItem = CreateBomItem(item);
|
||||
bomItem.SortOrder = sortOrder++;
|
||||
|
||||
if (!string.IsNullOrEmpty(item.LocalTempPath))
|
||||
{
|
||||
successCount++;
|
||||
LogProgress(context, $"Exported: {item.FileName}.dxf", LogLevel.Info);
|
||||
|
||||
// Create BOM item
|
||||
var dxfPath = Path.Combine(saveDirectory, item.FileName + ".dxf");
|
||||
var bomItem = new BomItem
|
||||
{
|
||||
ExportRecordId = exportRecordId ?? 0,
|
||||
ItemNo = item.ItemNo ?? "",
|
||||
PartNo = item.FileName ?? item.PartName ?? "",
|
||||
SortOrder = sortOrder++,
|
||||
Qty = item.Quantity,
|
||||
TotalQty = item.Quantity,
|
||||
Description = item.Description ?? "",
|
||||
PartName = item.PartName ?? "",
|
||||
ConfigurationName = item.Configuration ?? "",
|
||||
Material = item.Material ?? "",
|
||||
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);
|
||||
}
|
||||
}
|
||||
var wasPlaced = PlaceDxfFile(item, context, outputFolder, existingTemplates, bomItem);
|
||||
if (wasPlaced)
|
||||
successCount++;
|
||||
else
|
||||
unchangedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
failureCount++;
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
LogProgress(context, $"Export complete: {successCount} succeeded, {failureCount} failed",
|
||||
failureCount > 0 ? LogLevel.Warning : LogLevel.Info);
|
||||
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";
|
||||
|
||||
if (exportRecordId.HasValue)
|
||||
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))
|
||||
{
|
||||
LogProgress(context, $"BOM items saved to database (ExportRecord ID: {exportRecordId.Value})", LogLevel.Info);
|
||||
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
|
||||
{
|
||||
ItemNo = item.ItemNo ?? "",
|
||||
PartNo = item.FileName ?? item.PartName ?? "",
|
||||
SortOrder = 0,
|
||||
Qty = item.Quantity,
|
||||
TotalQty = item.Quantity,
|
||||
Description = item.Description ?? "",
|
||||
PartName = item.PartName ?? "",
|
||||
ConfigurationName = item.Configuration ?? "",
|
||||
Material = item.Material ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private string GetRevisionFileName(string baseName, int revision)
|
||||
{
|
||||
if (revision <= 1)
|
||||
return $"{baseName}.dxf";
|
||||
return $"{baseName} Rev{revision}.dxf";
|
||||
}
|
||||
|
||||
private string CreateTempWorkDir()
|
||||
{
|
||||
var path = Path.Combine(Path.GetTempPath(), "ExportDXF-" + Guid.NewGuid().ToString("N"));
|
||||
@@ -361,17 +464,17 @@ namespace ExportDXF.Services
|
||||
return path;
|
||||
}
|
||||
|
||||
private string ParseDrawingNumber(ExportContext context)
|
||||
private void CleanupTempDir(string tempDir)
|
||||
{
|
||||
// Prefer prefix (e.g., "5007 A02 PT"), fallback to active document title
|
||||
var candidate = context?.FilePrefix;
|
||||
var info = string.IsNullOrWhiteSpace(candidate) ? null : DrawingInfo.Parse(candidate);
|
||||
if (info == null)
|
||||
try
|
||||
{
|
||||
var title = context?.ActiveDocument?.Title;
|
||||
info = string.IsNullOrWhiteSpace(title) ? null : DrawingInfo.Parse(title);
|
||||
if (Directory.Exists(tempDir))
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort cleanup
|
||||
}
|
||||
return info != null ? ($"{info.EquipmentNo} {info.DrawingNo}") : null;
|
||||
}
|
||||
|
||||
private void ValidateContext(ExportContext context)
|
||||
@@ -381,6 +484,12 @@ namespace ExportDXF.Services
|
||||
|
||||
if (context.ProgressCallback == null)
|
||||
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)
|
||||
|
||||
@@ -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.Utilities;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
@@ -15,24 +15,29 @@ namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports a single part document to DXF.
|
||||
/// Returns an Item with export metadata (filename, hash, sheet metal properties), or null if export failed.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
||||
Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Exports an item (component from BOM or assembly) to DXF.
|
||||
/// </summary>
|
||||
/// <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>
|
||||
void ExportItem(Item item, string saveDirectory, ExportContext context);
|
||||
}
|
||||
|
||||
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)
|
||||
throw new ArgumentNullException(nameof(part));
|
||||
@@ -49,12 +54,52 @@ namespace ExportDXF.Services
|
||||
|
||||
try
|
||||
{
|
||||
var fileName = GetSinglePartFileName(model, context.FilePrefix);
|
||||
var fileName = GetSinglePartFileName(model);
|
||||
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();
|
||||
|
||||
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
|
||||
{
|
||||
@@ -94,7 +139,7 @@ namespace ExportDXF.Services
|
||||
|
||||
EnrichItemWithMetadata(item, model, part);
|
||||
|
||||
var fileName = GetItemFileName(item, context.FilePrefix);
|
||||
var fileName = GetItemFileName(item);
|
||||
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
||||
|
||||
var templateDrawing = context.GetOrCreateTemplateDrawing();
|
||||
@@ -102,6 +147,8 @@ namespace ExportDXF.Services
|
||||
if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, savePath, context))
|
||||
{
|
||||
item.FileName = Path.GetFileNameWithoutExtension(savePath);
|
||||
item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath);
|
||||
item.LocalTempPath = savePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -260,6 +307,7 @@ namespace ExportDXF.Services
|
||||
{
|
||||
var etcher = new EtchBendLines.Etcher();
|
||||
etcher.AddEtchLines(dxfPath);
|
||||
FixDegreeSymbol(dxfPath);
|
||||
}
|
||||
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 config = model.ConfigurationManager.ActiveConfiguration.Name;
|
||||
var isDefaultConfig = string.Equals(config, "default", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var name = isDefaultConfig ? title : $"{title} [{config}]";
|
||||
return prefix + name;
|
||||
return isDefaultConfig ? title : $"{title} [{config}]";
|
||||
}
|
||||
|
||||
private string GetItemFileName(Item item, string prefix)
|
||||
private string GetItemFileName(Item item)
|
||||
{
|
||||
prefix = prefix?.Replace("\"", "''") ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||
{
|
||||
return prefix + item.PartName;
|
||||
}
|
||||
return item.PartName ?? "unknown";
|
||||
|
||||
var num = item.ItemNo.PadLeft(2, '0');
|
||||
// Expected format: {DrawingNo} PT{ItemNo}
|
||||
return string.IsNullOrWhiteSpace(prefix)
|
||||
? $"PT{num}"
|
||||
: $"{prefix} PT{num}";
|
||||
return $"PT{num}";
|
||||
}
|
||||
|
||||
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>
|
||||
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>
|
||||
/// Gets the SolidWorks application instance.
|
||||
/// </summary>
|
||||
@@ -188,6 +195,15 @@ namespace ExportDXF.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCommandInProgress(bool inProgress)
|
||||
{
|
||||
if (_sldWorks != null)
|
||||
{
|
||||
_sldWorks.CommandInProgress = inProgress;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the native SolidWorks application instance.
|
||||
/// 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"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
|
||||
</startup>
|
||||
<appSettings>
|
||||
<add key="MaxBendRadius" value="2.0"/>
|
||||
<add key="ExportOutputFolder" value="C:\ExportDXF\Output"/>
|
||||
<add key="DefaultSuffix" value="PT{item_no:2}"/>
|
||||
</appSettings>
|
||||
<connectionStrings>
|
||||
<add name="ExportDxfDb"
|
||||
connectionString="Server=localhost;Database=ExportDxfDb;Trusted_Connection=True;TrustServerCertificate=True;"
|
||||
providerName="Microsoft.Data.SqlClient"/>
|
||||
</connectionStrings>
|
||||
</configuration>
|
||||
|
||||
@@ -1,79 +1,109 @@
|
||||
# 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.
|
||||
|
||||
## Features
|
||||
|
||||
- **Batch DXF export** from SolidWorks drawings, assemblies, or individual parts
|
||||
- **Flat pattern generation** with automatic sheet metal detection
|
||||
- **Etch line insertion** on bend-up lines for fabrication reference (via EtchBendLines library)
|
||||
- **PDF export** of SolidWorks drawings
|
||||
- **CutFab API integration** -- uploads DXFs, PDFs, and BOM data with sheet metal properties (thickness, K-factor, bend radius, material)
|
||||
- **View flip control** with automatic, manual, and prefer-up strategies
|
||||
- **Drawing selection UI** that connects to SolidWorks and displays the active document
|
||||
- **BOM extraction** from drawing BOM tables or assembly component trees
|
||||
|
||||
## Requirements
|
||||
|
||||
- Windows 10/11
|
||||
- .NET Framework 4.8
|
||||
- SolidWorks (installed and licensed)
|
||||
- CutFab API server (default: `http://localhost:7027`)
|
||||
|
||||
## Solution Structure
|
||||
|
||||
```
|
||||
ExportDXF.sln
|
||||
ExportDXF/ Main WinForms application
|
||||
EtchBendLines/ Library for adding etch lines to DXF files (git submodule)
|
||||
netDxf/ DXF file read/write library
|
||||
```
|
||||
|
||||
### Key Namespaces
|
||||
|
||||
| Namespace | Purpose |
|
||||
|-----------|---------|
|
||||
| `ExportDXF.Services` | Core services -- SolidWorks connection, DXF export, BOM extraction, PDF export, API client |
|
||||
| `ExportDXF.Forms` | WinForms UI -- drawing selection and main export form |
|
||||
| `ExportDXF.Models` | Data models -- BomItem, ExportContext, SolidWorksDocument |
|
||||
| `ExportDXF.ViewFlipDeciders` | Strategies for determining if a flat pattern view should be flipped |
|
||||
| `ExportDXF.ItemExtractors` | Extract component items from BOM tables and assemblies |
|
||||
| `ExportDXF.Utilities` | SolidWorks helpers, sheet metal property extraction, text utilities |
|
||||
| `EtchBendLines` | Post-processes DXF files to add etch marks on bend-up lines |
|
||||
|
||||
## 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.
|
||||
|
||||
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.
|
||||
- **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.
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Configuration
|
||||
|
||||
The API base URL is configured in `App.config`:
|
||||
|
||||
```xml
|
||||
<appSettings>
|
||||
<add key="CutFab.ApiBaseUrl" value="http://localhost:7027" />
|
||||
</appSettings>
|
||||
```
|
||||
|
||||
## DXF Filename Format
|
||||
|
||||
Exported files follow the pattern: `{EquipmentNo} {DrawingNo} PT{ItemNo}.dxf`
|
||||
|
||||
Example: `5007 A02 PT01.dxf`
|
||||
|
||||
## Building
|
||||
|
||||
Open `ExportDXF.sln` in Visual Studio and build. The EtchBendLines submodule must be initialized:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
# 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, and exports everything to a local folder with an Excel workbook for downstream tools like [OpenNest](https://github.com/ajisaacs/OpenNest).
|
||||
|
||||
## Features
|
||||
|
||||
- **Batch DXF export** from SolidWorks drawings, assemblies, or individual parts
|
||||
- **Flat pattern generation** with automatic sheet metal detection
|
||||
- **Etch line insertion** on bend-up lines for fabrication reference (via EtchBendLines/ACadSharp)
|
||||
- **PDF export** of SolidWorks drawings
|
||||
- **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
|
||||
- **Active document tracking** -- connects to SolidWorks and updates the UI when the active document changes
|
||||
- **Per-export and app-level logging** to plain text log files
|
||||
|
||||
## Requirements
|
||||
|
||||
- Windows 10/11
|
||||
- .NET 8.0
|
||||
- SolidWorks (installed and licensed)
|
||||
|
||||
## Solution Structure
|
||||
|
||||
```
|
||||
ExportDXF.sln
|
||||
ExportDXF/ Main WinForms application
|
||||
EtchBendLines/ Library for adding etch lines to DXF files (git submodule, uses ACadSharp)
|
||||
```
|
||||
|
||||
### Key Namespaces
|
||||
|
||||
| Namespace | Purpose |
|
||||
|-----------|---------|
|
||||
| `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 -- main export form with log, BOM, and cut templates grids |
|
||||
| `ExportDXF.Models` | Data models -- BomItem, CutTemplate, ExportContext, SolidWorksDocument |
|
||||
| `ExportDXF.ViewFlipDeciders` | Strategies for determining if a flat pattern view should be flipped |
|
||||
| `ExportDXF.ItemExtractors` | Extract component items from BOM tables and assemblies |
|
||||
| `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 |
|
||||
|
||||
## How It Works
|
||||
|
||||
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:
|
||||
- **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.
|
||||
- **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.
|
||||
|
||||
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
|
||||
|
||||
Settings are in `App.config`:
|
||||
|
||||
```xml
|
||||
<appSettings>
|
||||
<add key="MaxBendRadius" value="2.0" />
|
||||
<add key="DefaultSuffix" value="PT{item_no:2}" />
|
||||
</appSettings>
|
||||
```
|
||||
|
||||
### Filename Template Placeholders
|
||||
|
||||
| Placeholder | Description | Example |
|
||||
|-------------|-------------|---------|
|
||||
| `{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
|
||||
|
||||
Open `ExportDXF.sln` in Visual Studio and build. The EtchBendLines submodule must be initialized:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user