Refactored MainForm
This commit is contained in:
@@ -1,77 +0,0 @@
|
||||
using OfficeOpenXml;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace ExportDXF
|
||||
{
|
||||
public class BomToExcel
|
||||
{
|
||||
private const string DefaultTemplatePath = "Templates/BomTemplate.xlsx";
|
||||
private const string DefaultSheetName = "Parts";
|
||||
|
||||
public string TemplatePath { get; set; } = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, DefaultTemplatePath);
|
||||
|
||||
public void CreateBOMExcelFile(string filepath, IList<Item> items)
|
||||
{
|
||||
try
|
||||
{
|
||||
CopyTemplate(filepath);
|
||||
|
||||
using (var pkg = new ExcelPackage(new FileInfo(filepath)))
|
||||
{
|
||||
var partsSheet = pkg.Workbook.Worksheets[DefaultSheetName] ?? throw new Exception($"Worksheet '{DefaultSheetName}' not found in template.");
|
||||
|
||||
PopulateSheet(partsSheet, items);
|
||||
AutoFitColumns(partsSheet);
|
||||
|
||||
pkg.Save();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to create BOM Excel file: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyTemplate(string filepath)
|
||||
{
|
||||
if (!File.Exists(TemplatePath))
|
||||
throw new FileNotFoundException("Template file not found.", TemplatePath);
|
||||
|
||||
File.Copy(TemplatePath, filepath, true);
|
||||
}
|
||||
|
||||
private void PopulateSheet(ExcelWorksheet sheet, IList<Item> items)
|
||||
{
|
||||
for (int i = 0; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
var row = i + 2; // Assuming row 1 is the header
|
||||
var col = 1;
|
||||
|
||||
sheet.Cells[row, col++].Value = item.ItemNo;
|
||||
sheet.Cells[row, col++].Value = item.FileName;
|
||||
sheet.Cells[row, col++].Value = item.Quantity;
|
||||
sheet.Cells[row, col++].Value = item.Description;
|
||||
sheet.Cells[row, col++].Value = item.PartName;
|
||||
sheet.Cells[row, col++].Value = item.Configuration;
|
||||
sheet.Cells[row, col++].Value = item.Thickness > 0 ? (object)item.Thickness : null;
|
||||
sheet.Cells[row, col++].Value = item.Material;
|
||||
sheet.Cells[row, col++].Value = item.KFactor > 0 ? (object)item.KFactor : null;
|
||||
sheet.Cells[row, col++].Value = item.BendRadius > 0 ? (object)item.BendRadius : null;
|
||||
}
|
||||
}
|
||||
|
||||
private void AutoFitColumns(ExcelWorksheet sheet)
|
||||
{
|
||||
for (int i = 1; i <= sheet.Dimension.Columns; i++)
|
||||
{
|
||||
var column = sheet.Column(i);
|
||||
column.AutoFit();
|
||||
column.Width += 1; // Adding padding for better visibility
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,18 +94,34 @@
|
||||
<Compile Include="BendOrientation.cs" />
|
||||
<Compile Include="Bounds.cs" />
|
||||
<Compile Include="DrawingInfo.cs" />
|
||||
<Compile Include="BomToExcel.cs" />
|
||||
<Compile Include="Extensions.cs" />
|
||||
<Compile Include="Extensions\StringExtensions.cs" />
|
||||
<Compile Include="Extensions\SolidWorksExtensions.cs" />
|
||||
<Compile Include="Extensions\TimeSpanExtensions.cs" />
|
||||
<Compile Include="Extensions\UIExtensions.cs" />
|
||||
<Compile Include="Extensions\UnitConversionExtensions.cs" />
|
||||
<Compile Include="ItemExtractors\AssemblyItemExtractor.cs" />
|
||||
<Compile Include="ItemExtractors\BomColumnIndices.cs" />
|
||||
<Compile Include="ItemExtractors\BomItemExtractor.cs" />
|
||||
<Compile Include="ItemExtractors\ItemExtractor.cs" />
|
||||
<Compile Include="Forms\ViewFlipDeciderComboboxItem.cs" />
|
||||
<Compile Include="Item.cs" />
|
||||
<Compile Include="Models\DocumentType.cs" />
|
||||
<Compile Include="Models\ExportContext.cs" />
|
||||
<Compile Include="Models\Item.cs" />
|
||||
<Compile Include="Models\SolidWorksDocument.cs" />
|
||||
<Compile Include="Services\BomExcelExporter.cs" />
|
||||
<Compile Include="Services\BomExtractor.cs" />
|
||||
<Compile Include="Services\BomExcelSettings.cs" />
|
||||
<Compile Include="Services\DrawingExporter.cs" />
|
||||
<Compile Include="Services\DxfExportService.cs" />
|
||||
<Compile Include="Services\PartExporter.cs" />
|
||||
<Compile Include="Services\SolidWorksService.cs" />
|
||||
<Compile Include="Utilities\SheetMetalProperties.cs" />
|
||||
<Compile Include="Utilities\SolidWorksHelper.cs" />
|
||||
<Compile Include="Utilities\TextHelper.cs" />
|
||||
<Compile Include="Utilities\ViewHelper.cs" />
|
||||
<Compile Include="ViewFlipDeciders\AskViewFlipDecider.cs" />
|
||||
<Compile Include="ViewFlipDeciders\AutoViewFlipDecider.cs" />
|
||||
<Compile Include="ViewFlipDeciders\IViewFlipDecider.cs" />
|
||||
<Compile Include="Helper.cs" />
|
||||
<Compile Include="Forms\MainForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
@@ -114,10 +130,8 @@
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="SolidWorksExtensions.cs" />
|
||||
<Compile Include="Units.cs" />
|
||||
<Compile Include="ViewFlipDeciders\PreferUpViewFlipDecider.cs" />
|
||||
<Compile Include="ViewHelper.cs" />
|
||||
<Compile Include="ViewFlipDeciders\ViewFlipDeciderFactory.cs" />
|
||||
<EmbeddedResource Include="Forms\MainForm.resx">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
@@ -185,6 +199,7 @@
|
||||
<Name>EtchBendLines</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ExportDXF
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static void AppendText(this RichTextBox box, string text, Color color)
|
||||
{
|
||||
box.SelectionStart = box.TextLength;
|
||||
box.SelectionLength = 0;
|
||||
|
||||
box.SelectionColor = color;
|
||||
box.AppendText(text);
|
||||
box.SelectionColor = box.ForeColor;
|
||||
}
|
||||
|
||||
public static string ToReadableFormat(this TimeSpan ts)
|
||||
{
|
||||
var s = new StringBuilder();
|
||||
|
||||
if (ts.TotalHours >= 1)
|
||||
{
|
||||
var hrs = ts.Hours + ts.Days * 24.0;
|
||||
|
||||
s.Append(string.Format("{0}hrs ", hrs));
|
||||
s.Append(string.Format("{0}min ", ts.Minutes));
|
||||
s.Append(string.Format("{0}sec", ts.Seconds));
|
||||
}
|
||||
else if (ts.TotalMinutes >= 1)
|
||||
{
|
||||
s.Append(string.Format("{0}min ", ts.Minutes));
|
||||
s.Append(string.Format("{0}sec", ts.Seconds));
|
||||
}
|
||||
else
|
||||
{
|
||||
s.Append(string.Format("{0} seconds", ts.Seconds));
|
||||
}
|
||||
|
||||
return s.ToString();
|
||||
}
|
||||
|
||||
public static string PunctuateList(this IEnumerable<string> stringList)
|
||||
{
|
||||
var list = stringList.ToList();
|
||||
|
||||
switch (list.Count)
|
||||
{
|
||||
case 0:
|
||||
return string.Empty;
|
||||
|
||||
case 1:
|
||||
return list[0];
|
||||
|
||||
case 2:
|
||||
return string.Format("{0} and {1}", list[0], list[1]);
|
||||
|
||||
default:
|
||||
var s = string.Empty;
|
||||
|
||||
for (int i = 0; i < list.Count - 1; i++)
|
||||
s += list[i] + ", ";
|
||||
|
||||
s += "and " + list.Last();
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
namespace ExportDXF
|
||||
namespace ExportDXF.Extensions
|
||||
{
|
||||
public static class SolidWorksExtensions
|
||||
{
|
||||
48
ExportDXF/Extensions/StringExtensions.cs
Normal file
48
ExportDXF/Extensions/StringExtensions.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
namespace ExportDXF.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for string manipulation.
|
||||
/// </summary>
|
||||
public static class StringExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension method to remove XML tags from a string.
|
||||
/// </summary>
|
||||
public static string RemoveXmlTags(this string input)
|
||||
{
|
||||
return ExportDXF.Utilities.TextHelper.RemoveXmlTags(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method to clean text (remove XML and normalize whitespace).
|
||||
/// </summary>
|
||||
public static string CleanText(this string input)
|
||||
{
|
||||
return ExportDXF.Utilities.TextHelper.CleanText(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method to sanitize a filename.
|
||||
/// </summary>
|
||||
public static string SanitizeFileName(this string input)
|
||||
{
|
||||
return ExportDXF.Utilities.TextHelper.SanitizeFileName(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method to truncate a string.
|
||||
/// </summary>
|
||||
public static string Truncate(this string input, int maxLength, bool useEllipsis = true)
|
||||
{
|
||||
return ExportDXF.Utilities.TextHelper.Truncate(input, maxLength, useEllipsis);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method to convert to title case.
|
||||
/// </summary>
|
||||
public static string ToTitleCase(this string input)
|
||||
{
|
||||
return ExportDXF.Utilities.TextHelper.ToTitleCase(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
ExportDXF/Extensions/TimeSpanExtensions.cs
Normal file
31
ExportDXF/Extensions/TimeSpanExtensions.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace ExportDXF.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for TimeSpan formatting.
|
||||
/// </summary>
|
||||
public static class TimeSpanExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Formats a TimeSpan into a human-readable string.
|
||||
/// </summary>
|
||||
/// <param name="ts">The TimeSpan to format.</param>
|
||||
/// <returns>A human-readable duration string.</returns>
|
||||
public static string ToReadableFormat(this TimeSpan ts)
|
||||
{
|
||||
if (ts.TotalHours >= 1)
|
||||
{
|
||||
var totalHours = (int)(ts.TotalDays * 24 + ts.Hours);
|
||||
return $"{totalHours}hrs {ts.Minutes}min {ts.Seconds}sec";
|
||||
}
|
||||
|
||||
if (ts.TotalMinutes >= 1)
|
||||
{
|
||||
return $"{ts.Minutes}min {ts.Seconds}sec";
|
||||
}
|
||||
|
||||
return $"{ts.Seconds} seconds";
|
||||
}
|
||||
}
|
||||
}
|
||||
18
ExportDXF/Extensions/UIExtensions.cs
Normal file
18
ExportDXF/Extensions/UIExtensions.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Drawing;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ExportDXF.Extensions
|
||||
{
|
||||
public static class UIExtensions
|
||||
{
|
||||
public static void AppendText(this RichTextBox box, string text, Color color)
|
||||
{
|
||||
box.SelectionStart = box.TextLength;
|
||||
box.SelectionLength = 0;
|
||||
|
||||
box.SelectionColor = color;
|
||||
box.AppendText(text);
|
||||
box.SelectionColor = box.ForeColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
ExportDXF/Extensions/UnitConversionExtensions.cs
Normal file
41
ExportDXF/Extensions/UnitConversionExtensions.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
namespace ExportDXF.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for unit conversion between SolidWorks (meters) and millimeters.
|
||||
/// </summary>
|
||||
public static class UnitConversionExtensions
|
||||
{
|
||||
private const double METERS_TO_MM = .0001;
|
||||
private const double METERS_TO_INCHES = 0.0254;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a SolidWorks dimension (in meters) to millimeters.
|
||||
/// </summary>
|
||||
/// <param name="meters">The value in meters.</param>
|
||||
/// <returns>The value in millimeters.</returns>
|
||||
public static double FromSolidWorksToMM(this double meters)
|
||||
{
|
||||
return meters * METERS_TO_MM;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts millimeters to SolidWorks dimension (meters).
|
||||
/// </summary>
|
||||
/// <param name="millimeters">The value in millimeters.</param>
|
||||
/// <returns>The value in meters.</returns>
|
||||
public static double FromMMToSolidWorks(this double millimeters)
|
||||
{
|
||||
return millimeters / METERS_TO_MM;
|
||||
}
|
||||
|
||||
public static double FromSolidWorksToInches(this double meters)
|
||||
{
|
||||
return meters * METERS_TO_INCHES;
|
||||
}
|
||||
|
||||
public static double FromInchesToSolidWorks(this double inches)
|
||||
{
|
||||
return inches / METERS_TO_INCHES;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
ExportDXF/Forms/MainForm.Designer.cs
generated
2
ExportDXF/Forms/MainForm.Designer.cs
generated
@@ -48,7 +48,7 @@
|
||||
this.activeDocTitleBox.ReadOnly = true;
|
||||
this.activeDocTitleBox.Size = new System.Drawing.Size(584, 25);
|
||||
this.activeDocTitleBox.TabIndex = 2;
|
||||
this.activeDocTitleBox.TextChanged += new System.EventHandler(this.textBox1_TextChanged);
|
||||
this.activeDocTitleBox.TextChanged += new System.EventHandler(this.activeDocTitleBox_TextChanged);
|
||||
//
|
||||
// richTextBox1
|
||||
//
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
using ExportDXF.ItemExtractors;
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.Models;
|
||||
using ExportDXF.Services;
|
||||
using ExportDXF.ViewFlipDeciders;
|
||||
using OfficeOpenXml;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
@@ -17,627 +13,217 @@ namespace ExportDXF.Forms
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private SldWorks sldWorks;
|
||||
private BackgroundWorker worker;
|
||||
private DrawingDoc templateDrawing;
|
||||
private DateTime timeStarted;
|
||||
private IViewFlipDecider viewFlipDecider;
|
||||
private readonly ISolidWorksService _solidWorksService;
|
||||
private readonly IDxfExportService _exportService;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
public MainForm()
|
||||
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
worker = new BackgroundWorker();
|
||||
worker.WorkerSupportsCancellation = true;
|
||||
worker.DoWork += Worker_DoWork;
|
||||
worker.RunWorkerCompleted += Worker_RunWorkerCompleted;
|
||||
_solidWorksService = solidWorksService ??
|
||||
throw new ArgumentNullException(nameof(solidWorksService));
|
||||
|
||||
var type = typeof(IViewFlipDecider);
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(p => type.IsAssignableFrom(p) && p.IsClass)
|
||||
.ToList();
|
||||
_exportService = exportService ??
|
||||
throw new ArgumentNullException(nameof(exportService));
|
||||
|
||||
comboBox1.DataSource = GetItems();
|
||||
comboBox1.DisplayMember = "Name";
|
||||
InitializeViewFlipDeciders();
|
||||
}
|
||||
|
||||
protected override void OnLoad(EventArgs e)
|
||||
~MainForm()
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_solidWorksService?.Dispose();
|
||||
components?.Dispose();
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
protected override async void OnLoad(EventArgs e)
|
||||
{
|
||||
base.OnLoad(e);
|
||||
|
||||
button1.Enabled = false;
|
||||
|
||||
var task = new Task(ConnectToSolidWorks);
|
||||
|
||||
task.ContinueWith((t) =>
|
||||
{
|
||||
Invoke(new MethodInvoker(() =>
|
||||
{
|
||||
SetActiveDocName();
|
||||
button1.Enabled = true;
|
||||
}));
|
||||
});
|
||||
|
||||
task.Start();
|
||||
await InitializeAsync();
|
||||
}
|
||||
|
||||
private List<ViewFlipDeciderComboboxItem> GetItems()
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(s => s.GetTypes())
|
||||
.Where(p => typeof(IViewFlipDecider).IsAssignableFrom(p) && p.IsClass)
|
||||
try
|
||||
{
|
||||
LogMessage("Connecting to SolidWorks, this may take a minute...");
|
||||
|
||||
await _solidWorksService.ConnectAsync();
|
||||
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
||||
|
||||
LogMessage("Ready", Color.Green);
|
||||
|
||||
UpdateActiveDocumentDisplay();
|
||||
button1.Enabled = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage($"Failed to connect to SolidWorks: {ex.Message}", Color.Red);
|
||||
MessageBox.Show("Failed to connect to SolidWorks.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
Application.Exit();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeViewFlipDeciders()
|
||||
{
|
||||
var items = ViewFlipDeciderFactory.GetAvailableDeciders()
|
||||
.Select(d => new ViewFlipDeciderComboboxItem
|
||||
{
|
||||
Name = d.Name,
|
||||
ViewFlipDecider = d
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var items = new List<ViewFlipDeciderComboboxItem>();
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
var obj = (IViewFlipDecider)Activator.CreateInstance(type);
|
||||
|
||||
items.Add(new ViewFlipDeciderComboboxItem
|
||||
{
|
||||
Name = obj.Name,
|
||||
ViewFlipDecider = obj
|
||||
});
|
||||
}
|
||||
|
||||
// Move "Automatic" to the top if it exists
|
||||
var automatic = items.FirstOrDefault(i => i.Name == "Automatic");
|
||||
|
||||
if (automatic != null)
|
||||
{
|
||||
items.Remove(automatic);
|
||||
items.Insert(0, automatic);
|
||||
}
|
||||
|
||||
return items;
|
||||
comboBox1.DataSource = items;
|
||||
comboBox1.DisplayMember = "Name";
|
||||
}
|
||||
|
||||
private void button1_Click(object sender, EventArgs e)
|
||||
private async void button1_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (worker.IsBusy)
|
||||
if (_cancellationTokenSource != null)
|
||||
{
|
||||
button1.Enabled = false;
|
||||
worker.CancelAsync();
|
||||
return;
|
||||
CancelExport();
|
||||
}
|
||||
else
|
||||
{
|
||||
worker.RunWorkerAsync();
|
||||
await StartExportAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void Worker_DoWork(object sender, DoWorkEventArgs e)
|
||||
{
|
||||
timeStarted = DateTime.Now;
|
||||
|
||||
sldWorks.UserControl = false;
|
||||
sldWorks.ActiveModelDocChangeNotify -= SldWorks_ActiveModelDocChangeNotify;
|
||||
|
||||
Invoke(new MethodInvoker(() =>
|
||||
{
|
||||
var item = comboBox1.SelectedItem as ViewFlipDeciderComboboxItem;
|
||||
viewFlipDecider = item.ViewFlipDecider;
|
||||
|
||||
activeDocTitleBox.Enabled = false;
|
||||
prefixTextBox.Enabled = false;
|
||||
comboBox1.Enabled = false;
|
||||
|
||||
button1.Image = Properties.Resources.stop_alt;
|
||||
|
||||
if (richTextBox1.TextLength != 0)
|
||||
richTextBox1.AppendText("\n\n");
|
||||
}));
|
||||
|
||||
var model = sldWorks.ActiveDoc as ModelDoc2;
|
||||
|
||||
Print("Started at " + DateTime.Now.ToShortTimeString());
|
||||
|
||||
DetermineModelTypeAndExportToDXF(model);
|
||||
|
||||
sldWorks.UserControl = true;
|
||||
}
|
||||
|
||||
private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
|
||||
{
|
||||
Invoke(new MethodInvoker(() =>
|
||||
{
|
||||
if (templateDrawing != null)
|
||||
{
|
||||
sldWorks.CloseDoc(((ModelDoc2)templateDrawing).GetTitle());
|
||||
templateDrawing = null;
|
||||
}
|
||||
|
||||
activeDocTitleBox.Enabled = true;
|
||||
prefixTextBox.Enabled = true;
|
||||
comboBox1.Enabled = true;
|
||||
|
||||
button1.Image = Properties.Resources.play;
|
||||
button1.Enabled = true;
|
||||
}));
|
||||
|
||||
var duration = DateTime.Now - timeStarted;
|
||||
|
||||
Print("Run time: " + duration.ToReadableFormat());
|
||||
Print("Done.", Color.Green);
|
||||
|
||||
sldWorks.ActiveModelDocChangeNotify += SldWorks_ActiveModelDocChangeNotify;
|
||||
}
|
||||
|
||||
private int SldWorks_ActiveModelDocChangeNotify()
|
||||
{
|
||||
Invoke(new MethodInvoker(() =>
|
||||
{
|
||||
SetActiveDocName();
|
||||
UpdatePrefix();
|
||||
}));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private void Print(string s)
|
||||
{
|
||||
Invoke(new MethodInvoker(() =>
|
||||
{
|
||||
richTextBox1.AppendText(s + System.Environment.NewLine);
|
||||
richTextBox1.ScrollToCaret();
|
||||
}));
|
||||
}
|
||||
|
||||
private void Print(string s, Color color)
|
||||
{
|
||||
Invoke(new MethodInvoker(() =>
|
||||
{
|
||||
richTextBox1.AppendText(s + System.Environment.NewLine, color);
|
||||
richTextBox1.ScrollToCaret();
|
||||
}));
|
||||
}
|
||||
|
||||
private void ConnectToSolidWorks()
|
||||
{
|
||||
Print("Connecting to SolidWorks, this may take a minute...");
|
||||
sldWorks = Activator.CreateInstance(Type.GetTypeFromProgID("SldWorks.Application")) as SldWorks;
|
||||
|
||||
if (sldWorks == null)
|
||||
{
|
||||
MessageBox.Show("Failed to connect to SolidWorks.");
|
||||
Application.Exit();
|
||||
return;
|
||||
}
|
||||
|
||||
sldWorks.Visible = true;
|
||||
sldWorks.ActiveModelDocChangeNotify += SldWorks_ActiveModelDocChangeNotify;
|
||||
|
||||
Print("Ready", Color.Green);
|
||||
}
|
||||
|
||||
private void SetActiveDocName()
|
||||
{
|
||||
var model = sldWorks.ActiveDoc as ModelDoc2;
|
||||
activeDocTitleBox.Text = model == null ? "<No Document Open>" : model.GetTitle();
|
||||
}
|
||||
|
||||
private void DetermineModelTypeAndExportToDXF(ModelDoc2 model)
|
||||
{
|
||||
if (model is PartDoc)
|
||||
{
|
||||
Print("Active document is a Part");
|
||||
ExportToDXF(model as PartDoc);
|
||||
}
|
||||
else if (model is DrawingDoc)
|
||||
{
|
||||
Print("Active document is a Drawing");
|
||||
ExportToDXF(model as DrawingDoc);
|
||||
}
|
||||
else if (model is AssemblyDoc)
|
||||
{
|
||||
Print("Active document is a Assembly");
|
||||
ExportToDXF(model as AssemblyDoc);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPdfFileName(DrawingDoc drawingDoc)
|
||||
{
|
||||
var model = drawingDoc as ModelDoc2;
|
||||
var modelFilePath = model.GetPathName();
|
||||
var pdfFileName = Path.GetFileNameWithoutExtension(modelFilePath) + ".pdf";
|
||||
|
||||
return pdfFileName;
|
||||
}
|
||||
|
||||
private void ExportDrawingToPDF(DrawingDoc drawingDoc, string savePath)
|
||||
{
|
||||
var model = drawingDoc as ModelDoc2;
|
||||
|
||||
var exportData = sldWorks.GetExportFileData((int)swExportDataFileType_e.swExportPdfData) as ExportPdfData;
|
||||
exportData.ViewPdfAfterSaving = false;
|
||||
exportData.SetSheets((int)swExportDataSheetsToExport_e.swExportData_ExportAllSheets, drawingDoc);
|
||||
|
||||
int errors = 0;
|
||||
int warnings = 0;
|
||||
|
||||
var modelExtension = model.Extension;
|
||||
modelExtension.SaveAs(savePath, (int)swSaveAsVersion_e.swSaveAsCurrentVersion, (int)swSaveAsOptions_e.swSaveAsOptions_Silent, exportData, ref errors, ref warnings);
|
||||
|
||||
Print($"Saved drawing to PDF file \"{savePath}\"", Color.Green);
|
||||
}
|
||||
|
||||
private void ExportToDXF(DrawingDoc drawing)
|
||||
{
|
||||
Print("Finding BOM tables...");
|
||||
var bomTables = drawing.GetBomTables();
|
||||
|
||||
if (bomTables.Count == 0)
|
||||
{
|
||||
Print("Error: Bill of materials not found.", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
Print($"Found {bomTables.Count} BOM table(s)\n");
|
||||
|
||||
var items = new List<Item>();
|
||||
|
||||
foreach (var bom in bomTables)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
return;
|
||||
|
||||
var bomItems = GetItems(bom);
|
||||
|
||||
if (bomItems == null)
|
||||
return;
|
||||
|
||||
items.AddRange(bomItems);
|
||||
}
|
||||
|
||||
Print($"Found {items.Count} component(s)");
|
||||
|
||||
var saveDirectory = UserSelectFolder();
|
||||
|
||||
if (saveDirectory == null)
|
||||
{
|
||||
Print("Canceled\n", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
var pdfName = GetPdfFileName(drawing);
|
||||
var pdfPath = Path.Combine(saveDirectory, pdfName);
|
||||
|
||||
ExportDrawingToPDF(drawing, pdfPath);
|
||||
ExportToDXF(items, saveDirectory);
|
||||
}
|
||||
|
||||
private List<Item> GetItems(BomTableAnnotation bom)
|
||||
private async Task StartExportAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var itemExtractor = new BomItemExtractor(bom);
|
||||
itemExtractor.SkipHiddenRows = true;
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
var token = _cancellationTokenSource.Token;
|
||||
|
||||
Print($"Fetching components from {bom.BomFeature.Name}");
|
||||
UpdateUIForExportStart();
|
||||
|
||||
return itemExtractor.GetItems();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Print($"Failed to get items from BOM. {ex.Message}", Color.Red);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void ExportToDXF(PartDoc part)
|
||||
{
|
||||
var prefix = prefixTextBox.Text;
|
||||
var model = part as ModelDoc2;
|
||||
var activeConfig = ((Configuration)model.GetActiveConfiguration()).Name;
|
||||
|
||||
var dir = UserSelectFolder();
|
||||
|
||||
if (dir == null)
|
||||
{
|
||||
Print("Canceled\n", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dir == null)
|
||||
return;
|
||||
|
||||
var title = model.GetTitle().Replace(".SLDPRT", "");
|
||||
var config = model.ConfigurationManager.ActiveConfiguration.Name;
|
||||
var name = config.ToLower() == "default" ? title : string.Format("{0} [{1}]", title, config);
|
||||
var savePath = Path.Combine(dir, prefix + name + ".dxf");
|
||||
|
||||
SavePartToDXF(part, savePath);
|
||||
|
||||
model.ShowConfiguration(activeConfig);
|
||||
}
|
||||
|
||||
private void ExportToDXF(AssemblyDoc assembly)
|
||||
{
|
||||
Print("Fetching components...");
|
||||
|
||||
var itemExtractor = new AssemblyItemExtractor(assembly);
|
||||
itemExtractor.TopLevelOnly = false;
|
||||
|
||||
var items = itemExtractor.GetItems();
|
||||
|
||||
Print($"Found {items.Count} item(s).\n");
|
||||
|
||||
var saveDirectory = UserSelectFolder();
|
||||
|
||||
if (saveDirectory == null)
|
||||
{
|
||||
Print("Canceled\n", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
ExportToDXF(items, saveDirectory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="items"></param>
|
||||
/// <param name="saveDirectory">Directory to save the DXF file in.</param>
|
||||
private void ExportToDXF(IEnumerable<Item> items, string saveDirectory)
|
||||
{
|
||||
var prefix = prefixTextBox.Text;
|
||||
|
||||
templateDrawing = CreateDrawing();
|
||||
|
||||
Print("");
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (worker.CancellationPending)
|
||||
var activeDoc = _solidWorksService.GetActiveDocument();
|
||||
if (activeDoc == null)
|
||||
{
|
||||
LogMessage("No active document.", Color.Red);
|
||||
return;
|
||||
|
||||
if (item.Component == null)
|
||||
continue;
|
||||
|
||||
var fileName = GetFileName(item);
|
||||
var savepath = Path.Combine(saveDirectory, fileName + ".dxf");
|
||||
|
||||
item.Component.SetLightweightToResolved();
|
||||
|
||||
var model = item.Component.GetModelDoc2() as ModelDoc2;
|
||||
var part = model as PartDoc;
|
||||
|
||||
if (part == null)
|
||||
{
|
||||
Print(item.ItemNo + " - skipped, not a part document");
|
||||
continue;
|
||||
}
|
||||
|
||||
var config = item.Component.ReferencedConfiguration;
|
||||
var viewFlipDecider = GetSelectedViewFlipDecider();
|
||||
var prefix = prefixTextBox.Text;
|
||||
|
||||
var sheetMetal = model.GetFeatureByTypeName("SheetMetal");
|
||||
var sheetMetalData = sheetMetal?.GetDefinition() as SheetMetalFeatureData;
|
||||
|
||||
if (sheetMetalData != null)
|
||||
var exportContext = new ExportContext
|
||||
{
|
||||
item.Thickness = sheetMetalData.Thickness.FromSldWorks();
|
||||
item.KFactor = sheetMetalData.KFactor;
|
||||
item.BendRadius = sheetMetalData.BendRadius.FromSldWorks();
|
||||
}
|
||||
ActiveDocument = activeDoc,
|
||||
ViewFlipDecider = viewFlipDecider,
|
||||
FilePrefix = prefix,
|
||||
CancellationToken = token,
|
||||
ProgressCallback = LogMessage
|
||||
};
|
||||
|
||||
if (item.Description == null)
|
||||
item.Description = model.Extension.CustomPropertyManager[config].Get("Description");
|
||||
LogMessage($"Started at {DateTime.Now:t}");
|
||||
|
||||
if (item.Description == null)
|
||||
item.Description = model.Extension.CustomPropertyManager[""].Get("Description");
|
||||
await Task.Run(() => _exportService.Export(exportContext), token);
|
||||
|
||||
item.Description = RemoveFontXml(item.Description);
|
||||
|
||||
var db = string.Empty;
|
||||
|
||||
item.Material = part.GetMaterialPropertyName2(config, out db);
|
||||
|
||||
if (part == null)
|
||||
continue;
|
||||
|
||||
if (SavePartToDXF(part, config, savepath))
|
||||
{
|
||||
item.FileName = Path.GetFileNameWithoutExtension(savepath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var desc = item.Description.ToLower();
|
||||
|
||||
if (desc.Contains("laser"))
|
||||
{
|
||||
Print($"Failed to export item #{item.ItemNo} but description says it is laser cut.", Color.Red);
|
||||
}
|
||||
else if (desc.Contains("plasma"))
|
||||
{
|
||||
Print($"Failed to export item #{item.ItemNo} but description says it is plasma cut.", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
Print("");
|
||||
|
||||
Application.DoEvents();
|
||||
LogMessage("Done.", Color.Green);
|
||||
}
|
||||
|
||||
try
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
var drawingInfo = DrawingInfo.Parse(prefix);
|
||||
var bomName = drawingInfo != null ? $"{drawingInfo.JobNo} {drawingInfo.DrawingNo} BOM" : "BOM";
|
||||
var bomFile = Path.Combine(saveDirectory, bomName + ".xlsx");
|
||||
|
||||
var excelReport = new BomToExcel();
|
||||
excelReport.CreateBOMExcelFile(bomFile, items.ToList());
|
||||
LogMessage("Export canceled.", Color.Red);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Print(ex.Message, Color.Red);
|
||||
LogMessage($"Export failed: {ex.Message}", Color.Red);
|
||||
MessageBox.Show($"Export failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
UpdateUIForExportComplete();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private string RemoveFontXml(string s)
|
||||
private void CancelExport()
|
||||
{
|
||||
if (s == null)
|
||||
return null;
|
||||
|
||||
var fontXmlRegex = new Regex("<FONT.*?\\>");
|
||||
var matches = fontXmlRegex.Matches(s)
|
||||
.Cast<Match>()
|
||||
.OrderByDescending(m => m.Index);
|
||||
|
||||
foreach (var match in matches)
|
||||
{
|
||||
s = s.Remove(match.Index, match.Length);
|
||||
}
|
||||
|
||||
return s;
|
||||
button1.Enabled = false;
|
||||
_cancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private string GetFileName(Item item)
|
||||
private IViewFlipDecider GetSelectedViewFlipDecider()
|
||||
{
|
||||
var prefix = prefixTextBox.Text.Replace("\"", "''");
|
||||
var item = comboBox1.SelectedItem as ViewFlipDeciderComboboxItem;
|
||||
return item?.ViewFlipDecider;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||
private void UpdateUIForExportStart()
|
||||
{
|
||||
activeDocTitleBox.Enabled = false;
|
||||
prefixTextBox.Enabled = false;
|
||||
comboBox1.Enabled = false;
|
||||
button1.Image = Properties.Resources.stop_alt;
|
||||
|
||||
if (richTextBox1.TextLength != 0)
|
||||
{
|
||||
return prefix + item.PartName;
|
||||
}
|
||||
else
|
||||
{
|
||||
return prefix + item.ItemNo.PadLeft(2, '0');
|
||||
richTextBox1.AppendText("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
private bool SavePartToDXF(PartDoc part, string savePath)
|
||||
private void UpdateUIForExportComplete()
|
||||
{
|
||||
var partModel = part as ModelDoc2;
|
||||
var config = partModel.ConfigurationManager.ActiveConfiguration.Name;
|
||||
|
||||
return SavePartToDXF(part, config, savePath);
|
||||
activeDocTitleBox.Enabled = true;
|
||||
prefixTextBox.Enabled = true;
|
||||
comboBox1.Enabled = true;
|
||||
button1.Image = Properties.Resources.play;
|
||||
button1.Enabled = true;
|
||||
}
|
||||
|
||||
private bool SavePartToDXF(PartDoc part, string partConfiguration, string savePath)
|
||||
private void OnActiveDocumentChanged(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
if (InvokeRequired)
|
||||
{
|
||||
var partModel = part as ModelDoc2;
|
||||
Invoke(new Action(() => OnActiveDocumentChanged(sender, e)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (partModel.IsSheetMetal() == false)
|
||||
UpdateActiveDocumentDisplay();
|
||||
UpdatePrefixFromActiveDocument();
|
||||
}
|
||||
|
||||
private void UpdateActiveDocumentDisplay()
|
||||
{
|
||||
var activeDoc = _solidWorksService.GetActiveDocument();
|
||||
activeDocTitleBox.Text = activeDoc?.Title ?? "<No Document Open>";
|
||||
}
|
||||
|
||||
private void UpdatePrefixFromActiveDocument()
|
||||
{
|
||||
var activeDoc = _solidWorksService.GetActiveDocument();
|
||||
|
||||
if (activeDoc == null)
|
||||
{
|
||||
prefixTextBox.Text = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeDoc.DocumentType == DocumentType.Drawing)
|
||||
{
|
||||
var drawingInfo = DrawingInfo.Parse(activeDoc.Title);
|
||||
if (drawingInfo != null)
|
||||
{
|
||||
Print(partModel.GetTitle() + " - skipped, not sheet metal");
|
||||
return false;
|
||||
prefixTextBox.Text = $"{drawingInfo.JobNo} {drawingInfo.DrawingNo} PT";
|
||||
prefixTextBox.SelectionStart = prefixTextBox.Text.Length;
|
||||
}
|
||||
|
||||
if (templateDrawing == null)
|
||||
templateDrawing = CreateDrawing();
|
||||
|
||||
Helper.UncheckFlatPatternCornerTreatment(partModel);
|
||||
|
||||
var sheet = templateDrawing.IGetCurrentSheet();
|
||||
var modelName = Path.GetFileNameWithoutExtension(partModel.GetPathName());
|
||||
sheet.SetName(modelName);
|
||||
|
||||
Print(partModel.GetTitle() + " - Creating flat pattern.");
|
||||
SolidWorks.Interop.sldworks.View view;
|
||||
view = templateDrawing.CreateFlatPatternViewFromModelView3(partModel.GetPathName(), partConfiguration, 0, 0, 0, false, false);
|
||||
|
||||
if (view == null)
|
||||
return false;
|
||||
|
||||
view.ShowSheetMetalBendNotes = true;
|
||||
|
||||
var drawingModel = templateDrawing as ModelDoc2;
|
||||
drawingModel.ViewZoomtofit2();
|
||||
|
||||
var flatPatternModel = ViewHelper.GetModelFromView(view);
|
||||
Helper.SetFlatPatternSuppressionState(flatPatternModel, swComponentSuppressionState_e.swComponentFullyResolved);
|
||||
|
||||
if (ViewHelper.HasSupressedBends(view))
|
||||
{
|
||||
Print("A bend is suppressed, please check flat pattern", Color.Red);
|
||||
}
|
||||
|
||||
if (ViewHelper.HideModelSketches(view))
|
||||
{
|
||||
// delete the current view that has sketches shown
|
||||
drawingModel.SelectByName(0, view.Name);
|
||||
drawingModel.DeleteSelection(false);
|
||||
|
||||
// recreate the flat pattern view
|
||||
view = templateDrawing.CreateFlatPatternViewFromModelView3(partModel.GetPathName(), partConfiguration, 0, 0, 0, false, false);
|
||||
view.ShowSheetMetalBendNotes = true;
|
||||
}
|
||||
|
||||
if (viewFlipDecider.ShouldFlip(view))
|
||||
{
|
||||
Print(partModel.GetTitle() + " - Flipped view", Color.Blue);
|
||||
view.FlipView = true;
|
||||
}
|
||||
|
||||
drawingModel.SaveAs(savePath);
|
||||
|
||||
var etcher = new EtchBendLines.Etcher();
|
||||
etcher.AddEtchLines(savePath);
|
||||
|
||||
Print(partModel.GetTitle() + " - Saved to \"" + savePath + "\"", Color.Green);
|
||||
|
||||
drawingModel.SelectByName(0, view.Name);
|
||||
drawingModel.DeleteSelection(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show(ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string UserSelectFolder()
|
||||
{
|
||||
string path = null;
|
||||
|
||||
Invoke(new MethodInvoker(() =>
|
||||
{
|
||||
var dlg = new FolderBrowserDialog();
|
||||
dlg.Description = "Where do you want to save the DXF files?";
|
||||
|
||||
if (dlg.ShowDialog() != DialogResult.OK)
|
||||
throw new Exception("Export canceled by user.");
|
||||
|
||||
path = dlg.SelectedPath;
|
||||
}));
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private DrawingDoc CreateDrawing()
|
||||
{
|
||||
return sldWorks.NewDocument(
|
||||
DrawingTemplatePath,
|
||||
(int)swDwgPaperSizes_e.swDwgPaperDsize,
|
||||
1,
|
||||
1) as DrawingDoc;
|
||||
}
|
||||
|
||||
private static string DrawingTemplatePath
|
||||
{
|
||||
get { return Path.Combine(Application.StartupPath, "Templates", "Blank.drwdot"); }
|
||||
}
|
||||
|
||||
private void textBox1_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
var model = sldWorks.ActiveDoc as ModelDoc2;
|
||||
var isDrawing = model is DrawingDoc;
|
||||
|
||||
if (isDrawing)
|
||||
{
|
||||
var drawingInfo = DrawingInfo.Parse(activeDocTitleBox.Text);
|
||||
|
||||
if (drawingInfo == null)
|
||||
return;
|
||||
|
||||
prefixTextBox.Text = $"{drawingInfo.JobNo} {drawingInfo.DrawingNo} PT";
|
||||
prefixTextBox.SelectionStart = prefixTextBox.Text.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -645,30 +231,29 @@ namespace ExportDXF.Forms
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdatePrefix()
|
||||
private void activeDocTitleBox_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
var model = sldWorks.ActiveDoc as ModelDoc2;
|
||||
UpdatePrefixFromActiveDocument();
|
||||
}
|
||||
|
||||
if (model == null)
|
||||
return;
|
||||
|
||||
var isDrawing = model is DrawingDoc;
|
||||
var title = model.GetTitle();
|
||||
|
||||
if (isDrawing)
|
||||
private void LogMessage(string message, Color? color = null)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
var drawingInfo = DrawingInfo.Parse(title);
|
||||
Invoke(new Action(() => LogMessage(message, color)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (drawingInfo == null)
|
||||
return;
|
||||
|
||||
prefixTextBox.Text = $"{drawingInfo.JobNo} {drawingInfo.DrawingNo} PT";
|
||||
prefixTextBox.SelectionStart = prefixTextBox.Text.Length;
|
||||
if (color.HasValue)
|
||||
{
|
||||
richTextBox1.AppendText(message + System.Environment.NewLine, color.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
prefixTextBox.Text = string.Empty;
|
||||
richTextBox1.AppendText(message + System.Environment.NewLine);
|
||||
}
|
||||
|
||||
richTextBox1.ScrollToCaret();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ExportDXF
|
||||
{
|
||||
public static class Helper
|
||||
{
|
||||
public static string GetNumWithSuffix(int i)
|
||||
{
|
||||
if (i >= 11 && i <= 13)
|
||||
return i.ToString() + "th";
|
||||
|
||||
var j = i % 10;
|
||||
|
||||
switch (j)
|
||||
{
|
||||
case 1: return i.ToString() + "st";
|
||||
case 2: return i.ToString() + "nd";
|
||||
case 3: return i.ToString() + "rd";
|
||||
default: return i.ToString() + "th";
|
||||
}
|
||||
}
|
||||
|
||||
public static void UncheckFlatPatternCornerTreatment(ModelDoc2 model)
|
||||
{
|
||||
var flatPattern = model.GetFeatureByTypeName("FlatPattern");
|
||||
var flatPatternFeatureData = flatPattern.GetDefinition() as FlatPatternFeatureData;
|
||||
|
||||
flatPatternFeatureData.CornerTreatment = false;
|
||||
flatPatternFeatureData.SimplifyBends = true;
|
||||
|
||||
var ret = flatPattern.ModifyDefinition(flatPatternFeatureData, model, null);
|
||||
}
|
||||
|
||||
public static bool SetFlatPatternSuppressionState(ModelDoc2 model, swComponentSuppressionState_e suppressionState)
|
||||
{
|
||||
|
||||
|
||||
var flatPattern = model.GetFeatureByTypeName("FlatPattern");
|
||||
|
||||
flatPattern.SetSuppression((int)suppressionState);
|
||||
|
||||
return flatPattern.IsSuppressed();
|
||||
}
|
||||
|
||||
public static string RemoveXmlTags(string input)
|
||||
{
|
||||
// Define the regular expression pattern to match XML tags
|
||||
string pattern = @"<[^>]+>";
|
||||
|
||||
// Replace all matches of the pattern with an empty string
|
||||
string result = Regex.Replace(input, pattern, "");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
using SolidWorks.Interop.sldworks;
|
||||
|
||||
namespace ExportDXF
|
||||
{
|
||||
public class Item
|
||||
{
|
||||
public string ItemNo { get; set; }
|
||||
|
||||
public string FileName { get; set; }
|
||||
|
||||
public string PartName { get; set; }
|
||||
|
||||
public string Configuration { get; set; }
|
||||
|
||||
public int Quantity { get; set; }
|
||||
|
||||
public string Description { get; set; }
|
||||
|
||||
public double Thickness { get; set; }
|
||||
|
||||
public double KFactor { get; set; }
|
||||
|
||||
public double BendRadius { get; set; }
|
||||
|
||||
public string Material { get; set; }
|
||||
|
||||
public Component2 Component { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.Services;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.Services;
|
||||
using ExportDXF.Utilities;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -52,7 +55,7 @@ namespace ExportDXF.ItemExtractors
|
||||
if (columnIndices.ItemNumber != -1)
|
||||
{
|
||||
var x = table.DisplayedText[rowIndex, columnIndices.ItemNumber];
|
||||
x = Helper.RemoveXmlTags(x);
|
||||
x = TextHelper.RemoveXmlTags(x);
|
||||
|
||||
double d;
|
||||
|
||||
@@ -69,7 +72,7 @@ namespace ExportDXF.ItemExtractors
|
||||
if (columnIndices.PartNumber != -1)
|
||||
{
|
||||
var x = table.DisplayedText[rowIndex, columnIndices.PartNumber];
|
||||
x = Helper.RemoveXmlTags(x);
|
||||
x = TextHelper.RemoveXmlTags(x);
|
||||
|
||||
item.PartName = x;
|
||||
|
||||
@@ -78,7 +81,7 @@ namespace ExportDXF.ItemExtractors
|
||||
if (columnIndices.Description != -1)
|
||||
{
|
||||
var x = table.DisplayedText[rowIndex, columnIndices.Description];
|
||||
x = Helper.RemoveXmlTags(x);
|
||||
x = TextHelper.RemoveXmlTags(x);
|
||||
|
||||
item.Description = x;
|
||||
}
|
||||
@@ -86,7 +89,7 @@ namespace ExportDXF.ItemExtractors
|
||||
if (columnIndices.Quantity != -1)
|
||||
{
|
||||
var qtyString = table.DisplayedText[rowIndex, columnIndices.Quantity];
|
||||
qtyString = Helper.RemoveXmlTags(qtyString);
|
||||
qtyString = TextHelper.RemoveXmlTags(qtyString);
|
||||
|
||||
int qty = 0;
|
||||
int.TryParse(qtyString, out qty);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using ExportDXF.Services;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ExportDXF.ItemExtractors
|
||||
{
|
||||
|
||||
13
ExportDXF/Models/DocumentType.cs
Normal file
13
ExportDXF/Models/DocumentType.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace ExportDXF.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Enumeration of SolidWorks document types.
|
||||
/// </summary>
|
||||
public enum DocumentType
|
||||
{
|
||||
Unknown,
|
||||
Part,
|
||||
Assembly,
|
||||
Drawing
|
||||
}
|
||||
}
|
||||
38
ExportDXF/Models/ExportContext.cs
Normal file
38
ExportDXF/Models/ExportContext.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using ExportDXF.ViewFlipDeciders;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Threading;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Context object containing all information needed for an export operation.
|
||||
/// </summary>
|
||||
public class ExportContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The document to be exported.
|
||||
/// </summary>
|
||||
public SolidWorksDocument ActiveDocument { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The view flip decider to determine if views should be flipped.
|
||||
/// </summary>
|
||||
public IViewFlipDecider ViewFlipDecider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Prefix to prepend to exported filenames.
|
||||
/// </summary>
|
||||
public string FilePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cancellation token for canceling the export operation.
|
||||
/// </summary>
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Callback for reporting progress and status messages.
|
||||
/// </summary>
|
||||
public Action<string, Color?> ProgressCallback { get; set; }
|
||||
}
|
||||
}
|
||||
65
ExportDXF/Models/Item.cs
Normal file
65
ExportDXF/Models/Item.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using SolidWorks.Interop.sldworks;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an item extracted from a BOM or assembly.
|
||||
/// </summary>
|
||||
public class Item
|
||||
{
|
||||
/// <summary>
|
||||
/// Item number from the BOM.
|
||||
/// </summary>
|
||||
public string ItemNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Part name or file name.
|
||||
/// </summary>
|
||||
public string PartName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuration name.
|
||||
/// </summary>
|
||||
public string Configuration { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Item description.
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Quantity of this item.
|
||||
/// </summary>
|
||||
public int Quantity { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Material specification.
|
||||
/// </summary>
|
||||
public string Material { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sheet metal thickness in millimeters.
|
||||
/// </summary>
|
||||
public double Thickness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sheet metal K-factor.
|
||||
/// </summary>
|
||||
public double KFactor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bend radius in millimeters.
|
||||
/// </summary>
|
||||
public double BendRadius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The exported DXF filename (without path or extension).
|
||||
/// </summary>
|
||||
public string FileName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The SolidWorks component reference.
|
||||
/// </summary>
|
||||
public Component2 Component { get; set; }
|
||||
}
|
||||
}
|
||||
30
ExportDXF/Models/SolidWorksDocument.cs
Normal file
30
ExportDXF/Models/SolidWorksDocument.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using ExportDXF.Models;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a SolidWorks document with essential metadata.
|
||||
/// </summary>
|
||||
public class SolidWorksDocument
|
||||
{
|
||||
/// <summary>
|
||||
/// The title/name of the document.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The full file path of the document.
|
||||
/// </summary>
|
||||
public string FilePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of document (Part, Assembly, or Drawing).
|
||||
/// </summary>
|
||||
public DocumentType DocumentType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The native SolidWorks document object (ModelDoc2, PartDoc, etc.).
|
||||
/// </summary>
|
||||
public object NativeDocument { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,54 @@
|
||||
using System;
|
||||
using ExportDXF.Forms;
|
||||
using ExportDXF.Services;
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ExportDXF
|
||||
{
|
||||
internal static class Program
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
private static void Main()
|
||||
static void Main()
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new Forms.MainForm());
|
||||
|
||||
// Simple DI setup - could use a DI container like Microsoft.Extensions.DependencyInjection
|
||||
var container = new ServiceContainer();
|
||||
|
||||
var mainForm = container.Resolve<MainForm>();
|
||||
|
||||
Application.Run(mainForm);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Simple dependency injection container.
|
||||
/// For production, consider using Microsoft.Extensions.DependencyInjection or similar.
|
||||
/// </summary>
|
||||
public class ServiceContainer
|
||||
{
|
||||
public MainForm Resolve<T>() where T : MainForm
|
||||
{
|
||||
// Create the dependency tree
|
||||
var solidWorksService = new SolidWorksService();
|
||||
|
||||
var bomExtractor = new BomExtractor();
|
||||
var partExporter = new PartExporter();
|
||||
var drawingExporter = new DrawingExporter();
|
||||
var bomExcelExporter = new BomExcelExporter();
|
||||
|
||||
var exportService = new DxfExportService(
|
||||
solidWorksService,
|
||||
bomExtractor,
|
||||
partExporter,
|
||||
drawingExporter,
|
||||
bomExcelExporter);
|
||||
|
||||
return new MainForm(solidWorksService, exportService);
|
||||
}
|
||||
}
|
||||
}
|
||||
396
ExportDXF/Services/BomExcelExporter.cs
Normal file
396
ExportDXF/Services/BomExcelExporter.cs
Normal file
@@ -0,0 +1,396 @@
|
||||
using ExportDXF.Utilities;
|
||||
using OfficeOpenXml;
|
||||
using OfficeOpenXml.Style;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for creating BOM Excel files.
|
||||
/// </summary>
|
||||
public interface IBomExcelExporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an Excel file containing the Bill of Materials.
|
||||
/// </summary>
|
||||
/// <param name="filepath">The full path where the Excel file will be saved.</param>
|
||||
/// <param name="items">The list of items to include in the BOM.</param>
|
||||
void CreateBOMExcelFile(string filepath, IList<Item> items);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service for creating Excel files containing Bill of Materials data.
|
||||
/// </summary>
|
||||
public class BomExcelExporter : IBomExcelExporter
|
||||
{
|
||||
private const string DEFAULT_TEMPLATE_FILENAME = "BomTemplate.xlsx";
|
||||
private const string DEFAULT_SHEET_NAME = "Parts";
|
||||
private const int HEADER_ROW = 1;
|
||||
private const int DATA_START_ROW = 2;
|
||||
private const double COLUMN_PADDING = 2.0;
|
||||
private const int MAX_COLUMN_WIDTH = 50;
|
||||
|
||||
private readonly string _templatePath;
|
||||
private readonly BomExcelSettings _settings;
|
||||
|
||||
public BomExcelExporter() : this(GetDefaultTemplatePath(), new BomExcelSettings())
|
||||
{
|
||||
}
|
||||
|
||||
public BomExcelExporter(string templatePath) : this(templatePath, new BomExcelSettings())
|
||||
{
|
||||
}
|
||||
|
||||
public BomExcelExporter(string templatePath, BomExcelSettings settings)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(templatePath))
|
||||
throw new ArgumentException("Template path cannot be null or empty.", nameof(templatePath));
|
||||
|
||||
_templatePath = templatePath;
|
||||
_settings = settings ?? new BomExcelSettings();
|
||||
|
||||
// Set EPPlus license context (required for newer versions)
|
||||
//ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an Excel file containing the Bill of Materials.
|
||||
/// </summary>
|
||||
public void CreateBOMExcelFile(string filepath, IList<Item> items)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(filepath))
|
||||
throw new ArgumentException("Filepath cannot be null or empty.", nameof(filepath));
|
||||
|
||||
if (items == null)
|
||||
throw new ArgumentNullException(nameof(items));
|
||||
|
||||
ValidateAndPrepareFile(filepath);
|
||||
|
||||
using (var package = new ExcelPackage(new FileInfo(filepath)))
|
||||
{
|
||||
var worksheet = GetOrCreateWorksheet(package);
|
||||
|
||||
PopulateWorksheet(worksheet, items);
|
||||
FormatWorksheet(worksheet);
|
||||
|
||||
package.Save();
|
||||
}
|
||||
}
|
||||
|
||||
#region Worksheet Management
|
||||
|
||||
private ExcelWorksheet GetOrCreateWorksheet(ExcelPackage package)
|
||||
{
|
||||
var worksheet = package.Workbook.Worksheets[DEFAULT_SHEET_NAME];
|
||||
|
||||
if (worksheet == null)
|
||||
{
|
||||
if (_settings.CreateWorksheetIfMissing)
|
||||
{
|
||||
worksheet = package.Workbook.Worksheets.Add(DEFAULT_SHEET_NAME);
|
||||
CreateDefaultHeaders(worksheet);
|
||||
}
|
||||
else
|
||||
{
|
||||
var availableSheets = string.Join(", ",
|
||||
package.Workbook.Worksheets.Select(ws => ws.Name));
|
||||
|
||||
throw new InvalidOperationException(
|
||||
$"Worksheet '{DEFAULT_SHEET_NAME}' not found in template. " +
|
||||
$"Available sheets: {availableSheets}");
|
||||
}
|
||||
}
|
||||
|
||||
return worksheet;
|
||||
}
|
||||
|
||||
private void CreateDefaultHeaders(ExcelWorksheet worksheet)
|
||||
{
|
||||
var headers = new[]
|
||||
{
|
||||
"Item No", "File Name", "Qty", "Description", "Part Name",
|
||||
"Configuration", "Thickness", "Material", "K-Factor", "Bend Radius"
|
||||
};
|
||||
|
||||
for (int i = 0; i < headers.Length; i++)
|
||||
{
|
||||
var cell = worksheet.Cells[HEADER_ROW, i + 1];
|
||||
cell.Value = headers[i];
|
||||
|
||||
if (_settings.FormatHeaders)
|
||||
{
|
||||
cell.Style.Font.Bold = true;
|
||||
cell.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||
cell.Style.Fill.BackgroundColor.SetColor(Color.LightGray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Population
|
||||
|
||||
private void PopulateWorksheet(ExcelWorksheet worksheet, IList<Item> items)
|
||||
{
|
||||
var filteredItems = _settings.SkipItemsWithoutFiles
|
||||
? items.Where(i => !string.IsNullOrEmpty(i.FileName)).ToList()
|
||||
: items.ToList();
|
||||
|
||||
for (int i = 0; i < filteredItems.Count; i++)
|
||||
{
|
||||
var item = filteredItems[i];
|
||||
var row = DATA_START_ROW + i;
|
||||
|
||||
WriteItemToRow(worksheet, row, item);
|
||||
}
|
||||
|
||||
// Add summary information if enabled
|
||||
if (_settings.AddSummary && filteredItems.Count > 0)
|
||||
{
|
||||
AddSummarySection(worksheet, filteredItems, DATA_START_ROW + filteredItems.Count + 2);
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteItemToRow(ExcelWorksheet worksheet, int row, Item item)
|
||||
{
|
||||
int col = 1;
|
||||
|
||||
// Item No
|
||||
worksheet.Cells[row, col++].Value = FormatItemNumber(item.ItemNo);
|
||||
|
||||
// File Name
|
||||
worksheet.Cells[row, col++].Value = item.FileName;
|
||||
|
||||
// Quantity
|
||||
worksheet.Cells[row, col++].Value = item.Quantity;
|
||||
|
||||
// Description
|
||||
worksheet.Cells[row, col++].Value = CleanDescription(item.Description);
|
||||
|
||||
// Part Name
|
||||
worksheet.Cells[row, col++].Value = item.PartName;
|
||||
|
||||
// Configuration
|
||||
worksheet.Cells[row, col++].Value = item.Configuration;
|
||||
|
||||
// Thickness (only if > 0)
|
||||
worksheet.Cells[row, col++].Value = GetFormattedNumericValue(item.Thickness, _settings.ThicknessDecimalPlaces);
|
||||
|
||||
// Material
|
||||
worksheet.Cells[row, col++].Value = item.Material;
|
||||
|
||||
// K-Factor (only if > 0)
|
||||
worksheet.Cells[row, col++].Value = GetFormattedNumericValue(item.KFactor, _settings.KFactorDecimalPlaces);
|
||||
|
||||
// Bend Radius (only if > 0)
|
||||
worksheet.Cells[row, col++].Value = GetFormattedNumericValue(item.BendRadius, _settings.BendRadiusDecimalPlaces);
|
||||
|
||||
// Apply row formatting
|
||||
if (_settings.AlternateRowColors && row % 2 == 0)
|
||||
{
|
||||
var rowRange = worksheet.Cells[row, 1, row, col - 1];
|
||||
rowRange.Style.Fill.PatternType = ExcelFillStyle.Solid;
|
||||
rowRange.Style.Fill.BackgroundColor.SetColor(Color.FromArgb(240, 240, 240));
|
||||
}
|
||||
}
|
||||
|
||||
private void AddSummarySection(ExcelWorksheet worksheet, IList<Item> items, int startRow)
|
||||
{
|
||||
worksheet.Cells[startRow, 1].Value = "SUMMARY";
|
||||
worksheet.Cells[startRow, 1].Style.Font.Bold = true;
|
||||
worksheet.Cells[startRow, 1].Style.Font.Size = 12;
|
||||
|
||||
startRow += 2;
|
||||
|
||||
// Total items
|
||||
worksheet.Cells[startRow, 1].Value = "Total Items:";
|
||||
worksheet.Cells[startRow, 2].Value = items.Count;
|
||||
|
||||
// Total quantity
|
||||
var totalQty = items.Sum(i => i.Quantity);
|
||||
worksheet.Cells[startRow + 1, 1].Value = "Total Quantity:";
|
||||
worksheet.Cells[startRow + 1, 2].Value = totalQty;
|
||||
|
||||
// Unique materials
|
||||
var uniqueMaterials = items.Where(i => !string.IsNullOrEmpty(i.Material))
|
||||
.Select(i => i.Material)
|
||||
.Distinct()
|
||||
.Count();
|
||||
|
||||
worksheet.Cells[startRow + 2, 1].Value = "Unique Materials:";
|
||||
worksheet.Cells[startRow + 2, 2].Value = uniqueMaterials;
|
||||
|
||||
// Thickness range
|
||||
var thicknesses = items.Where(i => i.Thickness > 0).Select(i => i.Thickness).ToList();
|
||||
if (thicknesses.Any())
|
||||
{
|
||||
worksheet.Cells[startRow + 3, 1].Value = "Thickness Range:";
|
||||
worksheet.Cells[startRow + 3, 2].Value = $"{thicknesses.Min():F1} - {thicknesses.Max():F1} mm";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Data Formatting
|
||||
|
||||
private string FormatItemNumber(string itemNo)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(itemNo))
|
||||
return string.Empty;
|
||||
|
||||
// Try to parse as number and pad with zeros if it's a simple number
|
||||
if (int.TryParse(itemNo, out int number))
|
||||
{
|
||||
return number.ToString().PadLeft(_settings.ItemNumberPadding, '0');
|
||||
}
|
||||
|
||||
return itemNo;
|
||||
}
|
||||
|
||||
private string CleanDescription(string description)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(description))
|
||||
return string.Empty;
|
||||
|
||||
// Remove any remaining XML tags that might have been missed
|
||||
description = TextHelper.RemoveXmlTags(description);
|
||||
|
||||
// Trim and normalize whitespace
|
||||
return System.Text.RegularExpressions.Regex.Replace(description.Trim(), @"\s+", " ");
|
||||
}
|
||||
|
||||
private object GetFormattedNumericValue(double value, int decimalPlaces)
|
||||
{
|
||||
if (value <= 0)
|
||||
return null;
|
||||
|
||||
return Math.Round(value, decimalPlaces);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Worksheet Formatting
|
||||
|
||||
private void FormatWorksheet(ExcelWorksheet worksheet)
|
||||
{
|
||||
if (worksheet.Dimension == null)
|
||||
return;
|
||||
|
||||
// Auto-fit columns with limits
|
||||
for (int col = 1; col <= worksheet.Dimension.Columns; col++)
|
||||
{
|
||||
worksheet.Column(col).AutoFit();
|
||||
|
||||
if (worksheet.Column(col).Width > MAX_COLUMN_WIDTH)
|
||||
worksheet.Column(col).Width = MAX_COLUMN_WIDTH;
|
||||
else
|
||||
worksheet.Column(col).Width += COLUMN_PADDING;
|
||||
}
|
||||
|
||||
// Add borders if enabled
|
||||
if (_settings.AddBorders)
|
||||
{
|
||||
var dataRange = worksheet.Cells[HEADER_ROW, 1, worksheet.Dimension.End.Row, worksheet.Dimension.End.Column];
|
||||
dataRange.Style.Border.Top.Style = ExcelBorderStyle.Thin;
|
||||
dataRange.Style.Border.Left.Style = ExcelBorderStyle.Thin;
|
||||
dataRange.Style.Border.Right.Style = ExcelBorderStyle.Thin;
|
||||
dataRange.Style.Border.Bottom.Style = ExcelBorderStyle.Thin;
|
||||
}
|
||||
|
||||
// Format numeric columns
|
||||
FormatNumericColumns(worksheet);
|
||||
|
||||
// Freeze header row if enabled
|
||||
if (_settings.FreezeHeaderRow)
|
||||
{
|
||||
worksheet.View.FreezePanes(DATA_START_ROW, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void FormatNumericColumns(ExcelWorksheet worksheet)
|
||||
{
|
||||
if (worksheet.Dimension == null)
|
||||
return;
|
||||
|
||||
var lastRow = worksheet.Dimension.End.Row;
|
||||
|
||||
// Format quantity column (assuming it's column 3)
|
||||
if (worksheet.Dimension.Columns >= 3)
|
||||
{
|
||||
var qtyRange = worksheet.Cells[DATA_START_ROW, 3, lastRow, 3];
|
||||
qtyRange.Style.Numberformat.Format = "0";
|
||||
qtyRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Center;
|
||||
}
|
||||
|
||||
// Format thickness column (assuming it's column 7)
|
||||
if (worksheet.Dimension.Columns >= 7)
|
||||
{
|
||||
var thicknessRange = worksheet.Cells[DATA_START_ROW, 7, lastRow, 7];
|
||||
thicknessRange.Style.Numberformat.Format = $"0.{new string('0', _settings.ThicknessDecimalPlaces)}";
|
||||
thicknessRange.Style.HorizontalAlignment = ExcelHorizontalAlignment.Right;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region File Management
|
||||
|
||||
private void ValidateAndPrepareFile(string filepath)
|
||||
{
|
||||
var directory = Path.GetDirectoryName(filepath);
|
||||
|
||||
if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory))
|
||||
{
|
||||
Directory.CreateDirectory(directory);
|
||||
}
|
||||
|
||||
if (UseTemplate())
|
||||
{
|
||||
CopyTemplateToDestination(filepath);
|
||||
}
|
||||
else if (File.Exists(filepath))
|
||||
{
|
||||
File.Delete(filepath); // Remove existing file to start fresh
|
||||
}
|
||||
}
|
||||
|
||||
private bool UseTemplate()
|
||||
{
|
||||
return !string.IsNullOrEmpty(_templatePath) && File.Exists(_templatePath);
|
||||
}
|
||||
|
||||
private void CopyTemplateToDestination(string filepath)
|
||||
{
|
||||
if (!File.Exists(_templatePath))
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
$"BOM template file not found at: {_templatePath}. " +
|
||||
"Either provide a valid template or enable CreateWorksheetIfMissing.",
|
||||
_templatePath);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File.Copy(_templatePath, filepath, overwrite: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new IOException($"Failed to copy template to {filepath}: {ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetDefaultTemplatePath()
|
||||
{
|
||||
return Path.Combine(
|
||||
AppDomain.CurrentDomain.BaseDirectory,
|
||||
"Templates",
|
||||
DEFAULT_TEMPLATE_FILENAME);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
64
ExportDXF/Services/BomExcelSettings.cs
Normal file
64
ExportDXF/Services/BomExcelSettings.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Configuration settings for BOM Excel export.
|
||||
/// </summary>
|
||||
public class BomExcelSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Create the worksheet if it doesn't exist in the template.
|
||||
/// </summary>
|
||||
public bool CreateWorksheetIfMissing { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Skip items that don't have an associated DXF file.
|
||||
/// </summary>
|
||||
public bool SkipItemsWithoutFiles { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Add summary section with totals and statistics.
|
||||
/// </summary>
|
||||
public bool AddSummary { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Format header row with bold text and background color.
|
||||
/// </summary>
|
||||
public bool FormatHeaders { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Add borders around all cells.
|
||||
/// </summary>
|
||||
public bool AddBorders { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Freeze the header row for easier scrolling.
|
||||
/// </summary>
|
||||
public bool FreezeHeaderRow { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Alternate row background colors for easier reading.
|
||||
/// </summary>
|
||||
public bool AlternateRowColors { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Number of decimal places for thickness values.
|
||||
/// </summary>
|
||||
public int ThicknessDecimalPlaces { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Number of decimal places for K-Factor values.
|
||||
/// </summary>
|
||||
public int KFactorDecimalPlaces { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Number of decimal places for bend radius values.
|
||||
/// </summary>
|
||||
public int BendRadiusDecimalPlaces { get; set; } = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum number of digits for item numbers (pad with zeros).
|
||||
/// </summary>
|
||||
public int ItemNumberPadding { get; set; } = 2;
|
||||
}
|
||||
}
|
||||
66
ExportDXF/Services/BomExtractor.cs
Normal file
66
ExportDXF/Services/BomExtractor.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.ItemExtractors;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for extracting items from a Bill of Materials (BOM).
|
||||
/// </summary>
|
||||
public interface IBomExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// Extracts items from all BOM tables in a drawing document.
|
||||
/// </summary>
|
||||
/// <param name="drawing">The drawing document containing BOM tables.</param>
|
||||
/// <param name="progressCallback">Optional callback for progress updates.</param>
|
||||
/// <returns>A list of extracted items.</returns>
|
||||
List<Item> ExtractFromDrawing(DrawingDoc drawing, Action<string, Color?> progressCallback);
|
||||
}
|
||||
|
||||
public class BomExtractor : IBomExtractor
|
||||
{
|
||||
public List<Item> ExtractFromDrawing(DrawingDoc drawing, Action<string, Color?> progressCallback)
|
||||
{
|
||||
if (drawing == null)
|
||||
throw new ArgumentNullException(nameof(drawing));
|
||||
|
||||
var bomTables = drawing.GetBomTables();
|
||||
|
||||
if (bomTables.Count == 0)
|
||||
{
|
||||
progressCallback?.Invoke("Error: Bill of materials not found.", Color.Red);
|
||||
return new List<Item>();
|
||||
}
|
||||
|
||||
progressCallback?.Invoke($"Found {bomTables.Count} BOM table(s)", null);
|
||||
|
||||
var allItems = new List<Item>();
|
||||
|
||||
foreach (var bom in bomTables)
|
||||
{
|
||||
try
|
||||
{
|
||||
var extractor = new BomItemExtractor(bom)
|
||||
{
|
||||
SkipHiddenRows = true
|
||||
};
|
||||
|
||||
progressCallback?.Invoke($"Fetching components from {bom.BomFeature.Name}", null);
|
||||
|
||||
var items = extractor.GetItems();
|
||||
allItems.AddRange(items);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
progressCallback?.Invoke($"Failed to extract from {bom.BomFeature.Name}: {ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
return allItems;
|
||||
}
|
||||
}
|
||||
}
|
||||
85
ExportDXF/Services/DrawingExporter.cs
Normal file
85
ExportDXF/Services/DrawingExporter.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for exporting drawing documents to PDF format.
|
||||
/// </summary>
|
||||
public interface IDrawingExporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports a drawing document to PDF format.
|
||||
/// </summary>
|
||||
/// <param name="drawing">The drawing document to export.</param>
|
||||
/// <param name="saveDirectory">The directory where the PDF file will be saved.</param>
|
||||
/// <param name="progressCallback">Optional callback for progress updates.</param>
|
||||
void ExportToPdf(DrawingDoc drawing, string saveDirectory, Action<string, Color?> progressCallback);
|
||||
}
|
||||
|
||||
|
||||
public class DrawingExporter : IDrawingExporter
|
||||
{
|
||||
public void ExportToPdf(DrawingDoc drawing, string saveDirectory, Action<string, Color?> progressCallback)
|
||||
{
|
||||
if (drawing == null)
|
||||
throw new ArgumentNullException(nameof(drawing));
|
||||
|
||||
if (string.IsNullOrWhiteSpace(saveDirectory))
|
||||
throw new ArgumentException("Save directory cannot be null or empty.", nameof(saveDirectory));
|
||||
|
||||
try
|
||||
{
|
||||
var pdfFileName = GetPdfFileName(drawing);
|
||||
var pdfPath = Path.Combine(saveDirectory, pdfFileName);
|
||||
|
||||
var model = drawing as ModelDoc2;
|
||||
var sldWorks = (SldWorks)Activator.CreateInstance(Type.GetTypeFromProgID("SldWorks.Application"));
|
||||
|
||||
var exportData = sldWorks.GetExportFileData(
|
||||
(int)swExportDataFileType_e.swExportPdfData) as ExportPdfData;
|
||||
|
||||
exportData.ViewPdfAfterSaving = false;
|
||||
exportData.SetSheets(
|
||||
(int)swExportDataSheetsToExport_e.swExportData_ExportAllSheets,
|
||||
drawing);
|
||||
|
||||
int errors = 0;
|
||||
int warnings = 0;
|
||||
|
||||
var modelExtension = model.Extension;
|
||||
modelExtension.SaveAs(
|
||||
pdfPath,
|
||||
(int)swSaveAsVersion_e.swSaveAsCurrentVersion,
|
||||
(int)swSaveAsOptions_e.swSaveAsOptions_Silent,
|
||||
exportData,
|
||||
ref errors,
|
||||
ref warnings);
|
||||
|
||||
if (errors == 0)
|
||||
{
|
||||
progressCallback?.Invoke($"Saved drawing to PDF: \"{pdfFileName}\"", Color.Green);
|
||||
}
|
||||
else
|
||||
{
|
||||
progressCallback?.Invoke($"PDF export completed with errors: {errors}", Color.Yellow);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
progressCallback?.Invoke($"Failed to export PDF: {ex.Message}", Color.Red);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPdfFileName(DrawingDoc drawing)
|
||||
{
|
||||
var model = drawing as ModelDoc2;
|
||||
var modelFilePath = model.GetPathName();
|
||||
return Path.GetFileNameWithoutExtension(modelFilePath) + ".pdf";
|
||||
}
|
||||
}
|
||||
}
|
||||
413
ExportDXF/Services/DxfExportService.cs
Normal file
413
ExportDXF/Services/DxfExportService.cs
Normal file
@@ -0,0 +1,413 @@
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.ItemExtractors;
|
||||
using ExportDXF.Models;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
public interface IDxfExportService
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports the document specified in the context to DXF format.
|
||||
/// </summary>
|
||||
/// <param name="context">The export context containing all necessary information.</param>
|
||||
void Export(ExportContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for orchestrating the export of SolidWorks documents to DXF format.
|
||||
/// </summary>
|
||||
public class DxfExportService : IDxfExportService
|
||||
{
|
||||
private const string DRAWING_TEMPLATE_FOLDER = "Templates";
|
||||
private const string DRAWING_TEMPLATE_FILE = "Blank.drwdot";
|
||||
|
||||
private readonly ISolidWorksService _solidWorksService;
|
||||
private readonly IBomExtractor _bomExtractor;
|
||||
private readonly IPartExporter _partExporter;
|
||||
private readonly IDrawingExporter _drawingExporter;
|
||||
private readonly IBomExcelExporter _bomExcelExporter;
|
||||
|
||||
public DxfExportService(
|
||||
ISolidWorksService solidWorksService,
|
||||
IBomExtractor bomExtractor,
|
||||
IPartExporter partExporter,
|
||||
IDrawingExporter drawingExporter,
|
||||
IBomExcelExporter bomExcelExporter)
|
||||
{
|
||||
_solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService));
|
||||
_bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor));
|
||||
_partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter));
|
||||
_drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter));
|
||||
_bomExcelExporter = bomExcelExporter ?? throw new ArgumentNullException(nameof(bomExcelExporter));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports the document specified in the context to DXF format.
|
||||
/// </summary>
|
||||
public void Export(ExportContext context)
|
||||
{
|
||||
if (context == null)
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
|
||||
ValidateContext(context);
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
|
||||
try
|
||||
{
|
||||
_solidWorksService.EnableUserControl(false);
|
||||
|
||||
switch (context.ActiveDocument.DocumentType)
|
||||
{
|
||||
case DocumentType.Part:
|
||||
ExportPart(context);
|
||||
break;
|
||||
|
||||
case DocumentType.Assembly:
|
||||
ExportAssembly(context);
|
||||
break;
|
||||
|
||||
case DocumentType.Drawing:
|
||||
ExportDrawing(context);
|
||||
break;
|
||||
|
||||
default:
|
||||
LogProgress(context, "Unknown document type.", Color.Red);
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_solidWorksService.EnableUserControl(true);
|
||||
|
||||
var duration = DateTime.Now - startTime;
|
||||
LogProgress(context, $"Run time: {duration.ToReadableFormat()}", null);
|
||||
}
|
||||
}
|
||||
|
||||
#region Export Methods by Document Type
|
||||
|
||||
private void ExportPart(ExportContext context)
|
||||
{
|
||||
LogProgress(context, "Active document is a Part", null);
|
||||
|
||||
var part = context.ActiveDocument.NativeDocument as PartDoc;
|
||||
if (part == null)
|
||||
{
|
||||
LogProgress(context, "Failed to get part document.", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
var saveDirectory = PromptUserForDirectory(context);
|
||||
if (saveDirectory == null)
|
||||
{
|
||||
LogProgress(context, "Canceled", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
_partExporter.ExportSinglePart(part, saveDirectory, context);
|
||||
}
|
||||
|
||||
private void ExportAssembly(ExportContext context)
|
||||
{
|
||||
LogProgress(context, "Active document is an Assembly", null);
|
||||
LogProgress(context, "Fetching components...", null);
|
||||
|
||||
var assembly = context.ActiveDocument.NativeDocument as AssemblyDoc;
|
||||
if (assembly == null)
|
||||
{
|
||||
LogProgress(context, "Failed to get assembly document.", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
var items = ExtractItemsFromAssembly(assembly, context);
|
||||
|
||||
if (items == null || items.Count == 0)
|
||||
{
|
||||
LogProgress(context, "No items found in assembly.", Color.Yellow);
|
||||
return;
|
||||
}
|
||||
|
||||
LogProgress(context, $"Found {items.Count} item(s).", null);
|
||||
|
||||
var saveDirectory = PromptUserForDirectory(context);
|
||||
if (saveDirectory == null)
|
||||
{
|
||||
LogProgress(context, "Canceled", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
ExportItems(items, saveDirectory, context);
|
||||
}
|
||||
|
||||
private void ExportDrawing(ExportContext context)
|
||||
{
|
||||
LogProgress(context, "Active document is a Drawing", null);
|
||||
LogProgress(context, "Finding BOM tables...", null);
|
||||
|
||||
var drawing = context.ActiveDocument.NativeDocument as DrawingDoc;
|
||||
if (drawing == null)
|
||||
{
|
||||
LogProgress(context, "Failed to get drawing document.", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
var items = _bomExtractor.ExtractFromDrawing(drawing, context.ProgressCallback);
|
||||
|
||||
if (items == null || items.Count == 0)
|
||||
{
|
||||
LogProgress(context, "Error: Bill of materials not found.", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
LogProgress(context, $"Found {items.Count} component(s)", null);
|
||||
|
||||
var saveDirectory = PromptUserForDirectory(context);
|
||||
if (saveDirectory == null)
|
||||
{
|
||||
LogProgress(context, "Canceled", Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
// Export drawing to PDF first
|
||||
_drawingExporter.ExportToPdf(drawing, saveDirectory, context.ProgressCallback);
|
||||
|
||||
// Then export parts to DXF
|
||||
ExportItems(items, saveDirectory, context);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Item Processing
|
||||
|
||||
private List<Item> ExtractItemsFromAssembly(AssemblyDoc assembly, ExportContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var extractor = new AssemblyItemExtractor(assembly)
|
||||
{
|
||||
TopLevelOnly = false
|
||||
};
|
||||
|
||||
return extractor.GetItems();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"Failed to extract items from assembly: {ex.Message}", Color.Red);
|
||||
return new List<Item>();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportItems(List<Item> items, string saveDirectory, ExportContext context)
|
||||
{
|
||||
DrawingDoc templateDrawing = null;
|
||||
|
||||
try
|
||||
{
|
||||
templateDrawing = CreateTemplateDrawing();
|
||||
|
||||
LogProgress(context, "", null);
|
||||
|
||||
int successCount = 0;
|
||||
int failureCount = 0;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (context.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
LogProgress(context, "Export canceled by user.", Color.Yellow);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_partExporter.ExportItem(item, templateDrawing, saveDirectory, context);
|
||||
|
||||
if (!string.IsNullOrEmpty(item.FileName))
|
||||
successCount++;
|
||||
else
|
||||
failureCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", Color.Red);
|
||||
failureCount++;
|
||||
}
|
||||
|
||||
LogProgress(context, "", null);
|
||||
}
|
||||
|
||||
LogProgress(context, $"Export complete: {successCount} succeeded, {failureCount} failed",
|
||||
failureCount > 0 ? Color.Yellow : Color.Green);
|
||||
|
||||
// Create BOM Excel file
|
||||
CreateBomExcelFile(items, saveDirectory, context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseTemplateDrawing(templateDrawing);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Template Drawing Management
|
||||
|
||||
private DrawingDoc CreateTemplateDrawing()
|
||||
{
|
||||
var sldWorks = _solidWorksService.GetNativeSldWorks();
|
||||
|
||||
if (sldWorks == null)
|
||||
throw new InvalidOperationException("SolidWorks service is not connected.");
|
||||
|
||||
var templatePath = GetDrawingTemplatePath();
|
||||
|
||||
if (!File.Exists(templatePath))
|
||||
{
|
||||
throw new FileNotFoundException(
|
||||
$"Drawing template not found at: {templatePath}",
|
||||
templatePath);
|
||||
}
|
||||
|
||||
var drawing = sldWorks.NewDocument(
|
||||
templatePath,
|
||||
(int)swDwgPaperSizes_e.swDwgPaperDsize,
|
||||
1,
|
||||
1) as DrawingDoc;
|
||||
|
||||
if (drawing == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Failed to create drawing from template. Please check the template file.");
|
||||
}
|
||||
|
||||
return drawing;
|
||||
}
|
||||
|
||||
private void CloseTemplateDrawing(DrawingDoc drawing)
|
||||
{
|
||||
if (drawing == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var model = drawing as ModelDoc2;
|
||||
var title = model?.GetTitle();
|
||||
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
{
|
||||
_solidWorksService.CloseDocument(title);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
}
|
||||
|
||||
private string GetDrawingTemplatePath()
|
||||
{
|
||||
return Path.Combine(
|
||||
Application.StartupPath,
|
||||
DRAWING_TEMPLATE_FOLDER,
|
||||
DRAWING_TEMPLATE_FILE);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BOM Excel Creation
|
||||
|
||||
private void CreateBomExcelFile(List<Item> items, string saveDirectory, ExportContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var bomFileName = GetBomFileName(context.FilePrefix);
|
||||
var bomFilePath = Path.Combine(saveDirectory, bomFileName + ".xlsx");
|
||||
|
||||
_bomExcelExporter.CreateBOMExcelFile(bomFilePath, items);
|
||||
|
||||
LogProgress(context, $"Created BOM Excel file: {bomFileName}.xlsx", Color.Green);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"Failed to create BOM Excel: {ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetBomFileName(string prefix)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prefix))
|
||||
return "BOM";
|
||||
|
||||
var drawingInfo = DrawingInfo.Parse(prefix);
|
||||
|
||||
if (drawingInfo != null)
|
||||
{
|
||||
return $"{drawingInfo.JobNo} {drawingInfo.DrawingNo} BOM";
|
||||
}
|
||||
|
||||
return prefix.Trim() + " BOM";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region User Interaction
|
||||
|
||||
private string PromptUserForDirectory(ExportContext context)
|
||||
{
|
||||
// Check if already canceled
|
||||
if (context.CancellationToken.IsCancellationRequested)
|
||||
return null;
|
||||
|
||||
string selectedPath = null;
|
||||
|
||||
// Must run on STA thread for FolderBrowserDialog
|
||||
var thread = new System.Threading.Thread(() =>
|
||||
{
|
||||
using (var dialog = new FolderBrowserDialog())
|
||||
{
|
||||
dialog.Description = "Where do you want to save the DXF files?";
|
||||
dialog.ShowNewFolderButton = true;
|
||||
|
||||
if (dialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
selectedPath = dialog.SelectedPath;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
thread.SetApartmentState(System.Threading.ApartmentState.STA);
|
||||
thread.Start();
|
||||
thread.Join();
|
||||
|
||||
return selectedPath;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private void ValidateContext(ExportContext context)
|
||||
{
|
||||
if (context.ActiveDocument == null)
|
||||
throw new ArgumentException("ActiveDocument cannot be null.", nameof(context));
|
||||
|
||||
if (context.ProgressCallback == null)
|
||||
throw new ArgumentException("ProgressCallback cannot be null.", nameof(context));
|
||||
}
|
||||
|
||||
private void LogProgress(ExportContext context, string message, Color? color)
|
||||
{
|
||||
context.ProgressCallback?.Invoke(message, color);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
300
ExportDXF/Services/PartExporter.cs
Normal file
300
ExportDXF/Services/PartExporter.cs
Normal file
@@ -0,0 +1,300 @@
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.Utilities;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for exporting parts to DXF format.
|
||||
/// </summary>
|
||||
public interface IPartExporter
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports a single part document to DXF.
|
||||
/// </summary>
|
||||
/// <param name="part">The part document to export.</param>
|
||||
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param>
|
||||
/// <param name="context">The export context.</param>
|
||||
void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Exports an item (component from BOM or assembly) to DXF.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to export.</param>
|
||||
/// <param name="templateDrawing">The template drawing to use for creating flat patterns.</param>
|
||||
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param>
|
||||
/// <param name="context">The export context.</param>
|
||||
void ExportItem(Item item, DrawingDoc templateDrawing, string saveDirectory, ExportContext context);
|
||||
}
|
||||
|
||||
public class PartExporter : IPartExporter
|
||||
{
|
||||
public void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
|
||||
{
|
||||
if (part == null)
|
||||
throw new ArgumentNullException(nameof(part));
|
||||
|
||||
var model = part as ModelDoc2;
|
||||
var activeConfig = model.GetActiveConfiguration() as SolidWorks.Interop.sldworks.Configuration;
|
||||
var originalConfigName = activeConfig?.Name;
|
||||
|
||||
try
|
||||
{
|
||||
var fileName = GetSinglePartFileName(model, context.FilePrefix);
|
||||
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
||||
|
||||
var templateDrawing = CreateTemplateDrawing(context);
|
||||
|
||||
try
|
||||
{
|
||||
ExportPartToDxf(part, originalConfigName, templateDrawing, savePath, context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseTemplateDrawing(templateDrawing, context);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (originalConfigName != null)
|
||||
{
|
||||
model.ShowConfiguration(originalConfigName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ExportItem(Item item, DrawingDoc templateDrawing, string saveDirectory, ExportContext context)
|
||||
{
|
||||
if (item?.Component == null)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"Item {item?.ItemNo} - skipped, no component", Color.Yellow);
|
||||
return;
|
||||
}
|
||||
|
||||
context.CancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
item.Component.SetLightweightToResolved();
|
||||
|
||||
var model = item.Component.GetModelDoc2() as ModelDoc2;
|
||||
var part = model as PartDoc;
|
||||
|
||||
if (part == null)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"{item.ItemNo} - skipped, not a part document", null);
|
||||
return;
|
||||
}
|
||||
|
||||
EnrichItemWithMetadata(item, model, part);
|
||||
|
||||
var fileName = GetItemFileName(item, context.FilePrefix);
|
||||
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
||||
|
||||
if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, templateDrawing, savePath, context))
|
||||
{
|
||||
item.FileName = Path.GetFileNameWithoutExtension(savePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogExportFailure(item, context);
|
||||
}
|
||||
}
|
||||
|
||||
private void EnrichItemWithMetadata(Item item, ModelDoc2 model, PartDoc part)
|
||||
{
|
||||
// Get sheet metal properties
|
||||
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 config = item.Component.ReferencedConfiguration;
|
||||
item.Description = model.Extension.CustomPropertyManager[config].Get("Description");
|
||||
item.Description = model.Extension.CustomPropertyManager[""].Get("Description");
|
||||
item.Description = TextHelper.RemoveXmlTags(item.Description);
|
||||
|
||||
// Get material
|
||||
item.Material = part.GetMaterialPropertyName2(config, out _);
|
||||
}
|
||||
|
||||
private bool ExportPartToDxf(
|
||||
PartDoc part,
|
||||
string configName,
|
||||
DrawingDoc templateDrawing,
|
||||
string savePath,
|
||||
ExportContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var model = part as ModelDoc2;
|
||||
|
||||
if (!model.IsSheetMetal())
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - skipped, not sheet metal", null);
|
||||
return false;
|
||||
}
|
||||
|
||||
SolidWorksHelper.ConfigureFlatPatternSettings(model);
|
||||
|
||||
var sheet = templateDrawing.IGetCurrentSheet();
|
||||
var modelName = Path.GetFileNameWithoutExtension(model.GetPathName());
|
||||
sheet.SetName(modelName);
|
||||
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Creating flat pattern", null);
|
||||
|
||||
var view = CreateFlatPatternView(templateDrawing, model, configName);
|
||||
|
||||
if (view == null)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Failed to create flat pattern", Color.Red);
|
||||
return false;
|
||||
}
|
||||
|
||||
ConfigureFlatPatternView(view, templateDrawing, model, configName, context);
|
||||
|
||||
if (context.ViewFlipDecider?.ShouldFlip(view) == true)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Flipped view", Color.Blue);
|
||||
view.FlipView = true;
|
||||
}
|
||||
|
||||
var drawingModel = templateDrawing as ModelDoc2;
|
||||
drawingModel.SaveAs(savePath);
|
||||
|
||||
AddEtchLines(savePath);
|
||||
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Saved to \"{savePath}\"", Color.Green);
|
||||
|
||||
DeleteView(drawingModel, view);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"Export failed: {ex.Message}", Color.Red);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private SolidWorks.Interop.sldworks.View CreateFlatPatternView(
|
||||
DrawingDoc drawing,
|
||||
ModelDoc2 part,
|
||||
string configName)
|
||||
{
|
||||
return drawing.CreateFlatPatternViewFromModelView3(
|
||||
part.GetPathName(),
|
||||
configName,
|
||||
0, 0, 0,
|
||||
false,
|
||||
false);
|
||||
}
|
||||
|
||||
private void ConfigureFlatPatternView(
|
||||
SolidWorks.Interop.sldworks.View view,
|
||||
DrawingDoc drawing,
|
||||
ModelDoc2 partModel,
|
||||
string configName,
|
||||
ExportContext context)
|
||||
{
|
||||
view.ShowSheetMetalBendNotes = true;
|
||||
|
||||
var drawingModel = drawing as ModelDoc2;
|
||||
drawingModel.ViewZoomtofit2();
|
||||
|
||||
var flatPatternModel = ViewHelper.GetModelFromView(view);
|
||||
SolidWorksHelper.SetFlatPatternSuppressionState(
|
||||
flatPatternModel,
|
||||
swComponentSuppressionState_e.swComponentFullyResolved);
|
||||
|
||||
if (ViewHelper.HasSupressedBends(view))
|
||||
{
|
||||
context.ProgressCallback?.Invoke("A bend is suppressed, please check flat pattern", Color.Red);
|
||||
}
|
||||
|
||||
if (ViewHelper.HideModelSketches(view))
|
||||
{
|
||||
// Recreate view without sketches
|
||||
DeleteView(drawingModel, view);
|
||||
|
||||
view = CreateFlatPatternView(drawing, partModel, configName);
|
||||
view.ShowSheetMetalBendNotes = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void DeleteView(ModelDoc2 drawing, SolidWorks.Interop.sldworks.View view)
|
||||
{
|
||||
drawing.SelectByName(0, view.Name);
|
||||
drawing.DeleteSelection(false);
|
||||
}
|
||||
|
||||
private void AddEtchLines(string dxfPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var etcher = new EtchBendLines.Etcher();
|
||||
etcher.AddEtchLines(dxfPath);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Silently fail if etch lines can't be added
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSinglePartFileName(ModelDoc2 model, string prefix)
|
||||
{
|
||||
var title = model.GetTitle().Replace(".SLDPRT", "");
|
||||
var config = model.ConfigurationManager.ActiveConfiguration.Name;
|
||||
var isDefaultConfig = string.Equals(config, "default", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var name = isDefaultConfig ? title : $"{title} [{config}]";
|
||||
return prefix + name;
|
||||
}
|
||||
|
||||
private string GetItemFileName(Item item, string prefix)
|
||||
{
|
||||
prefix = prefix?.Replace("\"", "''") ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||
{
|
||||
return prefix + item.PartName;
|
||||
}
|
||||
|
||||
return prefix + item.ItemNo.PadLeft(2, '0');
|
||||
}
|
||||
|
||||
private void LogExportFailure(Item item, ExportContext context)
|
||||
{
|
||||
var desc = item.Description?.ToLower() ?? string.Empty;
|
||||
|
||||
if (desc.Contains("laser"))
|
||||
{
|
||||
context.ProgressCallback?.Invoke(
|
||||
$"Failed to export item #{item.ItemNo} but description says it is laser cut.",
|
||||
Color.Red);
|
||||
}
|
||||
else if (desc.Contains("plasma"))
|
||||
{
|
||||
context.ProgressCallback?.Invoke(
|
||||
$"Failed to export item #{item.ItemNo} but description says it is plasma cut.",
|
||||
Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private DrawingDoc CreateTemplateDrawing(ExportContext context)
|
||||
{
|
||||
throw new NotImplementedException("Template drawing creation not implemented.");
|
||||
}
|
||||
|
||||
private void CloseTemplateDrawing(DrawingDoc drawing, ExportContext context)
|
||||
{
|
||||
throw new NotImplementedException("Template drawing creation not implemented.");
|
||||
}
|
||||
}
|
||||
}
|
||||
406
ExportDXF/Services/SolidWorksService.cs
Normal file
406
ExportDXF/Services/SolidWorksService.cs
Normal file
@@ -0,0 +1,406 @@
|
||||
using ExportDXF.Models;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service for managing SolidWorks application connection and document lifecycle.
|
||||
/// </summary>
|
||||
public interface ISolidWorksService : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Connects to the SolidWorks application asynchronously.
|
||||
/// </summary>
|
||||
Task ConnectAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently active SolidWorks document.
|
||||
/// </summary>
|
||||
/// <returns>The active document or null if no document is open.</returns>
|
||||
SolidWorksDocument GetActiveDocument();
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables user control of the SolidWorks application.
|
||||
/// </summary>
|
||||
/// <param name="enable">True to enable user control, false to disable.</param>
|
||||
void EnableUserControl(bool enable);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SolidWorks application instance.
|
||||
/// </summary>
|
||||
/// <returns>The SldWorks instance.</returns>
|
||||
SldWorks GetNativeSldWorks();
|
||||
|
||||
/// <summary>
|
||||
/// Closes the document with the specified title.
|
||||
/// </summary>
|
||||
/// <param name="documentTitle"></param>
|
||||
/// <returns></returns>
|
||||
bool CloseDocument(string documentTitle);
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the active document changes.
|
||||
/// </summary>
|
||||
event EventHandler ActiveDocumentChanged;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing SolidWorks application connection and document lifecycle.
|
||||
/// </summary>
|
||||
public class SolidWorksService : ISolidWorksService
|
||||
{
|
||||
private SldWorks _sldWorks;
|
||||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the active document changes in SolidWorks.
|
||||
/// </summary>
|
||||
public event EventHandler ActiveDocumentChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Connects to the SolidWorks application asynchronously.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown when connection to SolidWorks fails.</exception>
|
||||
public async Task ConnectAsync()
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try to get running instance first
|
||||
_sldWorks = GetRunningInstance();
|
||||
|
||||
// If no running instance, create new one
|
||||
if (_sldWorks == null)
|
||||
{
|
||||
_sldWorks = CreateNewInstance();
|
||||
}
|
||||
|
||||
if (_sldWorks == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Failed to connect to SolidWorks. Please ensure SolidWorks is installed.");
|
||||
}
|
||||
|
||||
// Make SolidWorks visible
|
||||
_sldWorks.Visible = true;
|
||||
|
||||
// Subscribe to document change events
|
||||
_sldWorks.ActiveModelDocChangeNotify += OnSolidWorksActiveDocChanged;
|
||||
}
|
||||
catch (COMException ex)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"COM error while connecting to SolidWorks: {ex.Message}", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently active SolidWorks document.
|
||||
/// </summary>
|
||||
/// <returns>The active document wrapper, or null if no document is open.</returns>
|
||||
public SolidWorksDocument GetActiveDocument()
|
||||
{
|
||||
if (_sldWorks == null)
|
||||
return null;
|
||||
|
||||
var model = _sldWorks.ActiveDoc as ModelDoc2;
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
return CreateDocumentWrapper(model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disables user control of the SolidWorks application.
|
||||
/// When disabled, SolidWorks won't show dialogs or allow user interaction.
|
||||
/// </summary>
|
||||
/// <param name="enable">True to enable user control, false to disable.</param>
|
||||
public void EnableUserControl(bool enable)
|
||||
{
|
||||
if (_sldWorks != null)
|
||||
{
|
||||
_sldWorks.UserControl = enable;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the native SolidWorks application instance.
|
||||
/// Use this when you need direct access to the SolidWorks API.
|
||||
/// </summary>
|
||||
/// <returns>The SldWorks instance, or null if not connected.</returns>
|
||||
public SldWorks GetNativeSldWorks()
|
||||
{
|
||||
return _sldWorks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if SolidWorks is connected and ready.
|
||||
/// </summary>
|
||||
public bool IsConnected => _sldWorks != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the connected SolidWorks instance.
|
||||
/// </summary>
|
||||
public string GetSolidWorksVersion()
|
||||
{
|
||||
if (_sldWorks == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return _sldWorks.RevisionNumber();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes a document by its title.
|
||||
/// </summary>
|
||||
/// <param name="documentTitle">The title of the document to close.</param>
|
||||
/// <returns>True if successfully closed, false otherwise.</returns>
|
||||
public bool CloseDocument(string documentTitle)
|
||||
{
|
||||
if (_sldWorks == null || string.IsNullOrWhiteSpace(documentTitle))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
_sldWorks.CloseDoc(documentTitle);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new document from a template.
|
||||
/// </summary>
|
||||
/// <param name="templatePath">Path to the template file.</param>
|
||||
/// <param name="paperSize">Paper size for drawings.</param>
|
||||
/// <param name="width">Width dimension.</param>
|
||||
/// <param name="height">Height dimension.</param>
|
||||
/// <returns>The created document, or null if creation failed.</returns>
|
||||
public ModelDoc2 CreateDocument(string templatePath, int paperSize, double width, double height)
|
||||
{
|
||||
if (_sldWorks == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return _sldWorks.NewDocument(templatePath, paperSize, width, height) as ModelDoc2;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens an existing document.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Full path to the document.</param>
|
||||
/// <param name="options">Open options.</param>
|
||||
/// <param name="configuration">Configuration to open (empty for default).</param>
|
||||
/// <returns>The opened document, or null if open failed.</returns>
|
||||
public ModelDoc2 OpenDocument(string filePath, int options = 0, string configuration = "")
|
||||
{
|
||||
if (_sldWorks == null || string.IsNullOrWhiteSpace(filePath))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
int errors = 0;
|
||||
int warnings = 0;
|
||||
|
||||
var doc = _sldWorks.OpenDoc6(
|
||||
filePath,
|
||||
(int)GetDocumentType(filePath),
|
||||
options,
|
||||
configuration,
|
||||
ref errors,
|
||||
ref warnings);
|
||||
|
||||
return doc as ModelDoc2;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private SldWorks GetRunningInstance()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Marshal.GetActiveObject("SldWorks.Application") as SldWorks;
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
// No running instance
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private SldWorks CreateNewInstance()
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = Type.GetTypeFromProgID("SldWorks.Application");
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
return Activator.CreateInstance(type) as SldWorks;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private SolidWorksDocument CreateDocumentWrapper(ModelDoc2 model)
|
||||
{
|
||||
return new SolidWorksDocument
|
||||
{
|
||||
Title = model.GetTitle(),
|
||||
FilePath = model.GetPathName(),
|
||||
DocumentType = DetermineDocumentType(model),
|
||||
NativeDocument = model
|
||||
};
|
||||
}
|
||||
|
||||
private DocumentType DetermineDocumentType(ModelDoc2 model)
|
||||
{
|
||||
if (model is PartDoc) return DocumentType.Part;
|
||||
if (model is AssemblyDoc) return DocumentType.Assembly;
|
||||
if (model is DrawingDoc) return DocumentType.Drawing;
|
||||
return DocumentType.Unknown;
|
||||
}
|
||||
|
||||
private swDocumentTypes_e GetDocumentType(string filePath)
|
||||
{
|
||||
var extension = System.IO.Path.GetExtension(filePath)?.ToLowerInvariant();
|
||||
|
||||
switch (extension)
|
||||
{
|
||||
case ".sldprt":
|
||||
return swDocumentTypes_e.swDocPART;
|
||||
case ".sldasm":
|
||||
return swDocumentTypes_e.swDocASSEMBLY;
|
||||
case ".slddrw":
|
||||
return swDocumentTypes_e.swDocDRAWING;
|
||||
default:
|
||||
return swDocumentTypes_e.swDocNONE;
|
||||
}
|
||||
}
|
||||
|
||||
private int OnSolidWorksActiveDocChanged()
|
||||
{
|
||||
try
|
||||
{
|
||||
ActiveDocumentChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Swallow exceptions from event handlers
|
||||
}
|
||||
|
||||
return 1; // SolidWorks expects 1 to be returned
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable Implementation
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources used by the SolidWorksService.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases unmanaged and optionally managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">True to release both managed and unmanaged resources;
|
||||
/// false to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
// Unsubscribe from events
|
||||
if (_sldWorks != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
_sldWorks.ActiveModelDocChangeNotify -= OnSolidWorksActiveDocChanged;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Release COM object
|
||||
if (_sldWorks != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Marshal.ReleaseComObject(_sldWorks);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore errors during cleanup
|
||||
}
|
||||
|
||||
_sldWorks = null;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public SldWorks GetSldWorks()
|
||||
{
|
||||
if (_sldWorks == null)
|
||||
{
|
||||
_sldWorks = GetRunningInstance();
|
||||
return _sldWorks;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sldWorks = CreateNewInstance();
|
||||
return _sldWorks;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finalizer to ensure COM objects are released.
|
||||
/// </summary>
|
||||
~SolidWorksService()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace ExportDXF
|
||||
{
|
||||
public static class Units
|
||||
{
|
||||
/// <summary>
|
||||
/// Multiply factor needed to convert the desired units to meters.
|
||||
/// </summary>
|
||||
public static double ScaleFactor = 0.0254; // inches to meters
|
||||
|
||||
public static double ToSldWorks(this double d)
|
||||
{
|
||||
return Math.Round(d * ScaleFactor, 8);
|
||||
}
|
||||
|
||||
public static double FromSldWorks(this double d)
|
||||
{
|
||||
return Math.Round(d / ScaleFactor, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
ExportDXF/Utilities/SheetMetalProperties.cs
Normal file
43
ExportDXF/Utilities/SheetMetalProperties.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
namespace ExportDXF.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains sheet metal properties extracted from a SolidWorks part.
|
||||
/// </summary>
|
||||
public class SheetMetalProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// Material thickness in millimeters.
|
||||
/// </summary>
|
||||
public double Thickness { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// K-factor for bend calculations.
|
||||
/// </summary>
|
||||
public double KFactor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bend radius in millimeters.
|
||||
/// </summary>
|
||||
public double BendRadius { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bend allowance in millimeters.
|
||||
/// </summary>
|
||||
public double BendAllowance { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether auto relief is enabled.
|
||||
/// </summary>
|
||||
public bool AutoRelief { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Relief ratio for auto relief.
|
||||
/// </summary>
|
||||
public double ReliefRatio { get; set; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Thickness: {Thickness:F2}mm, K-Factor: {KFactor:F3}, Bend Radius: {BendRadius:F2}mm";
|
||||
}
|
||||
}
|
||||
}
|
||||
513
ExportDXF/Utilities/SolidWorksHelper.cs
Normal file
513
ExportDXF/Utilities/SolidWorksHelper.cs
Normal file
@@ -0,0 +1,513 @@
|
||||
using ExportDXF.Extensions;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace ExportDXF.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for SolidWorks-specific operations and helper functions.
|
||||
/// </summary>
|
||||
public static class SolidWorksHelper
|
||||
{
|
||||
#region Feature Names Constants
|
||||
|
||||
private const string FLAT_PATTERN_FEATURE = "FlatPattern";
|
||||
private const string SHEET_METAL_FEATURE = "SheetMetal";
|
||||
private const string BASE_FLANGE_FEATURE = "BaseFlange";
|
||||
private const string FLAT_PATTERN_FOLDER = "Flat-Pattern";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sheet Metal Operations
|
||||
|
||||
/// <summary>
|
||||
/// Gets sheet metal properties from the model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to extract properties from.</param>
|
||||
/// <returns>Sheet metal properties, or null if not a sheet metal part.</returns>
|
||||
public static SheetMetalProperties GetSheetMetalProperties(ModelDoc2 model)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
|
||||
var sheetMetalFeature = model.GetFeatureByTypeName(SHEET_METAL_FEATURE);
|
||||
|
||||
if (sheetMetalFeature == null)
|
||||
return null;
|
||||
|
||||
var sheetMetalData = sheetMetalFeature.GetDefinition() as SheetMetalFeatureData;
|
||||
|
||||
if (sheetMetalData == null)
|
||||
return null;
|
||||
|
||||
return new SheetMetalProperties
|
||||
{
|
||||
Thickness = sheetMetalData.Thickness.FromSolidWorksToInches(),
|
||||
KFactor = sheetMetalData.KFactor,
|
||||
BendRadius = sheetMetalData.BendRadius.FromSolidWorksToInches(),
|
||||
BendAllowance = sheetMetalData.BendAllowance.FromSolidWorksToInches(),
|
||||
AutoRelief = sheetMetalData.UseAutoRelief,
|
||||
ReliefRatio = sheetMetalData.ReliefRatio
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures flat pattern settings for optimal DXF export.
|
||||
/// Unchecks corner treatment and enables simplify bends.
|
||||
/// </summary>
|
||||
/// <param name="model">The model containing the flat pattern.</param>
|
||||
/// <returns>True if settings were successfully modified.</returns>
|
||||
public static bool ConfigureFlatPatternSettings(ModelDoc2 model)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
|
||||
var flatPattern = model.GetFeatureByTypeName(FLAT_PATTERN_FEATURE);
|
||||
|
||||
if (flatPattern == null)
|
||||
return false;
|
||||
|
||||
var featureData = flatPattern.GetDefinition() as FlatPatternFeatureData;
|
||||
|
||||
if (featureData == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Configure for cleaner DXF output
|
||||
featureData.CornerTreatment = false; // Remove corner treatments
|
||||
featureData.SimplifyBends = true; // Simplify bend representations
|
||||
|
||||
return flatPattern.ModifyDefinition(featureData, model, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the suppression state of the flat pattern feature.
|
||||
/// </summary>
|
||||
/// <param name="model">The model containing the flat pattern.</param>
|
||||
/// <param name="suppressionState">The desired suppression state.</param>
|
||||
/// <returns>True if the flat pattern is suppressed after the operation.</returns>
|
||||
public static bool SetFlatPatternSuppressionState(
|
||||
ModelDoc2 model,
|
||||
swComponentSuppressionState_e suppressionState)
|
||||
{
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
|
||||
var flatPattern = model.GetFeatureByTypeName(FLAT_PATTERN_FEATURE);
|
||||
|
||||
if (flatPattern == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
flatPattern.SetSuppression((int)suppressionState);
|
||||
return flatPattern.IsSuppressed();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the flat pattern feature from a model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to search.</param>
|
||||
/// <returns>The flat pattern feature, or null if not found.</returns>
|
||||
public static Feature GetFlatPatternFeature(ModelDoc2 model)
|
||||
{
|
||||
return model?.GetFeatureByTypeName(FLAT_PATTERN_FEATURE);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a model has a flat pattern feature.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to check.</param>
|
||||
/// <returns>True if the model has a flat pattern feature.</returns>
|
||||
public static bool HasFlatPattern(ModelDoc2 model)
|
||||
{
|
||||
return GetFlatPatternFeature(model) != null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Feature Operations
|
||||
|
||||
/// <summary>
|
||||
/// Gets all features of a specific type from a model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to search.</param>
|
||||
/// <param name="featureTypeName">The name of the feature type to find.</param>
|
||||
/// <returns>A list of features of the specified type.</returns>
|
||||
public static List<Feature> GetFeaturesByTypeName(ModelDoc2 model, string featureTypeName)
|
||||
{
|
||||
if (model == null || string.IsNullOrEmpty(featureTypeName))
|
||||
return new List<Feature>();
|
||||
|
||||
var features = new List<Feature>();
|
||||
var feature = model.FirstFeature() as Feature;
|
||||
|
||||
while (feature != null)
|
||||
{
|
||||
if (string.Equals(feature.GetTypeName2(), featureTypeName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
features.Add(feature);
|
||||
}
|
||||
|
||||
// Check sub-features
|
||||
var subFeature = feature.GetFirstSubFeature() as Feature;
|
||||
while (subFeature != null)
|
||||
{
|
||||
if (string.Equals(subFeature.GetTypeName2(), featureTypeName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
features.Add(subFeature);
|
||||
}
|
||||
subFeature = subFeature.GetNextSubFeature() as Feature;
|
||||
}
|
||||
|
||||
feature = feature.GetNextFeature() as Feature;
|
||||
}
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Suppresses or unsuppresses a feature by name.
|
||||
/// </summary>
|
||||
/// <param name="model">The model containing the feature.</param>
|
||||
/// <param name="featureName">The name of the feature to suppress/unsuppress.</param>
|
||||
/// <param name="suppress">True to suppress, false to unsuppress.</param>
|
||||
/// <returns>True if the operation was successful.</returns>
|
||||
public static bool SetFeatureSuppressionByName(ModelDoc2 model, string featureName, bool suppress)
|
||||
{
|
||||
if (model == null || string.IsNullOrEmpty(featureName))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// Use Extension.SelectByID2 to find and select the feature
|
||||
var modelExtension = model.Extension;
|
||||
bool selected = modelExtension.SelectByID2(featureName, "BODYFEATURE", 0, 0, 0, false, 0, null, 0);
|
||||
|
||||
if (!selected)
|
||||
return false;
|
||||
|
||||
var selectionManager = model.SelectionManager as SelectionMgr;
|
||||
var feature = selectionManager.GetSelectedObject6(1, -1) as Feature;
|
||||
|
||||
if (feature == null)
|
||||
return false;
|
||||
|
||||
var suppressionState = suppress
|
||||
? swFeatureSuppressionAction_e.swSuppressFeature
|
||||
: swFeatureSuppressionAction_e.swUnSuppressFeature;
|
||||
|
||||
feature.SetSuppression((int)suppressionState);
|
||||
|
||||
// Clear the selection
|
||||
model.ClearSelection2(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Configuration Operations
|
||||
|
||||
/// <summary>
|
||||
/// Gets all configuration names from a model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to query.</param>
|
||||
/// <returns>A list of configuration names.</returns>
|
||||
public static List<string> GetConfigurationNames(ModelDoc2 model)
|
||||
{
|
||||
if (model == null)
|
||||
return new List<string>();
|
||||
|
||||
var configNames = (string[])model.GetConfigurationNames();
|
||||
return configNames?.ToList() ?? new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activates a specific configuration.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to modify.</param>
|
||||
/// <param name="configurationName">The name of the configuration to activate.</param>
|
||||
/// <returns>True if the configuration was successfully activated.</returns>
|
||||
public static bool ActivateConfiguration(ModelDoc2 model, string configurationName)
|
||||
{
|
||||
if (model == null || string.IsNullOrEmpty(configurationName))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
return model.ShowConfiguration2(configurationName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the active configuration name.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to query.</param>
|
||||
/// <returns>The name of the active configuration, or null if unavailable.</returns>
|
||||
public static string GetActiveConfigurationName(ModelDoc2 model)
|
||||
{
|
||||
if (model == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var config = model.GetActiveConfiguration() as Configuration;
|
||||
return config?.Name;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Custom Properties
|
||||
|
||||
/// <summary>
|
||||
/// Gets a custom property value from a model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to query.</param>
|
||||
/// <param name="propertyName">The name of the property.</param>
|
||||
/// <param name="configurationName">The configuration name (empty string for file-level properties).</param>
|
||||
/// <returns>The property value, or null if not found.</returns>
|
||||
public static string GetCustomProperty(ModelDoc2 model, string propertyName, string configurationName = "")
|
||||
{
|
||||
if (model == null || string.IsNullOrEmpty(propertyName))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var customPropertyManager = model.Extension.CustomPropertyManager[configurationName ?? ""];
|
||||
return customPropertyManager.Get(propertyName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom property value on a model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to modify.</param>
|
||||
/// <param name="propertyName">The name of the property.</param>
|
||||
/// <param name="propertyValue">The value to set.</param>
|
||||
/// <param name="configurationName">The configuration name (empty string for file-level properties).</param>
|
||||
/// <returns>True if the property was successfully set.</returns>
|
||||
public static bool SetCustomProperty(ModelDoc2 model, string propertyName, string propertyValue, string configurationName = "")
|
||||
{
|
||||
if (model == null || string.IsNullOrEmpty(propertyName))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var customPropertyManager = model.Extension.CustomPropertyManager[configurationName ?? ""];
|
||||
var result = customPropertyManager.Add3(propertyName, (int)swCustomInfoType_e.swCustomInfoText, propertyValue, (int)swCustomPropertyAddOption_e.swCustomPropertyReplaceValue);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all custom property names from a model.
|
||||
/// </summary>
|
||||
/// <param name="model">The model to query.</param>
|
||||
/// <param name="configurationName">The configuration name (empty string for file-level properties).</param>
|
||||
/// <returns>A list of custom property names.</returns>
|
||||
public static List<string> GetCustomPropertyNames(ModelDoc2 model, string configurationName = "")
|
||||
{
|
||||
if (model == null)
|
||||
return new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
var customPropertyManager = model.Extension.CustomPropertyManager[configurationName ?? ""];
|
||||
var propertyNames = (string[])customPropertyManager.GetNames();
|
||||
return propertyNames?.ToList() ?? new List<string>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Material Operations
|
||||
|
||||
/// <summary>
|
||||
/// Gets the material name for a part document.
|
||||
/// </summary>
|
||||
/// <param name="part">The part document.</param>
|
||||
/// <param name="configurationName">The configuration name.</param>
|
||||
/// <returns>The material name, or null if not assigned.</returns>
|
||||
public static string GetMaterial(PartDoc part, string configurationName)
|
||||
{
|
||||
if (part == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return part.GetMaterialPropertyName2(configurationName, out _);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the material for a part document.
|
||||
/// </summary>
|
||||
/// <param name="part">The part document.</param>
|
||||
/// <param name="materialName">The name of the material to assign.</param>
|
||||
/// <param name="databasePath">The path to the material database.</param>
|
||||
/// <returns>True if the material was successfully assigned.</returns>
|
||||
public static bool SetMaterial(PartDoc part, string materialName, string databasePath)
|
||||
{
|
||||
if (part == null || string.IsNullOrEmpty(materialName))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
part.SetMaterialPropertyName2("", databasePath, materialName);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Document Operations
|
||||
|
||||
/// <summary>
|
||||
/// Gets the file path of a model document.
|
||||
/// </summary>
|
||||
/// <param name="model">The model document.</param>
|
||||
/// <returns>The full file path, or empty string if not saved.</returns>
|
||||
public static string GetFilePath(ModelDoc2 model)
|
||||
{
|
||||
return model?.GetPathName() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the document title (filename without path).
|
||||
/// </summary>
|
||||
/// <param name="model">The model document.</param>
|
||||
/// <returns>The document title.</returns>
|
||||
public static string GetTitle(ModelDoc2 model)
|
||||
{
|
||||
return model?.GetTitle() ?? string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a document has been saved.
|
||||
/// </summary>
|
||||
/// <param name="model">The model document.</param>
|
||||
/// <returns>True if the document has been saved to disk.</returns>
|
||||
public static bool IsSaved(ModelDoc2 model)
|
||||
{
|
||||
return !string.IsNullOrEmpty(GetFilePath(model));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves a document.
|
||||
/// </summary>
|
||||
/// <param name="model">The model document to save.</param>
|
||||
/// <returns>True if the save operation was successful.</returns>
|
||||
public static bool Save(ModelDoc2 model)
|
||||
{
|
||||
if (model == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
int errors = 0;
|
||||
int warnings = 0;
|
||||
return model.Save3((int)swSaveAsOptions_e.swSaveAsOptions_Silent, ref errors, ref warnings);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Component Operations
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a lightweight component to fully loaded.
|
||||
/// </summary>
|
||||
/// <param name="component">The component to resolve.</param>
|
||||
/// <returns>True if the component was successfully resolved.</returns>
|
||||
public static bool ResolveComponent(Component2 component)
|
||||
{
|
||||
if (component == null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
component.SetLightweightToResolved();
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a component is suppressed.
|
||||
/// </summary>
|
||||
/// <param name="component">The component to check.</param>
|
||||
/// <returns>True if the component is suppressed.</returns>
|
||||
public static bool IsComponentSuppressed(Component2 component)
|
||||
{
|
||||
return component?.IsSuppressed() ?? false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the model document from a component.
|
||||
/// </summary>
|
||||
/// <param name="component">The component.</param>
|
||||
/// <returns>The model document, or null if unavailable.</returns>
|
||||
public static ModelDoc2 GetModelFromComponent(Component2 component)
|
||||
{
|
||||
return component?.GetModelDoc2() as ModelDoc2;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
274
ExportDXF/Utilities/TextHelper.cs
Normal file
274
ExportDXF/Utilities/TextHelper.cs
Normal file
@@ -0,0 +1,274 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ExportDXF.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class for text processing and string manipulation operations.
|
||||
/// </summary>
|
||||
public static class TextHelper
|
||||
{
|
||||
private static readonly Regex XmlTagRegex = new Regex(@"<[^>]+>", RegexOptions.Compiled);
|
||||
private static readonly Regex WhitespaceRegex = new Regex(@"\s+", RegexOptions.Compiled);
|
||||
private static readonly Regex FontTagRegex = new Regex(@"<FONT.*?>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all XML tags from the input string.
|
||||
/// </summary>
|
||||
/// <param name="input">The string containing XML tags to remove.</param>
|
||||
/// <returns>The string with all XML tags removed, or the original input if null/empty.</returns>
|
||||
public static string RemoveXmlTags(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
return XmlTagRegex.Replace(input, string.Empty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes specific SolidWorks font XML tags from the input string.
|
||||
/// This is more targeted than RemoveXmlTags and handles SolidWorks-specific formatting.
|
||||
/// </summary>
|
||||
/// <param name="input">The string containing font tags to remove.</param>
|
||||
/// <returns>The string with font tags removed.</returns>
|
||||
public static string RemoveFontXmlTags(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
var result = input;
|
||||
var matches = FontTagRegex.Matches(result);
|
||||
|
||||
// Process matches in reverse order to maintain indices
|
||||
for (int i = matches.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var match = matches[i];
|
||||
result = result.Remove(match.Index, match.Length);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes whitespace in a string by replacing multiple consecutive whitespace characters with a single space.
|
||||
/// </summary>
|
||||
/// <param name="input">The string to normalize.</param>
|
||||
/// <returns>The string with normalized whitespace.</returns>
|
||||
public static string NormalizeWhitespace(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
return WhitespaceRegex.Replace(input.Trim(), " ");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cleans text by removing XML tags and normalizing whitespace.
|
||||
/// This is a common operation for processing text from SolidWorks.
|
||||
/// </summary>
|
||||
/// <param name="input">The text to clean.</param>
|
||||
/// <returns>Cleaned text with XML tags removed and whitespace normalized.</returns>
|
||||
public static string CleanText(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
var cleaned = RemoveXmlTags(input);
|
||||
return NormalizeWhitespace(cleaned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a number with its ordinal suffix (1st, 2nd, 3rd, 4th, etc.).
|
||||
/// </summary>
|
||||
/// <param name="number">The number to format.</param>
|
||||
/// <returns>The number with appropriate ordinal suffix.</returns>
|
||||
public static string GetOrdinalSuffix(int number)
|
||||
{
|
||||
if (number <= 0)
|
||||
return number.ToString();
|
||||
|
||||
// Special cases for 11th, 12th, 13th
|
||||
if (number >= 11 && number <= 13)
|
||||
return number + "th";
|
||||
|
||||
return number + GetSuffix(number % 10);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a string to title case (first letter of each word capitalized).
|
||||
/// </summary>
|
||||
/// <param name="input">The string to convert.</param>
|
||||
/// <returns>The string in title case.</returns>
|
||||
public static string ToTitleCase(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(input.ToLowerInvariant());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Truncates a string to the specified maximum length, optionally adding an ellipsis.
|
||||
/// </summary>
|
||||
/// <param name="input">The string to truncate.</param>
|
||||
/// <param name="maxLength">The maximum length of the result.</param>
|
||||
/// <param name="useEllipsis">Whether to add "..." when truncating.</param>
|
||||
/// <returns>The truncated string.</returns>
|
||||
public static string Truncate(string input, int maxLength, bool useEllipsis = true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
if (input.Length <= maxLength)
|
||||
return input;
|
||||
|
||||
if (useEllipsis && maxLength > 3)
|
||||
{
|
||||
return input.Substring(0, maxLength - 3) + "...";
|
||||
}
|
||||
|
||||
return input.Substring(0, maxLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes invalid filename characters from a string, replacing them with underscores.
|
||||
/// </summary>
|
||||
/// <param name="filename">The filename to sanitize.</param>
|
||||
/// <returns>A safe filename with invalid characters replaced.</returns>
|
||||
public static string SanitizeFileName(string filename)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filename))
|
||||
return filename;
|
||||
|
||||
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
|
||||
var sb = new StringBuilder(filename);
|
||||
|
||||
foreach (var invalidChar in invalidChars)
|
||||
{
|
||||
sb.Replace(invalidChar, '_');
|
||||
}
|
||||
|
||||
// Also replace some additional problematic characters
|
||||
sb.Replace(' ', '_'); // Spaces can be problematic
|
||||
sb.Replace('"', '\''); // Double quotes to single quotes
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a string is null, empty, or contains only whitespace.
|
||||
/// </summary>
|
||||
/// <param name="input">The string to check.</param>
|
||||
/// <returns>True if the string is null, empty, or whitespace only.</returns>
|
||||
public static bool IsNullOrWhiteSpace(string input)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(input);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely gets a substring without throwing exceptions for invalid indices.
|
||||
/// </summary>
|
||||
/// <param name="input">The source string.</param>
|
||||
/// <param name="startIndex">The starting index.</param>
|
||||
/// <param name="length">The length of the substring.</param>
|
||||
/// <returns>The substring, or empty string if indices are invalid.</returns>
|
||||
public static string SafeSubstring(string input, int startIndex, int length)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input) || startIndex < 0 || startIndex >= input.Length)
|
||||
return string.Empty;
|
||||
|
||||
var actualLength = Math.Min(length, input.Length - startIndex);
|
||||
return actualLength <= 0 ? string.Empty : input.Substring(startIndex, actualLength);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Safely gets a substring from the start index to the end of the string.
|
||||
/// </summary>
|
||||
/// <param name="input">The source string.</param>
|
||||
/// <param name="startIndex">The starting index.</param>
|
||||
/// <returns>The substring from start index to end, or empty if invalid.</returns>
|
||||
public static string SafeSubstring(string input, int startIndex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input) || startIndex < 0 || startIndex >= input.Length)
|
||||
return string.Empty;
|
||||
|
||||
return input.Substring(startIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pads a string to a specific length, truncating if too long.
|
||||
/// </summary>
|
||||
/// <param name="input">The string to pad or truncate.</param>
|
||||
/// <param name="totalLength">The desired total length.</param>
|
||||
/// <param name="paddingChar">The character to use for padding.</param>
|
||||
/// <param name="padLeft">True to pad on the left, false to pad on the right.</param>
|
||||
/// <returns>A string of exactly the specified length.</returns>
|
||||
public static string PadOrTruncate(string input, int totalLength, char paddingChar = ' ', bool padLeft = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
input = string.Empty;
|
||||
|
||||
if (input.Length == totalLength)
|
||||
return input;
|
||||
|
||||
if (input.Length > totalLength)
|
||||
return input.Substring(0, totalLength);
|
||||
|
||||
return padLeft
|
||||
? input.PadLeft(totalLength, paddingChar)
|
||||
: input.PadRight(totalLength, paddingChar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a string to a safe identifier (letters, numbers, underscores only).
|
||||
/// </summary>
|
||||
/// <param name="input">The input string.</param>
|
||||
/// <returns>A safe identifier string.</returns>
|
||||
public static string ToSafeIdentifier(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return "Identifier";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (char c in input)
|
||||
{
|
||||
if (char.IsLetterOrDigit(c))
|
||||
{
|
||||
sb.Append(c);
|
||||
}
|
||||
else if (c == ' ' || c == '-' || c == '.')
|
||||
{
|
||||
sb.Append('_');
|
||||
}
|
||||
}
|
||||
|
||||
var result = sb.ToString();
|
||||
|
||||
// Ensure it starts with a letter or underscore
|
||||
if (result.Length > 0 && char.IsDigit(result[0]))
|
||||
{
|
||||
result = "_" + result;
|
||||
}
|
||||
|
||||
return string.IsNullOrEmpty(result) ? "Identifier" : result;
|
||||
}
|
||||
|
||||
#region Private Helper Methods
|
||||
|
||||
private static string GetSuffix(int lastDigit)
|
||||
{
|
||||
switch (lastDigit)
|
||||
{
|
||||
case 1: return "st";
|
||||
case 2: return "nd";
|
||||
case 3: return "rd";
|
||||
default: return "th";
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using ExportDXF.Extensions;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ExportDXF
|
||||
namespace ExportDXF.Utilities
|
||||
{
|
||||
internal static class ViewHelper
|
||||
{
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Windows.Forms;
|
||||
using ExportDXF.Utilities;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ExportDXF.ViewFlipDeciders
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using ExportDXF.Utilities;
|
||||
using System.Linq;
|
||||
|
||||
namespace ExportDXF.ViewFlipDeciders
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using ExportDXF.Utilities;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ExportDXF.ViewFlipDeciders
|
||||
|
||||
147
ExportDXF/ViewFlipDeciders/ViewFlipDeciderFactory.cs
Normal file
147
ExportDXF/ViewFlipDeciders/ViewFlipDeciderFactory.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace ExportDXF.ViewFlipDeciders
|
||||
{
|
||||
/// <summary>
|
||||
/// Factory for discovering and creating IViewFlipDecider implementations.
|
||||
/// </summary>
|
||||
public static class ViewFlipDeciderFactory
|
||||
{
|
||||
private static readonly object _lock = new object();
|
||||
private static List<IViewFlipDecider> _cachedDeciders;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all available IViewFlipDecider implementations from loaded assemblies.
|
||||
/// Results are cached for performance.
|
||||
/// </summary>
|
||||
/// <returns>An enumerable collection of IViewFlipDecider instances.</returns>
|
||||
public static IEnumerable<IViewFlipDecider> GetAvailableDeciders()
|
||||
{
|
||||
if (_cachedDeciders != null)
|
||||
return _cachedDeciders;
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
if (_cachedDeciders != null)
|
||||
return _cachedDeciders;
|
||||
|
||||
_cachedDeciders = DiscoverDeciders().ToList();
|
||||
return _cachedDeciders;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific view flip decider by name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the decider to retrieve.</param>
|
||||
/// <returns>The matching decider, or null if not found.</returns>
|
||||
public static IViewFlipDecider GetDeciderByName(string name)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return null;
|
||||
|
||||
return GetAvailableDeciders()
|
||||
.FirstOrDefault(d => string.Equals(d.Name, name, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default (Automatic) view flip decider, or the first available if Automatic doesn't exist.
|
||||
/// </summary>
|
||||
/// <returns>The default decider.</returns>
|
||||
public static IViewFlipDecider GetDefaultDecider()
|
||||
{
|
||||
var automatic = GetDeciderByName("Automatic");
|
||||
if (automatic != null)
|
||||
return automatic;
|
||||
|
||||
return GetAvailableDeciders().FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the cached deciders, forcing rediscovery on next call.
|
||||
/// Useful for testing or if assemblies are loaded dynamically.
|
||||
/// </summary>
|
||||
public static void ClearCache()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_cachedDeciders = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discovers all IViewFlipDecider implementations in loaded assemblies.
|
||||
/// </summary>
|
||||
private static IEnumerable<IViewFlipDecider> DiscoverDeciders()
|
||||
{
|
||||
var deciderType = typeof(IViewFlipDecider);
|
||||
var discoveredTypes = new List<Type>();
|
||||
|
||||
try
|
||||
{
|
||||
// Search all loaded assemblies
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
var types = assembly.GetTypes()
|
||||
.Where(t => deciderType.IsAssignableFrom(t) &&
|
||||
t.IsClass &&
|
||||
!t.IsAbstract &&
|
||||
HasParameterlessConstructor(t));
|
||||
|
||||
discoveredTypes.AddRange(types);
|
||||
}
|
||||
catch (ReflectionTypeLoadException)
|
||||
{
|
||||
// Skip assemblies that can't be loaded
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// If discovery fails entirely, return empty collection
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Create instances of discovered types
|
||||
foreach (var type in discoveredTypes)
|
||||
{
|
||||
IViewFlipDecider instance = null;
|
||||
|
||||
try
|
||||
{
|
||||
instance = (IViewFlipDecider)Activator.CreateInstance(type);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Skip types that can't be instantiated
|
||||
continue;
|
||||
}
|
||||
|
||||
if (instance != null)
|
||||
{
|
||||
yield return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a type has a public parameterless constructor.
|
||||
/// </summary>
|
||||
private static bool HasParameterlessConstructor(Type type)
|
||||
{
|
||||
return type.GetConstructor(
|
||||
BindingFlags.Public | BindingFlags.Instance,
|
||||
null,
|
||||
Type.EmptyTypes,
|
||||
null) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user