Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 |
@@ -12,21 +12,49 @@ EndProject
|
|||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Debug|x64 = Debug|x64
|
||||||
|
Debug|x86 = Debug|x86
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
|
Release|x64 = Release|x64
|
||||||
|
Release|x86 = Release|x86
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.Build.0 = Release|Any CPU
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x86.Build.0 = Release|Any CPU
|
||||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{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
|
||||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|Any CPU.ActiveCfg = Debug|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}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|x86.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.ActiveCfg = Release|Any CPU
|
||||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|Any CPU.Build.0 = Release|Any CPU
|
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,74 +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 DbSet<CutTemplate> CutTemplates { 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.Property(e => e.PdfContentHash).HasMaxLength(64);
|
|
||||||
|
|
||||||
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.HasOne(e => e.CutTemplate)
|
|
||||||
.WithOne(ct => ct.BomItem)
|
|
||||||
.HasForeignKey<CutTemplate>(ct => ct.BomItemId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity<CutTemplate>(entity =>
|
|
||||||
{
|
|
||||||
entity.HasKey(e => e.Id);
|
|
||||||
entity.Property(e => e.CutTemplateName).HasMaxLength(100);
|
|
||||||
entity.Property(e => e.ContentHash).HasMaxLength(64);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace ExportDXF
|
namespace ExportDXF
|
||||||
{
|
{
|
||||||
public class DrawingInfo
|
public class DrawingInfo
|
||||||
{
|
{
|
||||||
private static Regex drawingFormatRegex = new Regex(@"(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\s?(?<dwgNo>[ABEP]\d+(-?(\d+[A-Z]?))?)", RegexOptions.IgnoreCase);
|
private static Regex drawingFormatRegex = new Regex(@"(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\s?(?<dwgNo>[ABEP]\d+(-?(\d+[A-Z]?))?)", RegexOptions.IgnoreCase);
|
||||||
|
private static Regex equipmentOnlyRegex = new Regex(@"^(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\b", RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
public string EquipmentNo { get; set; }
|
public string EquipmentNo { get; set; }
|
||||||
|
|
||||||
@@ -14,6 +15,8 @@ namespace ExportDXF
|
|||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(DrawingNo))
|
||||||
|
return EquipmentNo ?? string.Empty;
|
||||||
return $"{EquipmentNo} {DrawingNo}";
|
return $"{EquipmentNo} {DrawingNo}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,7 +38,21 @@ namespace ExportDXF
|
|||||||
var match = drawingFormatRegex.Match(input);
|
var match = drawingFormatRegex.Match(input);
|
||||||
|
|
||||||
if (match.Success == false)
|
if (match.Success == false)
|
||||||
|
{
|
||||||
|
// Try matching just the equipment number (e.g. "5028 Prox switch bracket")
|
||||||
|
var eqMatch = equipmentOnlyRegex.Match(input);
|
||||||
|
if (eqMatch.Success)
|
||||||
|
{
|
||||||
|
return new DrawingInfo
|
||||||
|
{
|
||||||
|
EquipmentNo = eqMatch.Groups["equipmentNo"].Value,
|
||||||
|
DrawingNo = null,
|
||||||
|
Source = input
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var dwg = new DrawingInfo();
|
var dwg = new DrawingInfo();
|
||||||
|
|
||||||
@@ -46,4 +63,4 @@ namespace ExportDXF
|
|||||||
return dwg;
|
return dwg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
<PackageReference Include="ClosedXML" Version="0.104.2" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
|
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
Generated
+52
-76
@@ -38,10 +38,8 @@ namespace ExportDXF.Forms
|
|||||||
bomDataGrid = new System.Windows.Forms.DataGridView();
|
bomDataGrid = new System.Windows.Forms.DataGridView();
|
||||||
cutTemplatesTab = new System.Windows.Forms.TabPage();
|
cutTemplatesTab = new System.Windows.Forms.TabPage();
|
||||||
cutTemplatesDataGrid = new System.Windows.Forms.DataGridView();
|
cutTemplatesDataGrid = new System.Windows.Forms.DataGridView();
|
||||||
equipmentBox = new System.Windows.Forms.ComboBox();
|
templateLabel = new System.Windows.Forms.Label();
|
||||||
label1 = new System.Windows.Forms.Label();
|
txtFilenameTemplate = new System.Windows.Forms.TextBox();
|
||||||
label2 = new System.Windows.Forms.Label();
|
|
||||||
drawingNoBox = new System.Windows.Forms.ComboBox();
|
|
||||||
mainTabControl.SuspendLayout();
|
mainTabControl.SuspendLayout();
|
||||||
logEventsTab.SuspendLayout();
|
logEventsTab.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).BeginInit();
|
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).BeginInit();
|
||||||
@@ -50,99 +48,115 @@ namespace ExportDXF.Forms
|
|||||||
cutTemplatesTab.SuspendLayout();
|
cutTemplatesTab.SuspendLayout();
|
||||||
((System.ComponentModel.ISupportInitialize)cutTemplatesDataGrid).BeginInit();
|
((System.ComponentModel.ISupportInitialize)cutTemplatesDataGrid).BeginInit();
|
||||||
SuspendLayout();
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// runButton
|
// runButton
|
||||||
//
|
//
|
||||||
runButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
|
runButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
|
||||||
runButton.Location = new System.Drawing.Point(514, 13);
|
runButton.Location = new System.Drawing.Point(508, 12);
|
||||||
runButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
runButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||||
runButton.Name = "runButton";
|
runButton.Name = "runButton";
|
||||||
runButton.Size = new System.Drawing.Size(100, 30);
|
runButton.Size = new System.Drawing.Size(65, 57);
|
||||||
runButton.TabIndex = 11;
|
runButton.TabIndex = 11;
|
||||||
runButton.Text = "Start";
|
runButton.Text = "Start";
|
||||||
runButton.UseVisualStyleBackColor = true;
|
runButton.UseVisualStyleBackColor = true;
|
||||||
runButton.Click += button1_Click;
|
runButton.Click += button1_Click;
|
||||||
//
|
//
|
||||||
|
// templateLabel
|
||||||
|
//
|
||||||
|
templateLabel.AutoSize = true;
|
||||||
|
templateLabel.Location = new System.Drawing.Point(15, 15);
|
||||||
|
templateLabel.Name = "templateLabel";
|
||||||
|
templateLabel.Size = new System.Drawing.Size(116, 17);
|
||||||
|
templateLabel.TabIndex = 2;
|
||||||
|
templateLabel.Text = "Filename Template";
|
||||||
|
//
|
||||||
|
// txtFilenameTemplate
|
||||||
|
//
|
||||||
|
txtFilenameTemplate.Location = new System.Drawing.Point(137, 12);
|
||||||
|
txtFilenameTemplate.Name = "txtFilenameTemplate";
|
||||||
|
txtFilenameTemplate.Size = new System.Drawing.Size(365, 25);
|
||||||
|
txtFilenameTemplate.TabIndex = 1;
|
||||||
|
//
|
||||||
// label3
|
// label3
|
||||||
//
|
//
|
||||||
label3.AutoSize = true;
|
label3.AutoSize = true;
|
||||||
label3.Location = new System.Drawing.Point(26, 46);
|
label3.Location = new System.Drawing.Point(26, 46);
|
||||||
label3.Name = "label3";
|
label3.Name = "label3";
|
||||||
label3.Size = new System.Drawing.Size(105, 17);
|
label3.Size = new System.Drawing.Size(105, 17);
|
||||||
label3.TabIndex = 2;
|
label3.TabIndex = 2;
|
||||||
label3.Text = "View flip decider";
|
label3.Text = "View flip decider";
|
||||||
//
|
//
|
||||||
// viewFlipDeciderBox
|
// viewFlipDeciderBox
|
||||||
//
|
//
|
||||||
viewFlipDeciderBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
viewFlipDeciderBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||||
viewFlipDeciderBox.FormattingEnabled = true;
|
viewFlipDeciderBox.FormattingEnabled = true;
|
||||||
viewFlipDeciderBox.Location = new System.Drawing.Point(137, 43);
|
viewFlipDeciderBox.Location = new System.Drawing.Point(137, 43);
|
||||||
viewFlipDeciderBox.Name = "viewFlipDeciderBox";
|
viewFlipDeciderBox.Name = "viewFlipDeciderBox";
|
||||||
viewFlipDeciderBox.Size = new System.Drawing.Size(365, 25);
|
viewFlipDeciderBox.Size = new System.Drawing.Size(365, 25);
|
||||||
viewFlipDeciderBox.TabIndex = 3;
|
viewFlipDeciderBox.TabIndex = 3;
|
||||||
//
|
//
|
||||||
// mainTabControl
|
// mainTabControl
|
||||||
//
|
//
|
||||||
mainTabControl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
mainTabControl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||||
mainTabControl.Controls.Add(logEventsTab);
|
mainTabControl.Controls.Add(logEventsTab);
|
||||||
mainTabControl.Controls.Add(bomTab);
|
mainTabControl.Controls.Add(bomTab);
|
||||||
mainTabControl.Controls.Add(cutTemplatesTab);
|
mainTabControl.Controls.Add(cutTemplatesTab);
|
||||||
mainTabControl.Location = new System.Drawing.Point(15, 74);
|
mainTabControl.Location = new System.Drawing.Point(15, 75);
|
||||||
mainTabControl.Name = "mainTabControl";
|
mainTabControl.Name = "mainTabControl";
|
||||||
mainTabControl.Padding = new System.Drawing.Point(20, 5);
|
mainTabControl.Padding = new System.Drawing.Point(20, 5);
|
||||||
mainTabControl.SelectedIndex = 0;
|
mainTabControl.SelectedIndex = 0;
|
||||||
mainTabControl.Size = new System.Drawing.Size(599, 330);
|
mainTabControl.Size = new System.Drawing.Size(910, 522);
|
||||||
mainTabControl.TabIndex = 12;
|
mainTabControl.TabIndex = 12;
|
||||||
//
|
//
|
||||||
// logEventsTab
|
// logEventsTab
|
||||||
//
|
//
|
||||||
logEventsTab.Controls.Add(logEventsDataGrid);
|
logEventsTab.Controls.Add(logEventsDataGrid);
|
||||||
logEventsTab.Location = new System.Drawing.Point(4, 30);
|
logEventsTab.Location = new System.Drawing.Point(4, 30);
|
||||||
logEventsTab.Name = "logEventsTab";
|
logEventsTab.Name = "logEventsTab";
|
||||||
logEventsTab.Padding = new System.Windows.Forms.Padding(3);
|
logEventsTab.Padding = new System.Windows.Forms.Padding(3);
|
||||||
logEventsTab.Size = new System.Drawing.Size(591, 296);
|
logEventsTab.Size = new System.Drawing.Size(902, 488);
|
||||||
logEventsTab.TabIndex = 0;
|
logEventsTab.TabIndex = 0;
|
||||||
logEventsTab.Text = "Log Events";
|
logEventsTab.Text = "Log Events";
|
||||||
logEventsTab.UseVisualStyleBackColor = true;
|
logEventsTab.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
// logEventsDataGrid
|
// logEventsDataGrid
|
||||||
//
|
//
|
||||||
logEventsDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
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.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||||
logEventsDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
logEventsDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||||
logEventsDataGrid.Location = new System.Drawing.Point(6, 6);
|
logEventsDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||||
logEventsDataGrid.Name = "logEventsDataGrid";
|
logEventsDataGrid.Name = "logEventsDataGrid";
|
||||||
logEventsDataGrid.Size = new System.Drawing.Size(579, 282);
|
logEventsDataGrid.Size = new System.Drawing.Size(890, 476);
|
||||||
logEventsDataGrid.TabIndex = 0;
|
logEventsDataGrid.TabIndex = 0;
|
||||||
//
|
//
|
||||||
// bomTab
|
// bomTab
|
||||||
//
|
//
|
||||||
bomTab.Controls.Add(bomDataGrid);
|
bomTab.Controls.Add(bomDataGrid);
|
||||||
bomTab.Location = new System.Drawing.Point(4, 30);
|
bomTab.Location = new System.Drawing.Point(4, 28);
|
||||||
bomTab.Name = "bomTab";
|
bomTab.Name = "bomTab";
|
||||||
bomTab.Padding = new System.Windows.Forms.Padding(3);
|
bomTab.Padding = new System.Windows.Forms.Padding(3);
|
||||||
bomTab.Size = new System.Drawing.Size(982, 549);
|
bomTab.Size = new System.Drawing.Size(902, 490);
|
||||||
bomTab.TabIndex = 1;
|
bomTab.TabIndex = 1;
|
||||||
bomTab.Text = "Bill Of Materials";
|
bomTab.Text = "Bill Of Materials";
|
||||||
bomTab.UseVisualStyleBackColor = true;
|
bomTab.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
// bomDataGrid
|
// bomDataGrid
|
||||||
//
|
//
|
||||||
bomDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
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.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||||
bomDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
bomDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||||
bomDataGrid.Location = new System.Drawing.Point(6, 6);
|
bomDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||||
bomDataGrid.Name = "bomDataGrid";
|
bomDataGrid.Name = "bomDataGrid";
|
||||||
bomDataGrid.Size = new System.Drawing.Size(970, 535);
|
bomDataGrid.Size = new System.Drawing.Size(890, 478);
|
||||||
bomDataGrid.TabIndex = 1;
|
bomDataGrid.TabIndex = 1;
|
||||||
//
|
//
|
||||||
// cutTemplatesTab
|
// cutTemplatesTab
|
||||||
//
|
//
|
||||||
cutTemplatesTab.Controls.Add(cutTemplatesDataGrid);
|
cutTemplatesTab.Controls.Add(cutTemplatesDataGrid);
|
||||||
cutTemplatesTab.Location = new System.Drawing.Point(4, 30);
|
cutTemplatesTab.Location = new System.Drawing.Point(4, 28);
|
||||||
cutTemplatesTab.Name = "cutTemplatesTab";
|
cutTemplatesTab.Name = "cutTemplatesTab";
|
||||||
cutTemplatesTab.Padding = new System.Windows.Forms.Padding(3);
|
cutTemplatesTab.Padding = new System.Windows.Forms.Padding(3);
|
||||||
cutTemplatesTab.Size = new System.Drawing.Size(982, 549);
|
cutTemplatesTab.Size = new System.Drawing.Size(902, 490);
|
||||||
cutTemplatesTab.TabIndex = 2;
|
cutTemplatesTab.TabIndex = 2;
|
||||||
cutTemplatesTab.Text = "Cut Templates";
|
cutTemplatesTab.Text = "Cut Templates";
|
||||||
cutTemplatesTab.UseVisualStyleBackColor = true;
|
cutTemplatesTab.UseVisualStyleBackColor = true;
|
||||||
@@ -154,53 +168,17 @@ namespace ExportDXF.Forms
|
|||||||
cutTemplatesDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
cutTemplatesDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||||
cutTemplatesDataGrid.Location = new System.Drawing.Point(6, 6);
|
cutTemplatesDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||||
cutTemplatesDataGrid.Name = "cutTemplatesDataGrid";
|
cutTemplatesDataGrid.Name = "cutTemplatesDataGrid";
|
||||||
cutTemplatesDataGrid.Size = new System.Drawing.Size(970, 535);
|
cutTemplatesDataGrid.Size = new System.Drawing.Size(890, 478);
|
||||||
cutTemplatesDataGrid.TabIndex = 2;
|
cutTemplatesDataGrid.TabIndex = 2;
|
||||||
//
|
//
|
||||||
// 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(321, 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(383, 12);
|
|
||||||
drawingNoBox.Name = "drawingNoBox";
|
|
||||||
drawingNoBox.Size = new System.Drawing.Size(119, 25);
|
|
||||||
drawingNoBox.TabIndex = 13;
|
|
||||||
//
|
|
||||||
// MainForm
|
// MainForm
|
||||||
//
|
//
|
||||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||||
ClientSize = new System.Drawing.Size(626, 416);
|
ClientSize = new System.Drawing.Size(937, 609);
|
||||||
Controls.Add(drawingNoBox);
|
Controls.Add(txtFilenameTemplate);
|
||||||
Controls.Add(equipmentBox);
|
|
||||||
Controls.Add(mainTabControl);
|
Controls.Add(mainTabControl);
|
||||||
Controls.Add(viewFlipDeciderBox);
|
Controls.Add(viewFlipDeciderBox);
|
||||||
Controls.Add(label2);
|
Controls.Add(templateLabel);
|
||||||
Controls.Add(label1);
|
|
||||||
Controls.Add(label3);
|
Controls.Add(label3);
|
||||||
Controls.Add(runButton);
|
Controls.Add(runButton);
|
||||||
Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
||||||
@@ -233,9 +211,7 @@ namespace ExportDXF.Forms
|
|||||||
private System.Windows.Forms.DataGridView bomDataGrid;
|
private System.Windows.Forms.DataGridView bomDataGrid;
|
||||||
private System.Windows.Forms.TabPage cutTemplatesTab;
|
private System.Windows.Forms.TabPage cutTemplatesTab;
|
||||||
private System.Windows.Forms.DataGridView cutTemplatesDataGrid;
|
private System.Windows.Forms.DataGridView cutTemplatesDataGrid;
|
||||||
private System.Windows.Forms.ComboBox equipmentBox;
|
private System.Windows.Forms.Label templateLabel;
|
||||||
private System.Windows.Forms.Label label1;
|
private System.Windows.Forms.TextBox txtFilenameTemplate;
|
||||||
private System.Windows.Forms.Label label2;
|
|
||||||
private System.Windows.Forms.ComboBox drawingNoBox;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+39
-128
@@ -1,12 +1,11 @@
|
|||||||
using ExportDXF.Data;
|
|
||||||
using ExportDXF.Extensions;
|
using ExportDXF.Extensions;
|
||||||
using ExportDXF.Models;
|
using ExportDXF.Models;
|
||||||
using ExportDXF.Services;
|
using ExportDXF.Services;
|
||||||
using ExportDXF.ViewFlipDeciders;
|
using ExportDXF.ViewFlipDeciders;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -18,15 +17,13 @@ namespace ExportDXF.Forms
|
|||||||
{
|
{
|
||||||
private readonly ISolidWorksService _solidWorksService;
|
private readonly ISolidWorksService _solidWorksService;
|
||||||
private readonly IDxfExportService _exportService;
|
private readonly IDxfExportService _exportService;
|
||||||
private readonly IFileExportService _fileExportService;
|
private readonly IDrawingInfoExtractor[] _extractors;
|
||||||
private readonly Func<ExportDxfDbContext> _dbContextFactory;
|
|
||||||
private CancellationTokenSource _cancellationTokenSource;
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
private readonly BindingList<LogEvent> _logEvents;
|
private readonly BindingList<LogEvent> _logEvents;
|
||||||
private readonly BindingList<BomItem> _bomItems;
|
private readonly BindingList<BomItem> _bomItems;
|
||||||
private readonly BindingList<CutTemplate> _cutTemplates;
|
private readonly BindingList<CutTemplate> _cutTemplates;
|
||||||
private List<DrawingInfo> _allDrawings;
|
|
||||||
|
|
||||||
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IFileExportService fileExportService, Func<ExportDxfDbContext> dbContextFactory = null)
|
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IDrawingInfoExtractor[] extractors)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_solidWorksService = solidWorksService ??
|
_solidWorksService = solidWorksService ??
|
||||||
@@ -34,18 +31,15 @@ namespace ExportDXF.Forms
|
|||||||
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
||||||
_exportService = exportService ??
|
_exportService = exportService ??
|
||||||
throw new ArgumentNullException(nameof(exportService));
|
throw new ArgumentNullException(nameof(exportService));
|
||||||
_fileExportService = fileExportService ??
|
_extractors = extractors ??
|
||||||
throw new ArgumentNullException(nameof(fileExportService));
|
throw new ArgumentNullException(nameof(extractors));
|
||||||
_dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext());
|
|
||||||
_logEvents = new BindingList<LogEvent>();
|
_logEvents = new BindingList<LogEvent>();
|
||||||
_bomItems = new BindingList<BomItem>();
|
_bomItems = new BindingList<BomItem>();
|
||||||
_cutTemplates = new BindingList<CutTemplate>();
|
_cutTemplates = new BindingList<CutTemplate>();
|
||||||
_allDrawings = new List<DrawingInfo>();
|
|
||||||
InitializeViewFlipDeciders();
|
InitializeViewFlipDeciders();
|
||||||
InitializeLogEventsGrid();
|
InitializeLogEventsGrid();
|
||||||
InitializeBomGrid();
|
InitializeBomGrid();
|
||||||
InitializeCutTemplatesGrid();
|
InitializeCutTemplatesGrid();
|
||||||
InitializeDrawingDropdowns();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~MainForm()
|
~MainForm()
|
||||||
@@ -70,7 +64,6 @@ namespace ExportDXF.Forms
|
|||||||
LogMessage("Connecting to SolidWorks, this may take a minute...");
|
LogMessage("Connecting to SolidWorks, this may take a minute...");
|
||||||
await _solidWorksService.ConnectAsync();
|
await _solidWorksService.ConnectAsync();
|
||||||
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
||||||
LogMessage($"Output folder: {_fileExportService.OutputFolder}");
|
|
||||||
LogMessage("Ready");
|
LogMessage("Ready");
|
||||||
UpdateActiveDocumentDisplay();
|
UpdateActiveDocumentDisplay();
|
||||||
runButton.Enabled = true;
|
runButton.Enabled = true;
|
||||||
@@ -92,7 +85,7 @@ namespace ExportDXF.Forms
|
|||||||
ViewFlipDecider = d
|
ViewFlipDecider = d
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
// Move "Automatic" to the top if it exists
|
|
||||||
var automatic = items.FirstOrDefault(i => i.Name == "Automatic");
|
var automatic = items.FirstOrDefault(i => i.Name == "Automatic");
|
||||||
if (automatic != null)
|
if (automatic != null)
|
||||||
{
|
{
|
||||||
@@ -105,17 +98,13 @@ namespace ExportDXF.Forms
|
|||||||
|
|
||||||
private void InitializeLogEventsGrid()
|
private void InitializeLogEventsGrid()
|
||||||
{
|
{
|
||||||
// Clear any existing columns first
|
|
||||||
logEventsDataGrid.Columns.Clear();
|
logEventsDataGrid.Columns.Clear();
|
||||||
|
|
||||||
// Configure grid settings
|
|
||||||
logEventsDataGrid.AutoGenerateColumns = false;
|
logEventsDataGrid.AutoGenerateColumns = false;
|
||||||
logEventsDataGrid.AllowUserToAddRows = false;
|
logEventsDataGrid.AllowUserToAddRows = false;
|
||||||
logEventsDataGrid.AllowUserToDeleteRows = false;
|
logEventsDataGrid.AllowUserToDeleteRows = false;
|
||||||
logEventsDataGrid.ReadOnly = true;
|
logEventsDataGrid.ReadOnly = true;
|
||||||
logEventsDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
logEventsDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||||
|
|
||||||
// Add columns
|
|
||||||
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
{
|
{
|
||||||
DataPropertyName = nameof(LogEvent.Time),
|
DataPropertyName = nameof(LogEvent.Time),
|
||||||
@@ -145,26 +134,19 @@ namespace ExportDXF.Forms
|
|||||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
|
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add row coloring based on log level
|
|
||||||
logEventsDataGrid.CellFormatting += LogEventsDataGrid_CellFormatting;
|
logEventsDataGrid.CellFormatting += LogEventsDataGrid_CellFormatting;
|
||||||
|
|
||||||
// Set the data source AFTER adding columns
|
|
||||||
logEventsDataGrid.DataSource = _logEvents;
|
logEventsDataGrid.DataSource = _logEvents;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeBomGrid()
|
private void InitializeBomGrid()
|
||||||
{
|
{
|
||||||
// Clear any existing columns first
|
|
||||||
bomDataGrid.Columns.Clear();
|
bomDataGrid.Columns.Clear();
|
||||||
|
|
||||||
// Configure grid settings
|
|
||||||
bomDataGrid.AutoGenerateColumns = false;
|
bomDataGrid.AutoGenerateColumns = false;
|
||||||
bomDataGrid.AllowUserToAddRows = false;
|
bomDataGrid.AllowUserToAddRows = false;
|
||||||
bomDataGrid.AllowUserToDeleteRows = false;
|
bomDataGrid.AllowUserToDeleteRows = false;
|
||||||
bomDataGrid.ReadOnly = true;
|
bomDataGrid.ReadOnly = true;
|
||||||
bomDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
bomDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||||
|
|
||||||
// Add columns
|
|
||||||
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
{
|
{
|
||||||
DataPropertyName = nameof(BomItem.ItemNo),
|
DataPropertyName = nameof(BomItem.ItemNo),
|
||||||
@@ -214,14 +196,12 @@ namespace ExportDXF.Forms
|
|||||||
Width = 120
|
Width = 120
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set the data source AFTER adding columns
|
|
||||||
bomDataGrid.DataSource = _bomItems;
|
bomDataGrid.DataSource = _bomItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeCutTemplatesGrid()
|
private void InitializeCutTemplatesGrid()
|
||||||
{
|
{
|
||||||
cutTemplatesDataGrid.Columns.Clear();
|
cutTemplatesDataGrid.Columns.Clear();
|
||||||
|
|
||||||
cutTemplatesDataGrid.AutoGenerateColumns = false;
|
cutTemplatesDataGrid.AutoGenerateColumns = false;
|
||||||
cutTemplatesDataGrid.AllowUserToAddRows = false;
|
cutTemplatesDataGrid.AllowUserToAddRows = false;
|
||||||
cutTemplatesDataGrid.AllowUserToDeleteRows = false;
|
cutTemplatesDataGrid.AllowUserToDeleteRows = false;
|
||||||
@@ -242,6 +222,13 @@ namespace ExportDXF.Forms
|
|||||||
Width = 250
|
Width = 250
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
|
{
|
||||||
|
DataPropertyName = nameof(CutTemplate.Revision),
|
||||||
|
HeaderText = "Rev",
|
||||||
|
Width = 50
|
||||||
|
});
|
||||||
|
|
||||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||||
{
|
{
|
||||||
DataPropertyName = nameof(CutTemplate.Thickness),
|
DataPropertyName = nameof(CutTemplate.Thickness),
|
||||||
@@ -273,83 +260,6 @@ namespace ExportDXF.Forms
|
|||||||
cutTemplatesDataGrid.DataSource = _cutTemplates;
|
cutTemplatesDataGrid.DataSource = _cutTemplates;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeDrawingDropdowns()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
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();
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
// Database might not exist yet - that's OK
|
|
||||||
System.Diagnostics.Debug.WriteLine($"Failed to load drawings from database: {ex.Message}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
drawingNoBox.Items.Add(drawing.DrawingNo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (drawingNoBox.Items.Count > 0)
|
|
||||||
{
|
|
||||||
drawingNoBox.SelectedIndex = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void button1_Click(object sender, EventArgs e)
|
private async void button1_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (_cancellationTokenSource != null)
|
if (_cancellationTokenSource != null)
|
||||||
@@ -366,6 +276,13 @@ namespace ExportDXF.Forms
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var template = txtFilenameTemplate.Text.Trim();
|
||||||
|
if (!FilenameTemplateParser.Validate(template, out var error))
|
||||||
|
{
|
||||||
|
MessageBox.Show(error, "Invalid Template", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
var token = _cancellationTokenSource.Token;
|
var token = _cancellationTokenSource.Token;
|
||||||
UpdateUIForExportStart();
|
UpdateUIForExportStart();
|
||||||
@@ -377,35 +294,31 @@ namespace ExportDXF.Forms
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use equipment/drawing values from the UI dropdowns
|
var sourceDir = Path.GetDirectoryName(activeDoc.FilePath);
|
||||||
var equipment = equipmentBox.Text?.Trim();
|
var outputFolder = Path.Combine(sourceDir, "Templates");
|
||||||
var drawingNo = drawingNoBox.Text?.Trim();
|
|
||||||
var filePrefix = !string.IsNullOrEmpty(equipment) && !string.IsNullOrEmpty(drawingNo)
|
|
||||||
? $"{equipment} {drawingNo}"
|
|
||||||
: activeDoc.Title;
|
|
||||||
|
|
||||||
var viewFlipDecider = GetSelectedViewFlipDecider();
|
var viewFlipDecider = GetSelectedViewFlipDecider();
|
||||||
|
|
||||||
var exportContext = new ExportContext
|
var exportContext = new ExportContext
|
||||||
{
|
{
|
||||||
ActiveDocument = activeDoc,
|
ActiveDocument = activeDoc,
|
||||||
ViewFlipDecider = viewFlipDecider,
|
ViewFlipDecider = viewFlipDecider,
|
||||||
FilePrefix = filePrefix,
|
FilenameTemplate = template,
|
||||||
EquipmentId = null,
|
OutputFolder = outputFolder,
|
||||||
CancellationToken = token,
|
CancellationToken = token,
|
||||||
ProgressCallback = (msg, level, file) => LogMessage(msg, level, file),
|
ProgressCallback = (msg, level, file) => LogMessage(msg, level, file),
|
||||||
BomItemCallback = AddBomItem
|
BomItemCallback = AddBomItem
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clear previous BOM items and cut templates
|
|
||||||
_bomItems.Clear();
|
_bomItems.Clear();
|
||||||
_cutTemplates.Clear();
|
_cutTemplates.Clear();
|
||||||
|
|
||||||
LogMessage($"Started at {DateTime.Now:t}");
|
LogMessage($"Started at {DateTime.Now:t}");
|
||||||
LogMessage($"Exporting to: {_fileExportService.OutputFolder}");
|
LogMessage($"Output: {outputFolder}");
|
||||||
|
|
||||||
_solidWorksService.SetCommandInProgress(true);
|
_solidWorksService.SetCommandInProgress(true);
|
||||||
|
|
||||||
await Task.Run(() => _exportService.Export(exportContext), token);
|
await Task.Run(async () => await _exportService.ExportAsync(exportContext), token);
|
||||||
|
|
||||||
LogMessage("Done.");
|
LogMessage("Done.");
|
||||||
}
|
}
|
||||||
@@ -442,12 +355,14 @@ namespace ExportDXF.Forms
|
|||||||
private void UpdateUIForExportStart()
|
private void UpdateUIForExportStart()
|
||||||
{
|
{
|
||||||
viewFlipDeciderBox.Enabled = false;
|
viewFlipDeciderBox.Enabled = false;
|
||||||
|
txtFilenameTemplate.Enabled = false;
|
||||||
runButton.Text = "Stop";
|
runButton.Text = "Stop";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateUIForExportComplete()
|
private void UpdateUIForExportComplete()
|
||||||
{
|
{
|
||||||
viewFlipDeciderBox.Enabled = true;
|
viewFlipDeciderBox.Enabled = true;
|
||||||
|
txtFilenameTemplate.Enabled = true;
|
||||||
runButton.Text = "Start";
|
runButton.Text = "Start";
|
||||||
runButton.Enabled = true;
|
runButton.Enabled = true;
|
||||||
}
|
}
|
||||||
@@ -456,7 +371,7 @@ namespace ExportDXF.Forms
|
|||||||
{
|
{
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
{
|
{
|
||||||
Invoke(new Action(() => OnActiveDocumentChanged(sender, e)));
|
Invoke(new Action(() => UpdateActiveDocumentDisplay()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
UpdateActiveDocumentDisplay();
|
UpdateActiveDocumentDisplay();
|
||||||
@@ -468,19 +383,16 @@ namespace ExportDXF.Forms
|
|||||||
var docTitle = activeDoc?.Title ?? "No Document Open";
|
var docTitle = activeDoc?.Title ?? "No Document Open";
|
||||||
this.Text = $"ExportDXF - {docTitle}";
|
this.Text = $"ExportDXF - {docTitle}";
|
||||||
|
|
||||||
// Parse the file name and fill Equipment/Drawing dropdowns
|
if (activeDoc == null)
|
||||||
if (activeDoc != null)
|
return;
|
||||||
{
|
|
||||||
var drawingInfo = DrawingInfo.Parse(activeDoc.Title);
|
|
||||||
if (drawingInfo != null)
|
|
||||||
{
|
|
||||||
if (!equipmentBox.Items.Contains(drawingInfo.EquipmentNo))
|
|
||||||
equipmentBox.Items.Add(drawingInfo.EquipmentNo);
|
|
||||||
equipmentBox.Text = drawingInfo.EquipmentNo;
|
|
||||||
|
|
||||||
if (!drawingNoBox.Items.Contains(drawingInfo.DrawingNo))
|
// Try each extractor to auto-fill the template
|
||||||
drawingNoBox.Items.Add(drawingInfo.DrawingNo);
|
foreach (var extractor in _extractors)
|
||||||
drawingNoBox.Text = drawingInfo.DrawingNo;
|
{
|
||||||
|
if (extractor.TryExtract(activeDoc.Title, out var info))
|
||||||
|
{
|
||||||
|
txtFilenameTemplate.Text = info.DefaultTemplate;
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -514,7 +426,6 @@ namespace ExportDXF.Forms
|
|||||||
|
|
||||||
_logEvents.Add(logEvent);
|
_logEvents.Add(logEvent);
|
||||||
|
|
||||||
// Auto-scroll to the last row
|
|
||||||
if (logEventsDataGrid.Rows.Count > 0)
|
if (logEventsDataGrid.Rows.Count > 0)
|
||||||
{
|
{
|
||||||
logEventsDataGrid.FirstDisplayedScrollingRowIndex = logEventsDataGrid.Rows.Count - 1;
|
logEventsDataGrid.FirstDisplayedScrollingRowIndex = logEventsDataGrid.Rows.Count - 1;
|
||||||
|
|||||||
@@ -1,188 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using ExportDXF.Data;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace ExportDXF.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(ExportDxfDbContext))]
|
|
||||||
[Migration("20260214195856_ExtractCutTemplate")]
|
|
||||||
partial class ExtractCutTemplate
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "8.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
|
||||||
|
|
||||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.BomItem", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("ID")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
|
|
||||||
|
|
||||||
b.Property<string>("ConfigurationName")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<int>("ExportRecordId")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("ItemNo")
|
|
||||||
.HasMaxLength(50)
|
|
||||||
.HasColumnType("nvarchar(50)");
|
|
||||||
|
|
||||||
b.Property<string>("Material")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<string>("PartName")
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<string>("PartNo")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<int?>("Qty")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<int>("SortOrder")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<int?>("TotalQty")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.HasKey("ID");
|
|
||||||
|
|
||||||
b.HasIndex("ExportRecordId");
|
|
||||||
|
|
||||||
b.ToTable("BomItems");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.CutTemplate", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<int>("BomItemId")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("ContentHash")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<string>("CutTemplateName")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<double?>("DefaultBendRadius")
|
|
||||||
.HasColumnType("float");
|
|
||||||
|
|
||||||
b.Property<string>("DxfFilePath")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<double?>("KFactor")
|
|
||||||
.HasColumnType("float");
|
|
||||||
|
|
||||||
b.Property<double?>("Thickness")
|
|
||||||
.HasColumnType("float");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("BomItemId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("CutTemplates");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.ExportRecord", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("DrawingNumber")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("ExportedAt")
|
|
||||||
.HasColumnType("datetime2");
|
|
||||||
|
|
||||||
b.Property<string>("ExportedBy")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<string>("OutputFolder")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<string>("PdfContentHash")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<string>("SourceFilePath")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("ExportRecords");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.BomItem", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("ExportDXF.Models.ExportRecord", "ExportRecord")
|
|
||||||
.WithMany("BomItems")
|
|
||||||
.HasForeignKey("ExportRecordId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("ExportRecord");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.CutTemplate", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("ExportDXF.Models.BomItem", "BomItem")
|
|
||||||
.WithOne("CutTemplate")
|
|
||||||
.HasForeignKey("ExportDXF.Models.CutTemplate", "BomItemId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("BomItem");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.BomItem", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("CutTemplate");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.ExportRecord", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("BomItems");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace ExportDXF.Migrations
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public partial class ExtractCutTemplate : Migration
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "ContentHash",
|
|
||||||
table: "BomItems");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "CutTemplateName",
|
|
||||||
table: "BomItems");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "DefaultBendRadius",
|
|
||||||
table: "BomItems");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "DxfFilePath",
|
|
||||||
table: "BomItems");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "KFactor",
|
|
||||||
table: "BomItems");
|
|
||||||
|
|
||||||
migrationBuilder.DropColumn(
|
|
||||||
name: "Thickness",
|
|
||||||
table: "BomItems");
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "CutTemplates",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<int>(type: "int", nullable: false)
|
|
||||||
.Annotation("SqlServer:Identity", "1, 1"),
|
|
||||||
DxfFilePath = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
|
||||||
ContentHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
|
||||||
CutTemplateName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
|
||||||
Thickness = table.Column<double>(type: "float", nullable: true),
|
|
||||||
KFactor = table.Column<double>(type: "float", nullable: true),
|
|
||||||
DefaultBendRadius = table.Column<double>(type: "float", nullable: true),
|
|
||||||
BomItemId = table.Column<int>(type: "int", nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_CutTemplates", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_CutTemplates_BomItems_BomItemId",
|
|
||||||
column: x => x.BomItemId,
|
|
||||||
principalTable: "BomItems",
|
|
||||||
principalColumn: "ID",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "IX_CutTemplates_BomItemId",
|
|
||||||
table: "CutTemplates",
|
|
||||||
column: "BomItemId",
|
|
||||||
unique: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
|
||||||
{
|
|
||||||
migrationBuilder.DropTable(
|
|
||||||
name: "CutTemplates");
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "ContentHash",
|
|
||||||
table: "BomItems",
|
|
||||||
type: "nvarchar(64)",
|
|
||||||
maxLength: 64,
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "CutTemplateName",
|
|
||||||
table: "BomItems",
|
|
||||||
type: "nvarchar(100)",
|
|
||||||
maxLength: 100,
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<double>(
|
|
||||||
name: "DefaultBendRadius",
|
|
||||||
table: "BomItems",
|
|
||||||
type: "float",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<string>(
|
|
||||||
name: "DxfFilePath",
|
|
||||||
table: "BomItems",
|
|
||||||
type: "nvarchar(max)",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<double>(
|
|
||||||
name: "KFactor",
|
|
||||||
table: "BomItems",
|
|
||||||
type: "float",
|
|
||||||
nullable: true);
|
|
||||||
|
|
||||||
migrationBuilder.AddColumn<double>(
|
|
||||||
name: "Thickness",
|
|
||||||
table: "BomItems",
|
|
||||||
type: "float",
|
|
||||||
nullable: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,185 +0,0 @@
|
|||||||
// <auto-generated />
|
|
||||||
using System;
|
|
||||||
using ExportDXF.Data;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
||||||
using Microsoft.EntityFrameworkCore.Metadata;
|
|
||||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
namespace ExportDXF.Migrations
|
|
||||||
{
|
|
||||||
[DbContext(typeof(ExportDxfDbContext))]
|
|
||||||
partial class ExportDxfDbContextModelSnapshot : ModelSnapshot
|
|
||||||
{
|
|
||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
#pragma warning disable 612, 618
|
|
||||||
modelBuilder
|
|
||||||
.HasAnnotation("ProductVersion", "8.0.11")
|
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
|
||||||
|
|
||||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.BomItem", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("ID")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
|
|
||||||
|
|
||||||
b.Property<string>("ConfigurationName")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<string>("Description")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<int>("ExportRecordId")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("ItemNo")
|
|
||||||
.HasMaxLength(50)
|
|
||||||
.HasColumnType("nvarchar(50)");
|
|
||||||
|
|
||||||
b.Property<string>("Material")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<string>("PartName")
|
|
||||||
.HasMaxLength(200)
|
|
||||||
.HasColumnType("nvarchar(200)");
|
|
||||||
|
|
||||||
b.Property<string>("PartNo")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<int?>("Qty")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<int>("SortOrder")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<int?>("TotalQty")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.HasKey("ID");
|
|
||||||
|
|
||||||
b.HasIndex("ExportRecordId");
|
|
||||||
|
|
||||||
b.ToTable("BomItems");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.CutTemplate", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<int>("BomItemId")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("ContentHash")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<string>("CutTemplateName")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<double?>("DefaultBendRadius")
|
|
||||||
.HasColumnType("float");
|
|
||||||
|
|
||||||
b.Property<string>("DxfFilePath")
|
|
||||||
.HasColumnType("nvarchar(max)");
|
|
||||||
|
|
||||||
b.Property<double?>("KFactor")
|
|
||||||
.HasColumnType("float");
|
|
||||||
|
|
||||||
b.Property<double?>("Thickness")
|
|
||||||
.HasColumnType("float");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("BomItemId")
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
b.ToTable("CutTemplates");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.ExportRecord", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
|
||||||
|
|
||||||
b.Property<string>("DrawingNumber")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<DateTime>("ExportedAt")
|
|
||||||
.HasColumnType("datetime2");
|
|
||||||
|
|
||||||
b.Property<string>("ExportedBy")
|
|
||||||
.HasMaxLength(100)
|
|
||||||
.HasColumnType("nvarchar(100)");
|
|
||||||
|
|
||||||
b.Property<string>("OutputFolder")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.Property<string>("PdfContentHash")
|
|
||||||
.HasMaxLength(64)
|
|
||||||
.HasColumnType("nvarchar(64)");
|
|
||||||
|
|
||||||
b.Property<string>("SourceFilePath")
|
|
||||||
.HasMaxLength(500)
|
|
||||||
.HasColumnType("nvarchar(500)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("ExportRecords");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.BomItem", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("ExportDXF.Models.ExportRecord", "ExportRecord")
|
|
||||||
.WithMany("BomItems")
|
|
||||||
.HasForeignKey("ExportRecordId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("ExportRecord");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.CutTemplate", b =>
|
|
||||||
{
|
|
||||||
b.HasOne("ExportDXF.Models.BomItem", "BomItem")
|
|
||||||
.WithOne("CutTemplate")
|
|
||||||
.HasForeignKey("ExportDXF.Models.CutTemplate", "BomItemId")
|
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.Navigation("BomItem");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.BomItem", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("CutTemplate");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("ExportDXF.Models.ExportRecord", b =>
|
|
||||||
{
|
|
||||||
b.Navigation("BomItems");
|
|
||||||
});
|
|
||||||
#pragma warning restore 612, 618
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,6 @@ namespace ExportDXF.Models
|
|||||||
{
|
{
|
||||||
public class BomItem
|
public class BomItem
|
||||||
{
|
{
|
||||||
public int ID { get; set; }
|
|
||||||
public string ItemNo { get; set; } = "";
|
public string ItemNo { get; set; } = "";
|
||||||
public string PartNo { get; set; } = "";
|
public string PartNo { get; set; } = "";
|
||||||
public int SortOrder { get; set; }
|
public int SortOrder { get; set; }
|
||||||
@@ -12,13 +11,7 @@ namespace ExportDXF.Models
|
|||||||
public string PartName { get; set; } = "";
|
public string PartName { get; set; } = "";
|
||||||
public string ConfigurationName { get; set; } = "";
|
public string ConfigurationName { get; set; } = "";
|
||||||
public string Material { get; set; } = "";
|
public string Material { get; set; } = "";
|
||||||
|
public CutTemplate CutTemplate { get; set; }
|
||||||
// EF Core relationship to ExportRecord
|
|
||||||
public int ExportRecordId { get; set; }
|
|
||||||
public virtual ExportRecord ExportRecord { get; set; }
|
|
||||||
|
|
||||||
// Optional 1:1 relationship to CutTemplate (only for sheet metal parts)
|
|
||||||
public virtual CutTemplate CutTemplate { get; set; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Size
|
public struct Size
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ namespace ExportDXF.Models
|
|||||||
{
|
{
|
||||||
public class CutTemplate
|
public class CutTemplate
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
|
||||||
public string DxfFilePath { get; set; } = "";
|
public string DxfFilePath { get; set; } = "";
|
||||||
public string ContentHash { get; set; }
|
public string ContentHash { get; set; }
|
||||||
public string CutTemplateName { get; set; } = "";
|
public string CutTemplateName { get; set; } = "";
|
||||||
|
public int Revision { get; set; } = 1;
|
||||||
|
|
||||||
// Sheet metal properties (moved from BomItem)
|
|
||||||
private double? _thickness;
|
private double? _thickness;
|
||||||
public double? Thickness
|
public double? Thickness
|
||||||
{
|
{
|
||||||
@@ -25,9 +24,5 @@ namespace ExportDXF.Models
|
|||||||
get => _defaultBendRadius;
|
get => _defaultBendRadius;
|
||||||
set => _defaultBendRadius = value.HasValue ? Math.Round(value.Value, 8) : null;
|
set => _defaultBendRadius = value.HasValue ? Math.Round(value.Value, 8) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FK back to BomItem
|
|
||||||
public int BomItemId { get; set; }
|
|
||||||
public virtual BomItem BomItem { get; set; }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using ExportDXF.Models;
|
using ExportDXF.Models;
|
||||||
using ExportDXF.ViewFlipDeciders;
|
using ExportDXF.ViewFlipDeciders;
|
||||||
using SolidWorks.Interop.sldworks;
|
using SolidWorks.Interop.sldworks;
|
||||||
using SolidWorks.Interop.swconst;
|
using SolidWorks.Interop.swconst;
|
||||||
@@ -28,14 +28,14 @@ namespace ExportDXF.Services
|
|||||||
public IViewFlipDecider ViewFlipDecider { get; set; }
|
public IViewFlipDecider ViewFlipDecider { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prefix to prepend to exported filenames.
|
/// Filename template with placeholders (e.g., "4321 A01 PT{item_no:2}").
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string FilePrefix { get; set; }
|
public string FilenameTemplate { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Selected Equipment ID for API operations (optional).
|
/// Output folder for DXF files and Excel workbook.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int? EquipmentId { get; set; }
|
public string OutputFolder { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cancellation token for canceling the export operation.
|
/// Cancellation token for canceling the export operation.
|
||||||
@@ -67,8 +67,8 @@ namespace ExportDXF.Services
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
return Path.Combine(
|
return Path.Combine(
|
||||||
Application.StartupPath,
|
Application.StartupPath,
|
||||||
DRAWING_TEMPLATE_FOLDER,
|
DRAWING_TEMPLATE_FOLDER,
|
||||||
DRAWING_TEMPLATE_FILE);
|
DRAWING_TEMPLATE_FILE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,23 +108,17 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(title))
|
if (!string.IsNullOrEmpty(title))
|
||||||
{
|
{
|
||||||
// Close the document without saving
|
|
||||||
SolidWorksApp.CloseDoc(title);
|
SolidWorksApp.CloseDoc(title);
|
||||||
ProgressCallback?.Invoke("Closed template drawing", LogLevel.Info, null);
|
ProgressCallback?.Invoke("Closed template drawing", LogLevel.Info, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the reference regardless of success/failure
|
|
||||||
TemplateDrawing = null;
|
TemplateDrawing = null;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ProgressCallback?.Invoke($"Failed to close template drawing: {ex.Message}", LogLevel.Error, null);
|
ProgressCallback?.Invoke($"Failed to close template drawing: {ex.Message}", LogLevel.Error, null);
|
||||||
|
|
||||||
// Still clear the reference to prevent further issues
|
|
||||||
TemplateDrawing = null;
|
TemplateDrawing = null;
|
||||||
|
|
||||||
// Don't throw here as this is cleanup code - log the error but continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace ExportDXF.Models
|
|
||||||
{
|
|
||||||
public class ExportRecord
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string DrawingNumber { get; set; }
|
|
||||||
public string SourceFilePath { get; set; }
|
|
||||||
public string OutputFolder { get; set; }
|
|
||||||
public DateTime ExportedAt { get; set; }
|
|
||||||
public string ExportedBy { get; set; }
|
|
||||||
|
|
||||||
public virtual ICollection<BomItem> BomItems { get; set; } = new List<BomItem>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -61,5 +61,16 @@ namespace ExportDXF.Services
|
|||||||
/// The SolidWorks component reference.
|
/// The SolidWorks component reference.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Component2 Component { get; set; }
|
public Component2 Component { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// SHA256 content hash of the exported DXF (transient, not persisted).
|
||||||
|
/// </summary>
|
||||||
|
public string ContentHash { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Full path to the locally-exported DXF temp file (transient, not persisted).
|
||||||
|
/// Set after successful export; used for upload to the API.
|
||||||
|
/// </summary>
|
||||||
|
public string LocalTempPath { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+11
-17
@@ -1,16 +1,12 @@
|
|||||||
using ExportDXF.Forms;
|
using ExportDXF.Forms;
|
||||||
using ExportDXF.Services;
|
using ExportDXF.Services;
|
||||||
using System;
|
using System;
|
||||||
using System.Configuration;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
namespace ExportDXF
|
namespace ExportDXF
|
||||||
{
|
{
|
||||||
static class Program
|
static class Program
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The main entry point for the application.
|
|
||||||
/// </summary>
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
static void Main()
|
static void Main()
|
||||||
{
|
{
|
||||||
@@ -23,34 +19,32 @@ namespace ExportDXF
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Simple dependency injection container.
|
|
||||||
/// </summary>
|
|
||||||
public class ServiceContainer
|
public class ServiceContainer
|
||||||
{
|
{
|
||||||
private readonly string _outputFolder;
|
|
||||||
|
|
||||||
public ServiceContainer()
|
|
||||||
{
|
|
||||||
_outputFolder = ConfigurationManager.AppSettings["ExportOutputFolder"] ?? @"C:\ExportDXF\Output";
|
|
||||||
}
|
|
||||||
|
|
||||||
public MainForm ResolveMainForm()
|
public MainForm ResolveMainForm()
|
||||||
{
|
{
|
||||||
var solidWorksService = new SolidWorksService();
|
var solidWorksService = new SolidWorksService();
|
||||||
var bomExtractor = new BomExtractor();
|
var bomExtractor = new BomExtractor();
|
||||||
var partExporter = new PartExporter();
|
var partExporter = new PartExporter();
|
||||||
var drawingExporter = new DrawingExporter();
|
var drawingExporter = new DrawingExporter();
|
||||||
var fileExportService = new FileExportService(_outputFolder);
|
var excelExportService = new ExcelExportService();
|
||||||
|
var logFileService = new LogFileService();
|
||||||
|
|
||||||
var exportService = new DxfExportService(
|
var exportService = new DxfExportService(
|
||||||
solidWorksService,
|
solidWorksService,
|
||||||
bomExtractor,
|
bomExtractor,
|
||||||
partExporter,
|
partExporter,
|
||||||
drawingExporter,
|
drawingExporter,
|
||||||
fileExportService);
|
excelExportService,
|
||||||
|
logFileService);
|
||||||
|
|
||||||
return new MainForm(solidWorksService, exportService, fileExportService);
|
var extractors = new IDrawingInfoExtractor[]
|
||||||
|
{
|
||||||
|
new EquipmentDrawingInfoExtractor(),
|
||||||
|
new DefaultDrawingInfoExtractor()
|
||||||
|
};
|
||||||
|
|
||||||
|
return new MainForm(solidWorksService, exportService, extractors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using System.Configuration;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Fallback extractor that uses the document name as prefix
|
||||||
|
/// and appends the configured default suffix.
|
||||||
|
/// Always returns true — this is the catch-all.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultDrawingInfoExtractor : IDrawingInfoExtractor
|
||||||
|
{
|
||||||
|
public bool TryExtract(string documentName, out DrawingInfoResult info)
|
||||||
|
{
|
||||||
|
var name = Path.GetFileNameWithoutExtension(documentName);
|
||||||
|
var suffix = ConfigurationManager.AppSettings["DefaultSuffix"] ?? "PT{item_no:2}";
|
||||||
|
|
||||||
|
info = new DrawingInfoResult
|
||||||
|
{
|
||||||
|
EquipmentNumber = null,
|
||||||
|
DrawingNumber = null,
|
||||||
|
DefaultTemplate = $"{name} {suffix}"
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,59 +1,47 @@
|
|||||||
using ExportDXF.Data;
|
|
||||||
using ExportDXF.Extensions;
|
using ExportDXF.Extensions;
|
||||||
using ExportDXF.ItemExtractors;
|
using ExportDXF.ItemExtractors;
|
||||||
using ExportDXF.Models;
|
using ExportDXF.Models;
|
||||||
using ExportDXF.Utilities;
|
using ExportDXF.Utilities;
|
||||||
using ExportDXF;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using SolidWorks.Interop.sldworks;
|
using SolidWorks.Interop.sldworks;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace ExportDXF.Services
|
namespace ExportDXF.Services
|
||||||
{
|
{
|
||||||
public interface IDxfExportService
|
public interface IDxfExportService
|
||||||
{
|
{
|
||||||
/// <summary>
|
Task ExportAsync(ExportContext context);
|
||||||
/// Exports the document specified in the context to DXF format.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="context">The export context containing all necessary information.</param>
|
|
||||||
void Export(ExportContext context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Service responsible for orchestrating the export of SolidWorks documents to DXF format.
|
|
||||||
/// </summary>
|
|
||||||
public class DxfExportService : IDxfExportService
|
public class DxfExportService : IDxfExportService
|
||||||
{
|
{
|
||||||
private readonly ISolidWorksService _solidWorksService;
|
private readonly ISolidWorksService _solidWorksService;
|
||||||
private readonly IBomExtractor _bomExtractor;
|
private readonly IBomExtractor _bomExtractor;
|
||||||
private readonly IPartExporter _partExporter;
|
private readonly IPartExporter _partExporter;
|
||||||
private readonly IDrawingExporter _drawingExporter;
|
private readonly IDrawingExporter _drawingExporter;
|
||||||
private readonly IFileExportService _fileExportService;
|
private readonly ExcelExportService _excelExportService;
|
||||||
private readonly Func<ExportDxfDbContext> _dbContextFactory;
|
private readonly LogFileService _logFileService;
|
||||||
|
|
||||||
public DxfExportService(
|
public DxfExportService(
|
||||||
ISolidWorksService solidWorksService,
|
ISolidWorksService solidWorksService,
|
||||||
IBomExtractor bomExtractor,
|
IBomExtractor bomExtractor,
|
||||||
IPartExporter partExporter,
|
IPartExporter partExporter,
|
||||||
IDrawingExporter drawingExporter,
|
IDrawingExporter drawingExporter,
|
||||||
IFileExportService fileExportService,
|
ExcelExportService excelExportService,
|
||||||
Func<ExportDxfDbContext> dbContextFactory = null)
|
LogFileService logFileService)
|
||||||
{
|
{
|
||||||
_solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService));
|
_solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService));
|
||||||
_bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor));
|
_bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor));
|
||||||
_partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter));
|
_partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter));
|
||||||
_drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter));
|
_drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter));
|
||||||
_fileExportService = fileExportService ?? throw new ArgumentNullException(nameof(fileExportService));
|
_excelExportService = excelExportService ?? throw new ArgumentNullException(nameof(excelExportService));
|
||||||
_dbContextFactory = dbContextFactory ?? (() => new ExportDxfDbContext());
|
_logFileService = logFileService ?? throw new ArgumentNullException(nameof(logFileService));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public async Task ExportAsync(ExportContext context)
|
||||||
/// Exports the document specified in the context to DXF format.
|
|
||||||
/// </summary>
|
|
||||||
public void Export(ExportContext context)
|
|
||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
throw new ArgumentNullException(nameof(context));
|
throw new ArgumentNullException(nameof(context));
|
||||||
@@ -61,7 +49,30 @@ namespace ExportDXF.Services
|
|||||||
ValidateContext(context);
|
ValidateContext(context);
|
||||||
SetupExportContext(context);
|
SetupExportContext(context);
|
||||||
|
|
||||||
|
var outputFolder = context.OutputFolder;
|
||||||
|
if (!Directory.Exists(outputFolder))
|
||||||
|
Directory.CreateDirectory(outputFolder);
|
||||||
|
|
||||||
|
var prefix = FilenameTemplateParser.GetPrefix(
|
||||||
|
context.FilenameTemplate,
|
||||||
|
context.ActiveDocument.Title);
|
||||||
|
|
||||||
|
var xlsxPath = Path.Combine(outputFolder, $"{prefix}.xlsx");
|
||||||
|
var logPath = Path.Combine(outputFolder, $"{prefix}.log");
|
||||||
|
|
||||||
|
_logFileService.StartExportLog(logPath);
|
||||||
|
_logFileService.LogInfo($"Export started: {context.ActiveDocument.FilePath}");
|
||||||
|
_logFileService.LogInfo($"Template: {context.FilenameTemplate}");
|
||||||
|
_logFileService.LogInfo($"Output: {outputFolder}");
|
||||||
|
|
||||||
|
// Read existing cut templates for revision comparison
|
||||||
|
var existingTemplates = _excelExportService.ReadExistingCutTemplates(xlsxPath);
|
||||||
|
|
||||||
|
var bomItems = new List<BomItem>();
|
||||||
|
List<Dictionary<string, string>> rawBomTable = null;
|
||||||
|
|
||||||
var startTime = DateTime.Now;
|
var startTime = DateTime.Now;
|
||||||
|
var tempDir = CreateTempWorkDir();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -70,35 +81,57 @@ namespace ExportDXF.Services
|
|||||||
switch (context.ActiveDocument.DocumentType)
|
switch (context.ActiveDocument.DocumentType)
|
||||||
{
|
{
|
||||||
case DocumentType.Part:
|
case DocumentType.Part:
|
||||||
ExportPart(context);
|
await ExportPartAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DocumentType.Assembly:
|
case DocumentType.Assembly:
|
||||||
ExportAssembly(context);
|
await ExportAssemblyAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DocumentType.Drawing:
|
case DocumentType.Drawing:
|
||||||
ExportDrawing(context);
|
rawBomTable = await ExportDrawingAsync(context, tempDir, outputFolder, existingTemplates, bomItems);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
LogProgress(context, "Unknown document type.", LogLevel.Error);
|
LogProgress(context, "Unknown document type.", LogLevel.Error);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write Excel file
|
||||||
|
_excelExportService.Write(xlsxPath, rawBomTable, bomItems);
|
||||||
|
_logFileService.LogInfo($"Wrote {Path.GetFileName(xlsxPath)}");
|
||||||
|
LogProgress(context, $"Saved {Path.GetFileName(xlsxPath)}");
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logFileService.LogWarning("Export cancelled by user");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logFileService.LogError($"Export failed: {ex.Message}");
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
CleanupExportContext(context);
|
CleanupExportContext(context);
|
||||||
_solidWorksService.EnableUserControl(true);
|
_solidWorksService.EnableUserControl(true);
|
||||||
|
CleanupTempDir(tempDir);
|
||||||
|
|
||||||
var duration = DateTime.Now - startTime;
|
var duration = DateTime.Now - startTime;
|
||||||
|
_logFileService.LogInfo($"Run time: {duration.ToReadableFormat()}");
|
||||||
LogProgress(context, $"Run time: {duration.ToReadableFormat()}");
|
LogProgress(context, $"Run time: {duration.ToReadableFormat()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Export Methods by Document Type
|
#region Export Methods by Document Type
|
||||||
|
|
||||||
private void ExportPart(ExportContext context)
|
private async Task ExportPartAsync(
|
||||||
|
ExportContext context,
|
||||||
|
string tempDir,
|
||||||
|
string outputFolder,
|
||||||
|
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
||||||
|
List<BomItem> bomItems)
|
||||||
{
|
{
|
||||||
LogProgress(context, "Active document is a Part");
|
LogProgress(context, "Active document is a Part");
|
||||||
|
|
||||||
@@ -109,11 +142,25 @@ namespace ExportDXF.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export directly to the output folder
|
var item = _partExporter.ExportSinglePart(part, tempDir, context);
|
||||||
_partExporter.ExportSinglePart(part, _fileExportService.OutputFolder, context);
|
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
item.ItemNo = "1";
|
||||||
|
|
||||||
|
var bomItem = CreateBomItem(item);
|
||||||
|
PlaceDxfFile(item, context, outputFolder, existingTemplates, bomItem);
|
||||||
|
bomItems.Add(bomItem);
|
||||||
|
context.BomItemCallback?.Invoke(bomItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExportAssembly(ExportContext context)
|
private async Task ExportAssemblyAsync(
|
||||||
|
ExportContext context,
|
||||||
|
string tempDir,
|
||||||
|
string outputFolder,
|
||||||
|
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
||||||
|
List<BomItem> bomItems)
|
||||||
{
|
{
|
||||||
LogProgress(context, "Active document is an Assembly");
|
LogProgress(context, "Active document is an Assembly");
|
||||||
LogProgress(context, "Fetching components...");
|
LogProgress(context, "Fetching components...");
|
||||||
@@ -135,11 +182,26 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
LogProgress(context, $"Found {items.Count} item(s).");
|
LogProgress(context, $"Found {items.Count} item(s).");
|
||||||
|
|
||||||
// Export directly to the output folder
|
// Assign item numbers
|
||||||
ExportItems(items, _fileExportService.OutputFolder, context);
|
int nextNum = 1;
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||||
|
{
|
||||||
|
item.ItemNo = nextNum.ToString();
|
||||||
|
nextNum++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, "Active document is a Drawing");
|
||||||
LogProgress(context, "Finding BOM tables...");
|
LogProgress(context, "Finding BOM tables...");
|
||||||
@@ -148,106 +210,35 @@ namespace ExportDXF.Services
|
|||||||
if (drawing == null)
|
if (drawing == null)
|
||||||
{
|
{
|
||||||
LogProgress(context, "Failed to get drawing document.", LogLevel.Error);
|
LogProgress(context, "Failed to get drawing document.", LogLevel.Error);
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read raw BOM table for Excel output
|
||||||
|
var rawBomTable = new List<Dictionary<string, string>>();
|
||||||
|
var bomTables = drawing.GetBomTables();
|
||||||
|
foreach (var table in bomTables)
|
||||||
|
{
|
||||||
|
var rows = RawBomTableReader.Read(table);
|
||||||
|
rawBomTable.AddRange(rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract items for DXF export
|
||||||
var items = _bomExtractor.ExtractFromDrawing(drawing, context.ProgressCallback);
|
var items = _bomExtractor.ExtractFromDrawing(drawing, context.ProgressCallback);
|
||||||
|
|
||||||
if (items == null || items.Count == 0)
|
if (items == null || items.Count == 0)
|
||||||
{
|
{
|
||||||
LogProgress(context, "Error: Bill of materials not found.", LogLevel.Error);
|
LogProgress(context, "Error: Bill of materials not found.", LogLevel.Error);
|
||||||
return;
|
return rawBomTable;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogProgress(context, $"Found {items.Count} component(s)");
|
LogProgress(context, $"Found {items.Count} component(s)");
|
||||||
|
|
||||||
// Determine drawing number for file naming
|
// Export drawing to PDF in output folder
|
||||||
var drawingNumber = ParseDrawingNumber(context);
|
_drawingExporter.ExportToPdf(drawing, outputFolder, context);
|
||||||
|
|
||||||
// Resolve output folder: /{outputDir}/{equipmentNo}/{drawingNo}/ or flat fallback
|
await ExportItemsAsync(items, tempDir, outputFolder, context, existingTemplates, bomItems);
|
||||||
var drawingOutputFolder = _fileExportService.GetDrawingOutputFolder(drawingNumber);
|
|
||||||
|
|
||||||
// Export drawing to PDF
|
return rawBomTable;
|
||||||
var tempDir = CreateTempWorkDir();
|
|
||||||
_drawingExporter.ExportToPdf(drawing, tempDir, context);
|
|
||||||
|
|
||||||
// Copy PDF to output folder with versioning
|
|
||||||
string pdfStashPath = null;
|
|
||||||
string savedPdfPath = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var pdfs = Directory.GetFiles(tempDir, "*.pdf");
|
|
||||||
if (pdfs.Length > 0)
|
|
||||||
{
|
|
||||||
// Determine the destination path to stash the existing file
|
|
||||||
var pdfFileName = !string.IsNullOrEmpty(drawingNumber)
|
|
||||||
? $"{drawingNumber}.pdf"
|
|
||||||
: Path.GetFileName(pdfs[0]);
|
|
||||||
var pdfDestPath = Path.Combine(drawingOutputFolder, pdfFileName);
|
|
||||||
|
|
||||||
pdfStashPath = _fileExportService.StashFile(pdfDestPath);
|
|
||||||
savedPdfPath = _fileExportService.SavePdfFile(pdfs[0], drawingNumber, drawingOutputFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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.Migrate();
|
|
||||||
exportRecord = new ExportRecord
|
|
||||||
{
|
|
||||||
DrawingNumber = drawingNumber ?? context.ActiveDocument.Title,
|
|
||||||
SourceFilePath = context.ActiveDocument.FilePath,
|
|
||||||
OutputFolder = drawingOutputFolder,
|
|
||||||
ExportedAt = DateTime.Now,
|
|
||||||
ExportedBy = System.Environment.UserName
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle PDF versioning - compute hash and compare with previous
|
|
||||||
if (savedPdfPath != null)
|
|
||||||
{
|
|
||||||
HandlePdfVersioning(savedPdfPath, exportRecord.DrawingNumber, exportRecord, context);
|
|
||||||
|
|
||||||
// Archive or discard old PDF based on hash comparison
|
|
||||||
if (pdfStashPath != null)
|
|
||||||
{
|
|
||||||
var previousRecord = db.ExportRecords
|
|
||||||
.Where(r => r.DrawingNumber == exportRecord.DrawingNumber && r.PdfContentHash != null)
|
|
||||||
.OrderByDescending(r => r.Id)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (previousRecord != null && previousRecord.PdfContentHash == exportRecord.PdfContentHash)
|
|
||||||
{
|
|
||||||
_fileExportService.DiscardStash(pdfStashPath);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_fileExportService.ArchiveFile(pdfStashPath, savedPdfPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
db.ExportRecords.Add(exportRecord);
|
|
||||||
db.SaveChanges();
|
|
||||||
LogProgress(context, $"Created export record (ID: {exportRecord.Id})", LogLevel.Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
// Clean up stash on error
|
|
||||||
_fileExportService.DiscardStash(pdfStashPath);
|
|
||||||
LogProgress(context, $"Database error creating export record: {ex.Message}", LogLevel.Error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export parts to DXF and save BOM items
|
|
||||||
ExportItems(items, drawingOutputFolder, context, exportRecord?.Id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@@ -256,17 +247,12 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
private void SetupExportContext(ExportContext context)
|
private void SetupExportContext(ExportContext context)
|
||||||
{
|
{
|
||||||
// Set up SolidWorks application reference
|
|
||||||
context.SolidWorksApp = _solidWorksService.GetNativeSldWorks();
|
context.SolidWorksApp = _solidWorksService.GetNativeSldWorks();
|
||||||
|
|
||||||
if (context.SolidWorksApp == null)
|
if (context.SolidWorksApp == null)
|
||||||
{
|
|
||||||
throw new InvalidOperationException("SolidWorks service is not connected.");
|
throw new InvalidOperationException("SolidWorks service is not connected.");
|
||||||
}
|
|
||||||
|
|
||||||
// Set up drawing template path
|
|
||||||
context.TemplateDrawing = null;
|
context.TemplateDrawing = null;
|
||||||
|
|
||||||
LogProgress(context, "Export context initialized");
|
LogProgress(context, "Export context initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,13 +260,11 @@ namespace ExportDXF.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Clean up template drawing if it was created
|
|
||||||
context.CleanupTemplateDrawing();
|
context.CleanupTemplateDrawing();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogProgress(context, $"Warning: Failed to cleanup template drawing: {ex.Message}", LogLevel.Warning);
|
LogProgress(context, $"Warning: Failed to cleanup template drawing: {ex.Message}", LogLevel.Warning);
|
||||||
// Don't throw - this is cleanup code
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +280,6 @@ namespace ExportDXF.Services
|
|||||||
{
|
{
|
||||||
TopLevelOnly = false
|
TopLevelOnly = false
|
||||||
};
|
};
|
||||||
|
|
||||||
return extractor.GetItems();
|
return extractor.GetItems();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -306,10 +289,17 @@ namespace ExportDXF.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExportItems(List<Item> items, string saveDirectory, ExportContext context, int? exportRecordId = null)
|
private async Task ExportItemsAsync(
|
||||||
|
List<Item> items,
|
||||||
|
string tempDir,
|
||||||
|
string outputFolder,
|
||||||
|
ExportContext context,
|
||||||
|
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
||||||
|
List<BomItem> bomItems)
|
||||||
{
|
{
|
||||||
int successCount = 0;
|
int successCount = 0;
|
||||||
int skippedCount = 0;
|
int skippedCount = 0;
|
||||||
|
int unchangedCount = 0;
|
||||||
int failureCount = 0;
|
int failureCount = 0;
|
||||||
int sortOrder = 0;
|
int sortOrder = 0;
|
||||||
|
|
||||||
@@ -323,168 +313,150 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// PartExporter will handle template drawing creation through context
|
_partExporter.ExportItem(item, tempDir, context);
|
||||||
_partExporter.ExportItem(item, saveDirectory, context);
|
|
||||||
|
|
||||||
// Always create BomItem for every item (sheet metal or not)
|
var bomItem = CreateBomItem(item);
|
||||||
var bomItem = new BomItem
|
bomItem.SortOrder = sortOrder++;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(item.LocalTempPath))
|
||||||
{
|
{
|
||||||
ExportRecordId = exportRecordId ?? 0,
|
var wasPlaced = PlaceDxfFile(item, context, outputFolder, existingTemplates, bomItem);
|
||||||
ItemNo = item.ItemNo ?? "",
|
if (wasPlaced)
|
||||||
PartNo = item.FileName ?? item.PartName ?? "",
|
successCount++;
|
||||||
SortOrder = sortOrder++,
|
else
|
||||||
Qty = item.Quantity,
|
unchangedCount++;
|
||||||
TotalQty = item.Quantity,
|
|
||||||
Description = item.Description ?? "",
|
|
||||||
PartName = item.PartName ?? "",
|
|
||||||
ConfigurationName = item.Configuration ?? "",
|
|
||||||
Material = item.Material ?? ""
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only create CutTemplate if DXF was exported successfully
|
|
||||||
if (!string.IsNullOrEmpty(item.FileName))
|
|
||||||
{
|
|
||||||
successCount++;
|
|
||||||
|
|
||||||
var dxfPath = Path.Combine(saveDirectory, item.FileName + ".dxf");
|
|
||||||
bomItem.CutTemplate = new CutTemplate
|
|
||||||
{
|
|
||||||
DxfFilePath = dxfPath,
|
|
||||||
ContentHash = item.ContentHash,
|
|
||||||
Thickness = item.Thickness > 0 ? item.Thickness : null,
|
|
||||||
KFactor = item.KFactor > 0 ? item.KFactor : null,
|
|
||||||
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null
|
|
||||||
};
|
|
||||||
|
|
||||||
// Compare hash with previous export to decide archive/discard
|
|
||||||
HandleDxfVersioning(item, dxfPath, context);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
skippedCount++;
|
skippedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to UI
|
bomItems.Add(bomItem);
|
||||||
context.BomItemCallback?.Invoke(bomItem);
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", LogLevel.Error);
|
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", LogLevel.Error);
|
||||||
|
_logFileService.LogError($"Item {item.ItemNo}: {ex.Message}");
|
||||||
failureCount++;
|
failureCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var summary = $"Export complete: {successCount} exported, {skippedCount} skipped";
|
var summary = $"Export complete: {successCount} exported";
|
||||||
|
if (unchangedCount > 0)
|
||||||
|
summary += $", {unchangedCount} unchanged";
|
||||||
|
if (skippedCount > 0)
|
||||||
|
summary += $", {skippedCount} skipped (non-sheet-metal)";
|
||||||
if (failureCount > 0)
|
if (failureCount > 0)
|
||||||
summary += $", {failureCount} failed";
|
summary += $", {failureCount} failed";
|
||||||
|
|
||||||
LogProgress(context, summary, failureCount > 0 ? LogLevel.Warning : LogLevel.Info);
|
LogProgress(context, summary, failureCount > 0 ? LogLevel.Warning : LogLevel.Info);
|
||||||
|
_logFileService.LogInfo(summary);
|
||||||
if (exportRecordId.HasValue)
|
|
||||||
{
|
|
||||||
LogProgress(context, $"BOM items saved to database (ExportRecord ID: {exportRecordId.Value})", LogLevel.Info);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
/// <summary>
|
||||||
|
/// Places a DXF file in the output folder with revision tracking.
|
||||||
#region Versioning
|
/// Returns true if a new file was written, false if unchanged.
|
||||||
|
/// </summary>
|
||||||
private void HandleDxfVersioning(Item item, string dxfPath, ExportContext context)
|
private bool PlaceDxfFile(
|
||||||
|
Item item,
|
||||||
|
ExportContext context,
|
||||||
|
string outputFolder,
|
||||||
|
Dictionary<string, (string ContentHash, int Revision, string FileName)> existingTemplates,
|
||||||
|
BomItem bomItem)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(item.ContentHash))
|
var baseName = FilenameTemplateParser.Evaluate(context.FilenameTemplate, item);
|
||||||
return;
|
var contentHash = item.ContentHash;
|
||||||
|
|
||||||
try
|
int revision = 1;
|
||||||
|
string dxfFileName;
|
||||||
|
|
||||||
|
// Check existing templates for revision comparison
|
||||||
|
if (existingTemplates.TryGetValue(item.ItemNo, out var existing))
|
||||||
{
|
{
|
||||||
using (var db = _dbContextFactory())
|
if (existing.ContentHash == contentHash)
|
||||||
{
|
{
|
||||||
var previousCutTemplate = db.CutTemplates
|
// Unchanged — skip file write, keep existing
|
||||||
.Where(ct => ct.DxfFilePath == dxfPath && ct.ContentHash != null)
|
dxfFileName = existing.FileName;
|
||||||
.OrderByDescending(ct => ct.Id)
|
revision = existing.Revision;
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (previousCutTemplate != null && previousCutTemplate.ContentHash == item.ContentHash)
|
LogProgress(context, $"Unchanged: {dxfFileName}", LogLevel.Info, item.PartName);
|
||||||
|
_logFileService.LogInfo($"Unchanged: {dxfFileName}");
|
||||||
|
|
||||||
|
bomItem.CutTemplate = new CutTemplate
|
||||||
{
|
{
|
||||||
// Content unchanged - discard the stashed file
|
DxfFilePath = dxfFileName,
|
||||||
_fileExportService.DiscardStash(item.StashedFilePath);
|
ContentHash = contentHash,
|
||||||
LogProgress(context, $"DXF unchanged: {item.FileName}.dxf", LogLevel.Info);
|
Revision = revision,
|
||||||
}
|
Thickness = item.Thickness > 0 ? item.Thickness : (double?)null,
|
||||||
else
|
KFactor = item.KFactor > 0 ? item.KFactor : (double?)null,
|
||||||
{
|
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : (double?)null
|
||||||
// Content changed or first export - archive the old file
|
};
|
||||||
if (!string.IsNullOrEmpty(item.StashedFilePath))
|
|
||||||
{
|
return false;
|
||||||
_fileExportService.ArchiveFile(item.StashedFilePath, dxfPath);
|
}
|
||||||
LogProgress(context, $"DXF updated, previous version archived: {item.FileName}.dxf", LogLevel.Info);
|
else
|
||||||
}
|
{
|
||||||
else
|
// Changed — increment revision
|
||||||
{
|
revision = existing.Revision + 1;
|
||||||
LogProgress(context, $"Exported: {item.FileName}.dxf", LogLevel.Info);
|
dxfFileName = GetRevisionFileName(baseName, revision);
|
||||||
}
|
|
||||||
}
|
LogProgress(context, $"Updated: {dxfFileName} (Rev{revision})", LogLevel.Info, item.PartName);
|
||||||
|
_logFileService.LogInfo($"Updated: {dxfFileName} (was {existing.FileName})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
else
|
||||||
{
|
{
|
||||||
// Don't fail the export if versioning fails - just discard the stash
|
// New item
|
||||||
_fileExportService.DiscardStash(item.StashedFilePath);
|
dxfFileName = $"{baseName}.dxf";
|
||||||
LogProgress(context, $"Versioning check failed for {item.FileName}: {ex.Message}", LogLevel.Warning);
|
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 void HandlePdfVersioning(string pdfPath, string drawingNumber, ExportRecord exportRecord, ExportContext context)
|
private BomItem CreateBomItem(Item item)
|
||||||
{
|
{
|
||||||
try
|
return new BomItem
|
||||||
{
|
{
|
||||||
var newHash = ContentHasher.ComputeFileHash(pdfPath);
|
ItemNo = item.ItemNo ?? "",
|
||||||
|
PartNo = item.FileName ?? item.PartName ?? "",
|
||||||
using (var db = _dbContextFactory())
|
SortOrder = 0,
|
||||||
{
|
Qty = item.Quantity,
|
||||||
var previousRecord = db.ExportRecords
|
TotalQty = item.Quantity,
|
||||||
.Where(r => r.DrawingNumber == drawingNumber && r.PdfContentHash != null)
|
Description = item.Description ?? "",
|
||||||
.OrderByDescending(r => r.Id)
|
PartName = item.PartName ?? "",
|
||||||
.FirstOrDefault();
|
ConfigurationName = item.Configuration ?? "",
|
||||||
|
Material = item.Material ?? ""
|
||||||
if (previousRecord != null && previousRecord.PdfContentHash == newHash)
|
};
|
||||||
{
|
|
||||||
LogProgress(context, $"PDF unchanged: {Path.GetFileName(pdfPath)}", LogLevel.Info);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LogProgress(context, $"Saved PDF: {Path.GetFileName(pdfPath)}", LogLevel.Info);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exportRecord != null)
|
|
||||||
exportRecord.PdfContentHash = newHash;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
LogProgress(context, $"PDF versioning check failed: {ex.Message}", LogLevel.Warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Helper Methods
|
#region Helper Methods
|
||||||
|
|
||||||
|
private string GetRevisionFileName(string baseName, int revision)
|
||||||
|
{
|
||||||
|
if (revision <= 1)
|
||||||
|
return $"{baseName}.dxf";
|
||||||
|
return $"{baseName} Rev{revision}.dxf";
|
||||||
|
}
|
||||||
|
|
||||||
private string CreateTempWorkDir()
|
private string CreateTempWorkDir()
|
||||||
{
|
{
|
||||||
var path = Path.Combine(Path.GetTempPath(), "ExportDXF-" + Guid.NewGuid().ToString("N"));
|
var path = Path.Combine(Path.GetTempPath(), "ExportDXF-" + Guid.NewGuid().ToString("N"));
|
||||||
@@ -492,17 +464,17 @@ namespace ExportDXF.Services
|
|||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ParseDrawingNumber(ExportContext context)
|
private void CleanupTempDir(string tempDir)
|
||||||
{
|
{
|
||||||
// Prefer prefix (e.g., "5007 A02 PT"), fallback to active document title
|
try
|
||||||
var candidate = context?.FilePrefix;
|
|
||||||
var info = string.IsNullOrWhiteSpace(candidate) ? null : DrawingInfo.Parse(candidate);
|
|
||||||
if (info == null)
|
|
||||||
{
|
{
|
||||||
var title = context?.ActiveDocument?.Title;
|
if (Directory.Exists(tempDir))
|
||||||
info = string.IsNullOrWhiteSpace(title) ? null : DrawingInfo.Parse(title);
|
Directory.Delete(tempDir, recursive: true);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Best-effort cleanup
|
||||||
}
|
}
|
||||||
return info != null ? ($"{info.EquipmentNo} {info.DrawingNo}") : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateContext(ExportContext context)
|
private void ValidateContext(ExportContext context)
|
||||||
@@ -512,6 +484,12 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
if (context.ProgressCallback == null)
|
if (context.ProgressCallback == null)
|
||||||
throw new ArgumentException("ProgressCallback cannot be null.", nameof(context));
|
throw new ArgumentException("ProgressCallback cannot be null.", nameof(context));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(context.FilenameTemplate))
|
||||||
|
throw new ArgumentException("FilenameTemplate cannot be null or empty.", nameof(context));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(context.OutputFolder))
|
||||||
|
throw new ArgumentException("OutputFolder cannot be null or empty.", nameof(context));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogProgress(ExportContext context, string message, LogLevel level = LogLevel.Info, string file = null)
|
private void LogProgress(ExportContext context, string message, LogLevel level = LogLevel.Info, string file = null)
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts equipment/drawing numbers from document names matching the
|
||||||
|
/// workplace format (e.g., "4321 A01.SLDDRW" → equipment "4321", drawing "A01").
|
||||||
|
/// Uses the existing DrawingInfo.Parse() logic.
|
||||||
|
/// </summary>
|
||||||
|
public class EquipmentDrawingInfoExtractor : IDrawingInfoExtractor
|
||||||
|
{
|
||||||
|
public bool TryExtract(string documentName, out DrawingInfoResult info)
|
||||||
|
{
|
||||||
|
info = null;
|
||||||
|
|
||||||
|
var name = Path.GetFileNameWithoutExtension(documentName);
|
||||||
|
var parsed = DrawingInfo.Parse(name);
|
||||||
|
|
||||||
|
if (parsed == null || string.IsNullOrEmpty(parsed.EquipmentNo))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var template = !string.IsNullOrEmpty(parsed.DrawingNo)
|
||||||
|
? $"{parsed.EquipmentNo} {parsed.DrawingNo} PT{{item_no:2}}"
|
||||||
|
: $"{parsed.EquipmentNo} PT{{item_no:2}}";
|
||||||
|
|
||||||
|
info = new DrawingInfoResult
|
||||||
|
{
|
||||||
|
EquipmentNumber = parsed.EquipmentNo,
|
||||||
|
DrawingNumber = parsed.DrawingNo,
|
||||||
|
DefaultTemplate = template
|
||||||
|
};
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using ClosedXML.Excel;
|
||||||
|
using ExportDXF.Models;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
public class ExcelExportService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads existing Cut Templates from an xlsx file to compare content hashes.
|
||||||
|
/// Returns empty dictionary if file doesn't exist or has no Cut Templates sheet.
|
||||||
|
/// Key = Item #, Value = (ContentHash, Revision, FileName)
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<string, (string ContentHash, int Revision, string FileName)> ReadExistingCutTemplates(string xlsxPath)
|
||||||
|
{
|
||||||
|
var result = new Dictionary<string, (string, int, string)>();
|
||||||
|
|
||||||
|
if (!File.Exists(xlsxPath))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
using (var workbook = new XLWorkbook(xlsxPath))
|
||||||
|
{
|
||||||
|
if (!workbook.TryGetWorksheet("Cut Templates", out var ws))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var lastCol = ws.LastColumnUsed()?.ColumnNumber() ?? 0;
|
||||||
|
var lastRow = ws.LastRowUsed()?.RowNumber() ?? 1;
|
||||||
|
|
||||||
|
if (lastCol == 0 || lastRow <= 1)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var headers = new Dictionary<string, int>();
|
||||||
|
for (int col = 1; col <= lastCol; col++)
|
||||||
|
{
|
||||||
|
var header = ws.Cell(1, col).GetString();
|
||||||
|
if (!string.IsNullOrEmpty(header))
|
||||||
|
headers[header] = col;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!headers.ContainsKey("Item #") || !headers.ContainsKey("Content Hash"))
|
||||||
|
return result;
|
||||||
|
|
||||||
|
for (int row = 2; row <= lastRow; row++)
|
||||||
|
{
|
||||||
|
var itemNo = ws.Cell(row, headers["Item #"]).GetString();
|
||||||
|
var hash = ws.Cell(row, headers["Content Hash"]).GetString();
|
||||||
|
var revision = headers.ContainsKey("Revision")
|
||||||
|
? ws.Cell(row, headers["Revision"]).GetValue<int>()
|
||||||
|
: 1;
|
||||||
|
var fileName = headers.ContainsKey("File Name")
|
||||||
|
? ws.Cell(row, headers["File Name"]).GetString()
|
||||||
|
: "";
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(itemNo))
|
||||||
|
result[itemNo] = (hash, revision, fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes or updates the xlsx file with BOM and Cut Templates sheets.
|
||||||
|
/// rawBomTable: list of rows where each row is a dictionary of column name → value.
|
||||||
|
/// If null or empty, the BOM sheet is not written (Part/Assembly exports).
|
||||||
|
/// </summary>
|
||||||
|
public void Write(
|
||||||
|
string xlsxPath,
|
||||||
|
List<Dictionary<string, string>> rawBomTable,
|
||||||
|
List<BomItem> bomItems)
|
||||||
|
{
|
||||||
|
using (var workbook = File.Exists(xlsxPath)
|
||||||
|
? new XLWorkbook(xlsxPath)
|
||||||
|
: new XLWorkbook())
|
||||||
|
{
|
||||||
|
WriteBomSheet(workbook, rawBomTable);
|
||||||
|
WriteCutTemplatesSheet(workbook, bomItems);
|
||||||
|
workbook.SaveAs(xlsxPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteBomSheet(XLWorkbook workbook, List<Dictionary<string, string>> rawBomTable)
|
||||||
|
{
|
||||||
|
if (rawBomTable == null || rawBomTable.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (workbook.TryGetWorksheet("BOM", out _))
|
||||||
|
workbook.Worksheets.Delete("BOM");
|
||||||
|
|
||||||
|
var sheet = workbook.Worksheets.Add("BOM");
|
||||||
|
var columns = rawBomTable[0].Keys.ToList();
|
||||||
|
|
||||||
|
for (int col = 0; col < columns.Count; col++)
|
||||||
|
sheet.Cell(1, col + 1).Value = columns[col];
|
||||||
|
|
||||||
|
for (int row = 0; row < rawBomTable.Count; row++)
|
||||||
|
{
|
||||||
|
for (int col = 0; col < columns.Count; col++)
|
||||||
|
{
|
||||||
|
string value;
|
||||||
|
rawBomTable[row].TryGetValue(columns[col], out value);
|
||||||
|
sheet.Cell(row + 2, col + 1).Value = value ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.Columns().AdjustToContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCutTemplatesSheet(XLWorkbook workbook, List<BomItem> bomItems)
|
||||||
|
{
|
||||||
|
if (workbook.TryGetWorksheet("Cut Templates", out _))
|
||||||
|
workbook.Worksheets.Delete("Cut Templates");
|
||||||
|
|
||||||
|
var sheet = workbook.Worksheets.Add("Cut Templates");
|
||||||
|
|
||||||
|
var headers = new[] { "Item #", "File Name", "Revision", "Thickness", "K-Factor", "Bend Radius", "Content Hash" };
|
||||||
|
for (int col = 0; col < headers.Length; col++)
|
||||||
|
sheet.Cell(1, col + 1).Value = headers[col];
|
||||||
|
|
||||||
|
int row = 2;
|
||||||
|
foreach (var item in bomItems.Where(b => b.CutTemplate != null).OrderBy(b => b.ItemNo))
|
||||||
|
{
|
||||||
|
var ct = item.CutTemplate;
|
||||||
|
sheet.Cell(row, 1).Value = item.ItemNo;
|
||||||
|
sheet.Cell(row, 2).Value = ct.DxfFilePath;
|
||||||
|
sheet.Cell(row, 3).Value = ct.Revision;
|
||||||
|
sheet.Cell(row, 4).Value = ct.Thickness ?? 0;
|
||||||
|
sheet.Cell(row, 5).Value = ct.KFactor ?? 0;
|
||||||
|
sheet.Cell(row, 6).Value = ct.DefaultBendRadius ?? 0;
|
||||||
|
sheet.Cell(row, 7).Value = ct.ContentHash ?? "";
|
||||||
|
row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.Columns().AdjustToContents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace ExportDXF.Services
|
|
||||||
{
|
|
||||||
public interface IFileExportService
|
|
||||||
{
|
|
||||||
string OutputFolder { get; }
|
|
||||||
string SaveDxfFile(string sourcePath, string drawingNumber, string itemNo);
|
|
||||||
string SavePdfFile(string sourcePath, string drawingNumber);
|
|
||||||
void EnsureOutputFolderExists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FileExportService : IFileExportService
|
|
||||||
{
|
|
||||||
public string OutputFolder { get; }
|
|
||||||
|
|
||||||
public FileExportService(string outputFolder)
|
|
||||||
{
|
|
||||||
OutputFolder = outputFolder ?? throw new ArgumentNullException(nameof(outputFolder));
|
|
||||||
EnsureOutputFolderExists();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnsureOutputFolderExists()
|
|
||||||
{
|
|
||||||
if (!Directory.Exists(OutputFolder))
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(OutputFolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SaveDxfFile(string sourcePath, string drawingNumber, string itemNo)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(sourcePath))
|
|
||||||
throw new ArgumentNullException(nameof(sourcePath));
|
|
||||||
|
|
||||||
var fileName = !string.IsNullOrEmpty(drawingNumber) && !string.IsNullOrEmpty(itemNo)
|
|
||||||
? $"{drawingNumber} PT{itemNo}.dxf"
|
|
||||||
: Path.GetFileName(sourcePath);
|
|
||||||
|
|
||||||
var destPath = Path.Combine(OutputFolder, fileName);
|
|
||||||
|
|
||||||
// If source and dest are the same, skip copy
|
|
||||||
if (!string.Equals(sourcePath, destPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
File.Copy(sourcePath, destPath, overwrite: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return destPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string SavePdfFile(string sourcePath, string drawingNumber)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(sourcePath))
|
|
||||||
throw new ArgumentNullException(nameof(sourcePath));
|
|
||||||
|
|
||||||
var fileName = !string.IsNullOrEmpty(drawingNumber)
|
|
||||||
? $"{drawingNumber}.pdf"
|
|
||||||
: Path.GetFileName(sourcePath);
|
|
||||||
|
|
||||||
var destPath = Path.Combine(OutputFolder, fileName);
|
|
||||||
|
|
||||||
// If source and dest are the same, skip copy
|
|
||||||
if (!string.Equals(sourcePath, destPath, StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
File.Copy(sourcePath, destPath, overwrite: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return destPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
public static class FilenameTemplateParser
|
||||||
|
{
|
||||||
|
private static readonly Regex PlaceholderPattern = new Regex(
|
||||||
|
@"\{(?<name>\w+)(?::(?<pad>\d+))?\}",
|
||||||
|
RegexOptions.Compiled);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates a template string for a given item.
|
||||||
|
/// e.g. "4321 A01 PT{item_no:2}" with item_no=3 → "4321 A01 PT03"
|
||||||
|
/// </summary>
|
||||||
|
public static string Evaluate(string template, Item item)
|
||||||
|
{
|
||||||
|
return PlaceholderPattern.Replace(template, match =>
|
||||||
|
{
|
||||||
|
var name = match.Groups["name"].Value.ToLowerInvariant();
|
||||||
|
var padStr = match.Groups["pad"].Value;
|
||||||
|
int pad = string.IsNullOrEmpty(padStr) ? 0 : int.Parse(padStr);
|
||||||
|
|
||||||
|
string value;
|
||||||
|
switch (name)
|
||||||
|
{
|
||||||
|
case "item_no":
|
||||||
|
value = item.ItemNo ?? "0";
|
||||||
|
if (pad > 0)
|
||||||
|
value = value.PadLeft(pad, '0');
|
||||||
|
break;
|
||||||
|
case "part_name":
|
||||||
|
value = item.PartName ?? "";
|
||||||
|
break;
|
||||||
|
case "config":
|
||||||
|
value = item.Configuration ?? "";
|
||||||
|
break;
|
||||||
|
case "material":
|
||||||
|
value = item.Material ?? "";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value = match.Value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extracts the literal prefix before the first placeholder.
|
||||||
|
/// Used for naming the xlsx and log files.
|
||||||
|
/// Falls back to documentName if prefix is empty.
|
||||||
|
/// </summary>
|
||||||
|
public static string GetPrefix(string template, string documentName)
|
||||||
|
{
|
||||||
|
var match = PlaceholderPattern.Match(template);
|
||||||
|
if (!match.Success)
|
||||||
|
return template.Trim();
|
||||||
|
|
||||||
|
var prefix = template.Substring(0, match.Index).Trim();
|
||||||
|
if (string.IsNullOrEmpty(prefix))
|
||||||
|
return Path.GetFileNameWithoutExtension(documentName);
|
||||||
|
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that the template contains {item_no} to prevent filename collisions.
|
||||||
|
/// </summary>
|
||||||
|
public static bool Validate(string template, out string error)
|
||||||
|
{
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(template))
|
||||||
|
{
|
||||||
|
error = "Template cannot be empty.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasItemNo = PlaceholderPattern.Matches(template)
|
||||||
|
.Cast<Match>()
|
||||||
|
.Any(m => m.Groups["name"].Value.Equals("item_no", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (!hasItemNo)
|
||||||
|
{
|
||||||
|
error = "Template must contain {item_no} or {item_no:N} to avoid filename collisions.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
public interface IDrawingInfoExtractor
|
||||||
|
{
|
||||||
|
bool TryExtract(string documentName, out DrawingInfoResult info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DrawingInfoResult
|
||||||
|
{
|
||||||
|
public string EquipmentNumber { get; set; }
|
||||||
|
public string DrawingNumber { get; set; }
|
||||||
|
public string DefaultTemplate { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
public class LogFileService : IDisposable
|
||||||
|
{
|
||||||
|
private StreamWriter _exportLog;
|
||||||
|
private static readonly string AppLogPath = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"ExportDXF", "ExportDXF.log");
|
||||||
|
|
||||||
|
public void StartExportLog(string logFilePath)
|
||||||
|
{
|
||||||
|
_exportLog?.Dispose();
|
||||||
|
|
||||||
|
var dir = Path.GetDirectoryName(logFilePath);
|
||||||
|
if (!Directory.Exists(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
_exportLog = new StreamWriter(logFilePath, append: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log(string level, string message)
|
||||||
|
{
|
||||||
|
var line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] {message}";
|
||||||
|
|
||||||
|
_exportLog?.WriteLine(line);
|
||||||
|
_exportLog?.Flush();
|
||||||
|
|
||||||
|
WriteAppLog(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LogInfo(string message) => Log("INFO", message);
|
||||||
|
public void LogWarning(string message) => Log("WARNING", message);
|
||||||
|
public void LogError(string message) => Log("ERROR", message);
|
||||||
|
|
||||||
|
private void WriteAppLog(string line)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var dir = Path.GetDirectoryName(AppLogPath);
|
||||||
|
if (!Directory.Exists(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
|
File.AppendAllText(AppLogPath, line + Environment.NewLine);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Best-effort app log — don't fail exports if log write fails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_exportLog?.Dispose();
|
||||||
|
_exportLog = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using ExportDXF.Extensions;
|
using ExportDXF.Extensions;
|
||||||
using ExportDXF.Models;
|
using ExportDXF.Models;
|
||||||
using ExportDXF.Utilities;
|
using ExportDXF.Utilities;
|
||||||
using SolidWorks.Interop.sldworks;
|
using SolidWorks.Interop.sldworks;
|
||||||
@@ -15,24 +15,29 @@ namespace ExportDXF.Services
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exports a single part document to DXF.
|
/// Exports a single part document to DXF.
|
||||||
|
/// Returns an Item with export metadata (filename, hash, sheet metal properties), or null if export failed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="part">The part document to export.</param>
|
/// <param name="part">The part document to export.</param>
|
||||||
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param>
|
/// <param name="saveDirectory">The temp directory where the DXF file will be saved.</param>
|
||||||
/// <param name="context">The export context.</param>
|
/// <param name="context">The export context.</param>
|
||||||
void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exports an item (component from BOM or assembly) to DXF.
|
/// Exports an item (component from BOM or assembly) to DXF.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="item">The item to export.</param>
|
/// <param name="item">The item to export.</param>
|
||||||
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param>
|
/// <param name="saveDirectory">The temp directory where the DXF file will be saved.</param>
|
||||||
/// <param name="context">The export context.</param>
|
/// <param name="context">The export context.</param>
|
||||||
void ExportItem(Item item, string saveDirectory, ExportContext context);
|
void ExportItem(Item item, string saveDirectory, ExportContext context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class PartExporter : IPartExporter
|
public class PartExporter : IPartExporter
|
||||||
{
|
{
|
||||||
public void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
|
public PartExporter()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
|
||||||
{
|
{
|
||||||
if (part == null)
|
if (part == null)
|
||||||
throw new ArgumentNullException(nameof(part));
|
throw new ArgumentNullException(nameof(part));
|
||||||
@@ -49,12 +54,52 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fileName = GetSinglePartFileName(model, context.FilePrefix);
|
var fileName = GetSinglePartFileName(model);
|
||||||
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
||||||
|
|
||||||
|
// Build result item with metadata
|
||||||
|
var item = new Item
|
||||||
|
{
|
||||||
|
PartName = model.GetTitle()?.Replace(".SLDPRT", "") ?? "",
|
||||||
|
Configuration = originalConfigName ?? "",
|
||||||
|
Quantity = 1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enrich with sheet metal properties and description
|
||||||
|
var sheetMetalProps = SolidWorksHelper.GetSheetMetalProperties(model);
|
||||||
|
if (sheetMetalProps != null)
|
||||||
|
{
|
||||||
|
item.Thickness = sheetMetalProps.Thickness;
|
||||||
|
item.KFactor = sheetMetalProps.KFactor;
|
||||||
|
item.BendRadius = sheetMetalProps.BendRadius;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get description from custom properties
|
||||||
|
var configPropMgr = model.Extension.CustomPropertyManager[originalConfigName];
|
||||||
|
item.Description = configPropMgr?.Get("Description");
|
||||||
|
if (string.IsNullOrEmpty(item.Description))
|
||||||
|
{
|
||||||
|
var docPropMgr = model.Extension.CustomPropertyManager[""];
|
||||||
|
item.Description = docPropMgr?.Get("Description");
|
||||||
|
}
|
||||||
|
item.Description = TextHelper.RemoveXmlTags(item.Description);
|
||||||
|
|
||||||
|
// Get material
|
||||||
|
item.Material = part.GetMaterialPropertyName2(originalConfigName, out _);
|
||||||
|
|
||||||
context.GetOrCreateTemplateDrawing();
|
context.GetOrCreateTemplateDrawing();
|
||||||
|
|
||||||
ExportPartToDxf(part, originalConfigName, savePath, context);
|
if (ExportPartToDxf(part, originalConfigName, savePath, context))
|
||||||
|
{
|
||||||
|
item.FileName = Path.GetFileNameWithoutExtension(savePath);
|
||||||
|
item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath);
|
||||||
|
item.LocalTempPath = savePath;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -94,7 +139,7 @@ namespace ExportDXF.Services
|
|||||||
|
|
||||||
EnrichItemWithMetadata(item, model, part);
|
EnrichItemWithMetadata(item, model, part);
|
||||||
|
|
||||||
var fileName = GetItemFileName(item, context.FilePrefix);
|
var fileName = GetItemFileName(item);
|
||||||
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
||||||
|
|
||||||
var templateDrawing = context.GetOrCreateTemplateDrawing();
|
var templateDrawing = context.GetOrCreateTemplateDrawing();
|
||||||
@@ -102,6 +147,8 @@ namespace ExportDXF.Services
|
|||||||
if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, savePath, context))
|
if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, savePath, context))
|
||||||
{
|
{
|
||||||
item.FileName = Path.GetFileNameWithoutExtension(savePath);
|
item.FileName = Path.GetFileNameWithoutExtension(savePath);
|
||||||
|
item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath);
|
||||||
|
item.LocalTempPath = savePath;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -260,6 +307,7 @@ namespace ExportDXF.Services
|
|||||||
{
|
{
|
||||||
var etcher = new EtchBendLines.Etcher();
|
var etcher = new EtchBendLines.Etcher();
|
||||||
etcher.AddEtchLines(dxfPath);
|
etcher.AddEtchLines(dxfPath);
|
||||||
|
FixDegreeSymbol(dxfPath);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@@ -267,30 +315,39 @@ namespace ExportDXF.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetSinglePartFileName(ModelDoc2 model, string prefix)
|
/// <summary>
|
||||||
|
/// Workaround for ACadSharp encoding bug (no upstream fix as of v3.4.9).
|
||||||
|
/// ACadSharp's DxfReader uses $DWGCODEPAGE (ANSI_1252) to decode text, but
|
||||||
|
/// AC1018+ DXF files use UTF-8. The degree symbol ° (UTF-8: C2 B0) gets
|
||||||
|
/// misread as two ANSI_1252 characters: Â (C2) and ° (B0).
|
||||||
|
/// See: https://github.com/DomCR/ACadSharp/issues?q=encoding
|
||||||
|
/// </summary>
|
||||||
|
private static void FixDegreeSymbol(string path)
|
||||||
|
{
|
||||||
|
var text = System.IO.File.ReadAllText(path);
|
||||||
|
if (text.Contains("\u00C2\u00B0"))
|
||||||
|
{
|
||||||
|
text = text.Replace("\u00C2\u00B0", "\u00B0");
|
||||||
|
System.IO.File.WriteAllText(path, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetSinglePartFileName(ModelDoc2 model)
|
||||||
{
|
{
|
||||||
var title = model.GetTitle().Replace(".SLDPRT", "");
|
var title = model.GetTitle().Replace(".SLDPRT", "");
|
||||||
var config = model.ConfigurationManager.ActiveConfiguration.Name;
|
var config = model.ConfigurationManager.ActiveConfiguration.Name;
|
||||||
var isDefaultConfig = string.Equals(config, "default", StringComparison.OrdinalIgnoreCase);
|
var isDefaultConfig = string.Equals(config, "default", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
var name = isDefaultConfig ? title : $"{title} [{config}]";
|
return isDefaultConfig ? title : $"{title} [{config}]";
|
||||||
return prefix + name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetItemFileName(Item item, string prefix)
|
private string GetItemFileName(Item item)
|
||||||
{
|
{
|
||||||
prefix = prefix?.Replace("\"", "''") ?? string.Empty;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||||
{
|
return item.PartName ?? "unknown";
|
||||||
return prefix + item.PartName;
|
|
||||||
}
|
|
||||||
|
|
||||||
var num = item.ItemNo.PadLeft(2, '0');
|
var num = item.ItemNo.PadLeft(2, '0');
|
||||||
// Expected format: {DrawingNo} PT{ItemNo}
|
return $"PT{num}";
|
||||||
return string.IsNullOrWhiteSpace(prefix)
|
|
||||||
? $"PT{num}"
|
|
||||||
: $"{prefix} PT{num}";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LogExportFailure(Item item, ExportContext context)
|
private void LogExportFailure(Item item, ExportContext context)
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using SolidWorks.Interop.sldworks;
|
||||||
|
|
||||||
|
namespace ExportDXF.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Reads all visible columns and rows from a SolidWorks BOM table annotation
|
||||||
|
/// as raw string data for direct copy into an Excel sheet.
|
||||||
|
/// </summary>
|
||||||
|
public static class RawBomTableReader
|
||||||
|
{
|
||||||
|
public static List<Dictionary<string, string>> Read(BomTableAnnotation bomTable)
|
||||||
|
{
|
||||||
|
var table = (TableAnnotation)bomTable;
|
||||||
|
var rows = new List<Dictionary<string, string>>();
|
||||||
|
|
||||||
|
int colCount = table.ColumnCount;
|
||||||
|
int rowCount = table.RowCount;
|
||||||
|
|
||||||
|
// Build visible column headers
|
||||||
|
var columns = new List<(int Index, string Header)>();
|
||||||
|
for (int col = 0; col < colCount; col++)
|
||||||
|
{
|
||||||
|
if (table.ColumnHidden[col])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var header = table.get_Text(0, col)?.Trim() ?? $"Column{col}";
|
||||||
|
columns.Add((col, header));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read data rows (skip header row 0, skip hidden rows)
|
||||||
|
for (int row = 1; row < rowCount; row++)
|
||||||
|
{
|
||||||
|
if (table.RowHidden[row])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var rowData = new Dictionary<string, string>();
|
||||||
|
foreach (var (colIdx, header) in columns)
|
||||||
|
{
|
||||||
|
rowData[header] = table.get_Text(row, colIdx)?.Trim() ?? "";
|
||||||
|
}
|
||||||
|
rows.Add(rowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace ExportDXF.Utilities
|
||||||
|
{
|
||||||
|
public static class ContentHasher
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a SHA256 hash of DXF file content, skipping the HEADER section
|
||||||
|
/// which contains timestamps that change on every save.
|
||||||
|
/// </summary>
|
||||||
|
public static string ComputeDxfContentHash(string filePath)
|
||||||
|
{
|
||||||
|
var text = File.ReadAllText(filePath);
|
||||||
|
var contentStart = FindEndOfHeader(text);
|
||||||
|
var content = contentStart >= 0 ? text.Substring(contentStart) : text;
|
||||||
|
|
||||||
|
using (var sha = SHA256.Create())
|
||||||
|
{
|
||||||
|
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(content));
|
||||||
|
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Computes a SHA256 hash of the entire file contents (for PDFs and other binary files).
|
||||||
|
/// </summary>
|
||||||
|
public static string ComputeFileHash(string filePath)
|
||||||
|
{
|
||||||
|
using (var sha = SHA256.Create())
|
||||||
|
using (var stream = File.OpenRead(filePath))
|
||||||
|
{
|
||||||
|
var bytes = sha.ComputeHash(stream);
|
||||||
|
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the position immediately after the HEADER section's ENDSEC marker.
|
||||||
|
/// DXF HEADER format:
|
||||||
|
/// 0\nSECTION\n2\nHEADER\n...variables...\n0\nENDSEC\n
|
||||||
|
/// Returns -1 if no HEADER section is found.
|
||||||
|
/// </summary>
|
||||||
|
private static int FindEndOfHeader(string text)
|
||||||
|
{
|
||||||
|
// Find the HEADER section start
|
||||||
|
var headerIndex = FindGroupCode(text, 0, "2", "HEADER");
|
||||||
|
if (headerIndex < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Advance past the HEADER value line so pair scanning stays aligned
|
||||||
|
var headerLineEnd = text.IndexOf('\n', headerIndex);
|
||||||
|
if (headerLineEnd < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Find the ENDSEC that closes the HEADER section
|
||||||
|
var pos = headerLineEnd + 1;
|
||||||
|
while (pos < text.Length)
|
||||||
|
{
|
||||||
|
var endsecIndex = FindGroupCode(text, pos, "0", "ENDSEC");
|
||||||
|
if (endsecIndex < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
// Move past the ENDSEC line
|
||||||
|
var lineEnd = text.IndexOf('\n', endsecIndex);
|
||||||
|
return lineEnd >= 0 ? lineEnd + 1 : text.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds a DXF group code pair (code line followed by value line) starting from the given position.
|
||||||
|
/// Returns the position of the value line, or -1 if not found.
|
||||||
|
/// </summary>
|
||||||
|
private static int FindGroupCode(string text, int startIndex, string groupCode, string value)
|
||||||
|
{
|
||||||
|
var pos = startIndex;
|
||||||
|
while (pos < text.Length)
|
||||||
|
{
|
||||||
|
// Skip whitespace/newlines to find the group code
|
||||||
|
while (pos < text.Length && (text[pos] == '\r' || text[pos] == '\n' || text[pos] == ' '))
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
if (pos >= text.Length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Read the group code line
|
||||||
|
var codeLineEnd = text.IndexOf('\n', pos);
|
||||||
|
if (codeLineEnd < 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var codeLine = text.Substring(pos, codeLineEnd - pos).Trim();
|
||||||
|
|
||||||
|
// Move to the value line
|
||||||
|
var valueStart = codeLineEnd + 1;
|
||||||
|
if (valueStart >= text.Length)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var valueLineEnd = text.IndexOf('\n', valueStart);
|
||||||
|
if (valueLineEnd < 0)
|
||||||
|
valueLineEnd = text.Length;
|
||||||
|
|
||||||
|
var valueLine = text.Substring(valueStart, valueLineEnd - valueStart).Trim();
|
||||||
|
|
||||||
|
if (codeLine == groupCode && string.Equals(valueLine, value, StringComparison.OrdinalIgnoreCase))
|
||||||
|
return valueStart;
|
||||||
|
|
||||||
|
// Move to the next pair
|
||||||
|
pos = valueLineEnd + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup>
|
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
|
|
||||||
</startup>
|
|
||||||
<appSettings>
|
<appSettings>
|
||||||
<add key="MaxBendRadius" value="2.0"/>
|
<add key="MaxBendRadius" value="2.0"/>
|
||||||
<add key="ExportOutputFolder" value="C:\ExportDXF\Output"/>
|
<add key="DefaultSuffix" value="PT{item_no:2}"/>
|
||||||
</appSettings>
|
</appSettings>
|
||||||
<connectionStrings>
|
|
||||||
<add name="ExportDxfDb"
|
|
||||||
connectionString="Server=localhost;Database=ExportDxfDb;Trusted_Connection=True;TrustServerCertificate=True;"
|
|
||||||
providerName="Microsoft.Data.SqlClient"/>
|
|
||||||
</connectionStrings>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,181 @@
|
|||||||
|
# ExportDXF: Remove API, Export to Excel
|
||||||
|
|
||||||
|
**Date:** 2026-04-13
|
||||||
|
**Status:** Draft
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Remove the FabWorks API and database dependencies from ExportDXF. Replace with local file output: DXF files + an Excel workbook (BOM + Cut Templates) that downstream tools like OpenNest can import directly. Make the tool self-contained — no server setup required.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
Merge the `feature/fabworks-api` branch into `master` to get its structural improvements (service layer, models, content hashing, EtchBendLines fixes), then remove the API/DB layer and replace with Excel + log file output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Output Structure
|
||||||
|
|
||||||
|
### Output Folder
|
||||||
|
|
||||||
|
- Default location: `Templates/` folder in the same directory as the source SolidWorks file
|
||||||
|
- If `Templates/` already exists, the export writes into it (see Revision Handling below)
|
||||||
|
|
||||||
|
### Files Produced
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\Projects\4321\
|
||||||
|
├── 4321 A01.SLDDRW (source drawing)
|
||||||
|
└── Templates\
|
||||||
|
├── 4321 A01 PT01.dxf
|
||||||
|
├── 4321 A01 PT02.dxf
|
||||||
|
├── 4321 A01 PT03.dxf
|
||||||
|
├── 4321 A01 PT03 Rev2.dxf (revised file, original kept)
|
||||||
|
├── 4321 A01.xlsx
|
||||||
|
└── 4321 A01.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Excel Workbook
|
||||||
|
|
||||||
|
Written with **ClosedXML 0.104.2** (same version as OpenNest).
|
||||||
|
|
||||||
|
**"BOM" sheet** — exact copy of the SolidWorks BOM table. All visible columns and rows reproduced as-is. Only present when exporting from a Drawing that contains a BOM table. Part and Assembly exports omit this sheet.
|
||||||
|
|
||||||
|
**"Cut Templates" sheet** — one row per sheet metal part:
|
||||||
|
|
||||||
|
| Column | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| Item # | Item number from the BOM or export sequence |
|
||||||
|
| File Name | DXF filename stem (no path, no extension) |
|
||||||
|
| Revision | Revision number (1 for initial, increments on change) |
|
||||||
|
| Thickness | Sheet metal thickness |
|
||||||
|
| K-Factor | K-Factor value |
|
||||||
|
| Bend Radius | Default bend radius |
|
||||||
|
| Content Hash | SHA256 of DXF content (excluding HEADER section) |
|
||||||
|
|
||||||
|
Present for all export types (Part, Assembly, Drawing).
|
||||||
|
|
||||||
|
### Log Files
|
||||||
|
|
||||||
|
**Per-export log:** `Templates/{prefix}.log`
|
||||||
|
- Timestamped lines: `2026-04-13 14:32:05 [INFO] Exported 4321 A01 PT03.dxf`
|
||||||
|
- Levels: INFO, WARNING, ERROR
|
||||||
|
- Appends on re-export
|
||||||
|
|
||||||
|
**App-level log:** `C:\ExportDXF\ExportDXF.log`
|
||||||
|
- Rolling log capturing all exports across all drawings
|
||||||
|
- Same format as per-export log
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Filename Format Template
|
||||||
|
|
||||||
|
### User Interface
|
||||||
|
|
||||||
|
A single text box replaces the equipment/drawing dropdowns. The user types a format string that controls DXF and xlsx naming.
|
||||||
|
|
||||||
|
**Example:** `4321 A01 PT{item_no:2}`
|
||||||
|
|
||||||
|
### Supported Placeholders
|
||||||
|
|
||||||
|
| Placeholder | Description | Example Output |
|
||||||
|
|-------------|-------------|----------------|
|
||||||
|
| `{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` |
|
||||||
|
|
||||||
|
### Naming Rules
|
||||||
|
|
||||||
|
- DXF filename: full template evaluated per part → `4321 A01 PT03.dxf`
|
||||||
|
- Excel filename: literal text before the first placeholder → `4321 A01.xlsx`
|
||||||
|
- Log filename: same stem as excel → `4321 A01.log`
|
||||||
|
- Validation: template must contain `{item_no}` (or `{item_no:N}`) to avoid filename collisions
|
||||||
|
- If the template starts with a placeholder (no literal prefix), fall back to the document name for the xlsx/log filename
|
||||||
|
|
||||||
|
### Auto-Fill
|
||||||
|
|
||||||
|
On document open, the format template text box is auto-filled via `IDrawingInfoExtractor`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Drawing Info Extraction
|
||||||
|
|
||||||
|
### Interface
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
public interface IDrawingInfoExtractor
|
||||||
|
{
|
||||||
|
bool TryExtract(string documentName, out DrawingInfo info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DrawingInfo
|
||||||
|
{
|
||||||
|
public string EquipmentNumber { get; set; }
|
||||||
|
public string DrawingNumber { get; set; }
|
||||||
|
public string DefaultTemplate { get; set; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementations
|
||||||
|
|
||||||
|
**`DefaultDrawingInfoExtractor`** — uses the document name as literal prefix, appends the default suffix from app.config (`DefaultSuffix` setting, default `PT{item_no:2}`).
|
||||||
|
|
||||||
|
Example: document `MyPart.SLDPRT` → template `MyPart {item_no:2}` (or with configured suffix)
|
||||||
|
|
||||||
|
**`EquipmentDrawingInfoExtractor`** — parses the `{EquipmentNo} {DrawingNo}` pattern from document names in AJ's workplace format. Builds template like `4321 A01 PT{item_no:2}`.
|
||||||
|
|
||||||
|
### Resolution
|
||||||
|
|
||||||
|
Extractors are tried in order. First one that returns `true` wins. Falls back to `DefaultDrawingInfoExtractor`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Revision Handling
|
||||||
|
|
||||||
|
When re-exporting to an existing `Templates` folder:
|
||||||
|
|
||||||
|
1. Read the existing xlsx Cut Templates sheet (if present)
|
||||||
|
2. For each part being exported, compute the DXF content hash
|
||||||
|
3. **Hash matches existing row** → skip, leave existing DXF file untouched
|
||||||
|
4. **Hash differs from existing row** → write new DXF with revision suffix (e.g., `PT03 Rev2.dxf`), update the xlsx row with new filename, new hash, incremented revision number
|
||||||
|
5. **New part (no existing row)** → write DXF, add new row to xlsx with revision 1
|
||||||
|
6. Old revision DXF files are kept in the folder as history
|
||||||
|
|
||||||
|
The BOM sheet (if present) is fully rewritten from the current drawing's BOM table on each export.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What We Keep from feature/fabworks-api
|
||||||
|
|
||||||
|
- Service layer architecture: `DxfExportService`, `IPartExporter`, `IDrawingExporter`, `IBomExtractor`
|
||||||
|
- Models: `BomItem`, `CutTemplate`, `Item`, `ExportContext`, `LogEvent`
|
||||||
|
- `ContentHasher` utility (SHA256 excluding DXF HEADER section)
|
||||||
|
- All 9 EtchBendLines submodule fixes (ACadSharp migration, bend detection, degree symbol, etc.)
|
||||||
|
- Async export flow with cancellation
|
||||||
|
- View flip decider
|
||||||
|
- SolidWorks user input disabling during export
|
||||||
|
|
||||||
|
## What We Remove
|
||||||
|
|
||||||
|
- `FabWorksApiClient` and all API DTOs
|
||||||
|
- `ExportDxfDbContext`, EF Core migrations, SQL Server connection string
|
||||||
|
- NuGet packages: `Microsoft.EntityFrameworkCore.SqlServer`, `Microsoft.EntityFrameworkCore.Tools`
|
||||||
|
- Equipment/Drawing dropdowns from MainForm
|
||||||
|
- Any FabWorks.Core / FabWorks.Api projects (if present after merge)
|
||||||
|
|
||||||
|
## What We Add
|
||||||
|
|
||||||
|
- `IDrawingInfoExtractor` interface + two implementations
|
||||||
|
- Format template text box with placeholder parsing
|
||||||
|
- `ExcelExportService` using ClosedXML — reads/writes BOM + Cut Templates sheets
|
||||||
|
- `LogFileService` — per-export and app-level log writing
|
||||||
|
- Output folder logic (Templates folder next to source, revision handling)
|
||||||
|
- NuGet package: `ClosedXML 0.104.2`
|
||||||
|
- `DefaultSuffix` setting in app.config
|
||||||
|
|
||||||
|
## UI Changes
|
||||||
|
|
||||||
|
- Replace equipment/drawing combo boxes with a single format template text box
|
||||||
|
- Keep 3 tabs: Log Events, Bill of Materials, Cut Templates
|
||||||
|
- Keep Start/Stop button
|
||||||
|
- Keep View Flip Decider dropdown
|
||||||
Reference in New Issue
Block a user