Compare commits

..

26 Commits

Author SHA1 Message Date
6b1a5f0ab6 docs: remove AGENTS.md documentation file
Remove obsolete or unnecessary documentation file.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 09:59:16 -05:00
13009aa15e feat: add async SolidWorks connection at startup
Update service container to asynchronously connect to SolidWorks before showing DrawingSelectionForm:
- Convert ResolveDrawingSelection to async method
- Attempt SolidWorks connection and handle failures gracefully
- Show warning dialog if connection fails but allow user to continue
- Pass SolidWorksService instance to DrawingSelectionForm

This enables the active drawing display feature while maintaining robustness when SolidWorks is unavailable.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 09:59:06 -05:00
136a571aea feat(ui): display active SolidWorks drawing in DrawingSelectionForm
Add real-time display of the currently active SolidWorks document:
- Show active drawing name with visual status indicators
- Display different states: drawing (green), non-drawing (orange), none (gray), error (red)
- Subscribe to ActiveDocumentChanged events for live updates
- Inject ISolidWorksService dependency for document access

This helps users verify they're working with the correct drawing before export.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 09:58:48 -05:00
8b1c2b5b1b refactor(ui): improve DrawingSelectionForm layout and responsiveness
- Make form wider (584x315) for better content display
- Add anchor properties to controls for responsive resizing
- Adjust control positions and spacing for improved layout
- Update MainForm tab sizes to match new dimensions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 09:58:33 -05:00
f68bddac93 chore: remove test SolidWorks files from repository
Remove TestDocs/ directory and add to .gitignore. Test files can be maintained locally without bloating the repository history.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 09:56:45 -05:00
de4847b834 refactor(ui): update MainForm for drawing-based workflow
Refactored MainForm to work with pre-selected drawing ID and number. Added PDF viewer control, enhanced BOM item management with sheet metal properties, and improved UI layout for drawing-specific operations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 06:47:04 -05:00
cbfb9190c5 refactor: update application startup flow
Modified Program.cs to display DrawingSelectionForm at startup before launching MainForm. The selected drawing ID and number are now passed to MainForm constructor.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 06:46:43 -05:00
9b1fbd9fad feat(ui): add drawing selection form
Added DrawingSelectionForm to allow users to select equipment and drawing at application startup, replacing the previous workflow where drawing selection happened within the main form.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 06:46:32 -05:00
51bf3b00dd feat(api): add BOM items and cut templates endpoints
Added GetBomItemsForDrawingAsync and GetCutTemplatesAsync methods to fetch BOM items with sheet metal properties and cut template data from the API.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 06:46:22 -05:00
a32bbfa5d9 feat: add BomItem and LogEvent models
Added BomItem model with sheet metal properties (thickness, k-factor, bend radius) and LogEvent model for structured logging with action tracking.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 06:46:11 -05:00
84f0196c97 refactor: remove Excel export functionality
Removed BomExcelExporter service, BomExcelSettings class, BomTemplate.xlsx template, and EPPlus package dependency. This functionality is being replaced with direct API integration.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-03 06:45:58 -05:00
5cf7e1f1e5 chore: add Claude Code configuration and documentation
- Add /organize-commits slash command for logical commit organization
- Add AGENTS.md with repository guidelines and workflows

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 17:30:06 -04:00
35ac0fb3f8 feat(api): add sheet metal properties to DXF upload
- Add defaultBendRadius and material parameters to UploadDxfZipAsync
- Pass bend radius and material from BOM items to API
- Enables more complete sheet metal specification in CutFab

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 17:29:56 -04:00
cc34fb43b6 refactor(ui): rename controls and improve drawing selection
- Rename form controls from generic names to descriptive ones:
  - button1 → runButton
  - richTextBox1 → logTextBox
  - comboBox1 → viewFlipDeciderBox
  - comboBox2 → drawingNoBox
- Preserve drawing selection when switching equipment
- Auto-populate drawing number from active document

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-30 17:29:47 -04:00
AJ
d29d9a0e06 Feature: send sheet metal properties and upload all BOM items
Enhanced DXF export to send thickness and kfactor properties from SolidWorks
to the CutFab API, and ensures all BOM items are uploaded regardless of whether
they have DXF files.

Changes:
- Modified UploadDxfZipAsync to accept and send thickness/kfactor parameters
- Updated DxfExportService to extract thickness/kfactor from Item and pass to API
- Refactored BOM item creation to happen for all items, not just those with DXF files
- This ensures purchased parts, hardware, and other non-sheet-metal items are uploaded

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 00:17:37 -04:00
c7f2a51823 Submodule EtchBendLines updated
Submodule EtchBendLines:
    > 89d987f - Refactor Main method to separate responsibilities and improve readability
    > 78ae737 - Refactor Etcher.AddEtchLines into discrete steps and helpers
    > 2391eb7 - Format Bend.ToString() with 2dp and “?” placeholders for nulls
    > dd7443d - Moved GetEtchLines to Etcher class
    > e5daf74 - Extract IsBendLine helper to clean up BendLineExtractor
    > 7740120 - bendNoteRegex tweaks
    > 214cc94 - Culture-safe parsing
2025-10-28 17:35:14 -04:00
AJ
5b996be91e refactor(model): rename JobNo to EquipmentNo in DrawingInfo
- Update regex group and ToString to use EquipmentNo

- Prepare for equipment-centric drawing identification
2025-10-28 17:24:27 -04:00
AJ
6bddbff08e feat(naming): update DXF filename format to include drawing number and PT##
- Use prefix as drawing number and format as {DrawingNo} PT{ItemNo}

- Default to PT{ItemNo} when no prefix provided
2025-10-28 17:24:21 -04:00
AJ
1ec72bc98f feat(export): integrate CutFab API in export flow
- Export to temp directory and auto-upload PDF/DXF

- Resolve or create drawing via API using selected Equipment ID

- Upload DXFs per-part and create BOM items

- Attempt auto-linking of templates after export

- Add EquipmentId to ExportContext
2025-10-28 17:24:16 -04:00
AJ
b122b88435 feat(ui): add equipment and drawing selectors powered by API\n\n- Inject ICutFabApiClient into MainForm\n- Populate equipment and drawings on load\n- Hook selection changes and pass into export context\n- Resize layout and replace prefix textbox with selectors 2025-10-28 17:24:00 -04:00
AJ
b677ac8ec9 feat(api): add CutFab API client and configuration\n\n- Add ICutFabApiClient + CutFabApiClient HTTP client\n- Wire base URL via appSettings (CutFab.ApiBaseUrl)\n- Register client in Program and inject into services\n- Add required System.Net.Http and compression references 2025-10-28 17:23:56 -04:00
AJ
c9a8442a29 Refactored ExportContext 2025-10-01 09:44:07 -04:00
AJ
a2b89318e1 Changed mm to inches in sheet metal properties 2025-10-01 09:42:22 -04:00
AJ
f1fc105a1b Set minimum width for BOM description column 2025-10-01 09:42:01 -04:00
AJ
58269f9761 Changed BomExcelSettings defaults 2025-10-01 09:40:54 -04:00
AJ
4053038632 Fixed unit scales 2025-10-01 09:40:16 -04:00
31 changed files with 2246 additions and 907 deletions

View File

@@ -0,0 +1,23 @@
# Organize Changes into Logical Commits
Analyze all current git changes and organize them into logical, atomic commits. Follow these steps:
1. **Analyze Changes**: Run git status and git diff to see all modified and untracked files
2. **Review Content**: Examine the actual changes in each file to understand what was modified
3. **Group Logically**: Group changes by:
- Feature or bug fix
- Service or component
- Related functionality
- UI changes vs business logic vs API changes
4. **Create Commits**: For each logical group:
- Stage only the relevant files
- Create a descriptive commit message following conventional commit format
- Use prefixes like feat:, fix:, refactor:, chore:, docs:, etc.
5. **Verify**: After all commits, show git log to confirm all changes were committed
Important guidelines:
- Keep commits atomic (one logical change per commit)
- Write clear, descriptive commit messages
- Don't mix unrelated changes in the same commit
- Follow the existing commit message style in the repository
- Include the Claude Code attribution at the end of each commit message

View File

@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(git add:*)",
"Bash(git commit:*)"
],
"deny": [],
"ask": []
}
}

3
.gitignore vendored
View File

@@ -242,3 +242,6 @@ ModelManifest.xml
.fake/ .fake/
.pfx .pfx
# Test documents
TestDocs/

View File

@@ -4,9 +4,9 @@ namespace ExportDXF
{ {
public class DrawingInfo public class DrawingInfo
{ {
private static Regex drawingFormatRegex = new Regex(@"(?<jobNo>[345]\d{3}(-\d+\w{1,2})?)\s?(?<dwgNo>[ABEP]\d+(-?(\d+[A-Z]?))?)", RegexOptions.IgnoreCase); private static Regex drawingFormatRegex = new Regex(@"(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\s?(?<dwgNo>[ABEP]\d+(-?(\d+[A-Z]?))?)", RegexOptions.IgnoreCase);
public string JobNo { get; set; } public string EquipmentNo { get; set; }
public string DrawingNo { get; set; } public string DrawingNo { get; set; }
@@ -14,7 +14,7 @@ namespace ExportDXF
public override string ToString() public override string ToString()
{ {
return $"{JobNo} {DrawingNo}"; return $"{EquipmentNo} {DrawingNo}";
} }
public override bool Equals(object obj) public override bool Equals(object obj)
@@ -39,7 +39,7 @@ namespace ExportDXF
var dwg = new DrawingInfo(); var dwg = new DrawingInfo();
dwg.JobNo = match.Groups["jobNo"].Value; dwg.EquipmentNo = match.Groups["equipmentNo"].Value;
dwg.DrawingNo = match.Groups["dwgNo"].Value; dwg.DrawingNo = match.Groups["dwgNo"].Value;
dwg.Source = input; dwg.Source = input;

View File

@@ -26,7 +26,7 @@
<PublisherName>Rogers Engineering</PublisherName> <PublisherName>Rogers Engineering</PublisherName>
<CreateWebPageOnPublish>true</CreateWebPageOnPublish> <CreateWebPageOnPublish>true</CreateWebPageOnPublish>
<WebPage>publish.htm</WebPage> <WebPage>publish.htm</WebPage>
<ApplicationRevision>6</ApplicationRevision> <ApplicationRevision>8</ApplicationRevision>
<ApplicationVersion>1.6.0.%2a</ApplicationVersion> <ApplicationVersion>1.6.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted> <PublishWizardCompleted>true</PublishWizardCompleted>
@@ -70,6 +70,9 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="PresentationCore" /> <Reference Include="PresentationCore" />
<Reference Include="System.Net.Http" />
<Reference Include="System.IO.Compression" />
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="SolidWorks.Interop.sldworks, Version=24.1.0.45, Culture=neutral, PublicKeyToken=7c4797c3e4eeac03, processorArchitecture=MSIL"> <Reference Include="SolidWorks.Interop.sldworks, Version=24.1.0.45, Culture=neutral, PublicKeyToken=7c4797c3e4eeac03, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>False</EmbedInteropTypes> <EmbedInteropTypes>False</EmbedInteropTypes>
@@ -85,6 +88,7 @@
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.Security" /> <Reference Include="System.Security" />
<Reference Include="System.Web.Extensions" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
@@ -99,6 +103,18 @@
<Compile Include="Extensions\TimeSpanExtensions.cs" /> <Compile Include="Extensions\TimeSpanExtensions.cs" />
<Compile Include="Extensions\UIExtensions.cs" /> <Compile Include="Extensions\UIExtensions.cs" />
<Compile Include="Extensions\UnitConversionExtensions.cs" /> <Compile Include="Extensions\UnitConversionExtensions.cs" />
<Compile Include="Forms\DrawingSelectionForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\DrawingSelectionForm.Designer.cs">
<DependentUpon>DrawingSelectionForm.cs</DependentUpon>
</Compile>
<Compile Include="Forms\MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="ItemExtractors\AssemblyItemExtractor.cs" /> <Compile Include="ItemExtractors\AssemblyItemExtractor.cs" />
<Compile Include="ItemExtractors\BomColumnIndices.cs" /> <Compile Include="ItemExtractors\BomColumnIndices.cs" />
<Compile Include="ItemExtractors\BomItemExtractor.cs" /> <Compile Include="ItemExtractors\BomItemExtractor.cs" />
@@ -106,13 +122,14 @@
<Compile Include="Forms\ViewFlipDeciderComboboxItem.cs" /> <Compile Include="Forms\ViewFlipDeciderComboboxItem.cs" />
<Compile Include="Models\DocumentType.cs" /> <Compile Include="Models\DocumentType.cs" />
<Compile Include="Models\ExportContext.cs" /> <Compile Include="Models\ExportContext.cs" />
<Compile Include="Models\BomItem.cs" />
<Compile Include="Models\Item.cs" /> <Compile Include="Models\Item.cs" />
<Compile Include="Models\LogEvent.cs" />
<Compile Include="Models\SolidWorksDocument.cs" /> <Compile Include="Models\SolidWorksDocument.cs" />
<Compile Include="Services\BomExcelExporter.cs" />
<Compile Include="Services\BomExtractor.cs" /> <Compile Include="Services\BomExtractor.cs" />
<Compile Include="Services\BomExcelSettings.cs" />
<Compile Include="Services\DrawingExporter.cs" /> <Compile Include="Services\DrawingExporter.cs" />
<Compile Include="Services\DxfExportService.cs" /> <Compile Include="Services\DxfExportService.cs" />
<Compile Include="Services\CutFabApiClient.cs" />
<Compile Include="Services\PartExporter.cs" /> <Compile Include="Services\PartExporter.cs" />
<Compile Include="Services\SolidWorksService.cs" /> <Compile Include="Services\SolidWorksService.cs" />
<Compile Include="Utilities\SheetMetalProperties.cs" /> <Compile Include="Utilities\SheetMetalProperties.cs" />
@@ -122,16 +139,13 @@
<Compile Include="ViewFlipDeciders\AskViewFlipDecider.cs" /> <Compile Include="ViewFlipDeciders\AskViewFlipDecider.cs" />
<Compile Include="ViewFlipDeciders\AutoViewFlipDecider.cs" /> <Compile Include="ViewFlipDeciders\AutoViewFlipDecider.cs" />
<Compile Include="ViewFlipDeciders\IViewFlipDecider.cs" /> <Compile Include="ViewFlipDeciders\IViewFlipDecider.cs" />
<Compile Include="Forms\MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Forms\MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ViewFlipDeciders\PreferUpViewFlipDecider.cs" /> <Compile Include="ViewFlipDeciders\PreferUpViewFlipDecider.cs" />
<Compile Include="ViewFlipDeciders\ViewFlipDeciderFactory.cs" /> <Compile Include="ViewFlipDeciders\ViewFlipDeciderFactory.cs" />
<EmbeddedResource Include="Forms\DrawingSelectionForm.resx">
<DependentUpon>DrawingSelectionForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Forms\MainForm.resx"> <EmbeddedResource Include="Forms\MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon> <DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
@@ -158,9 +172,6 @@
<Content Include="Templates\Blank.drwdot"> <Content Include="Templates\Blank.drwdot">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Templates\BomTemplate.xlsx">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="Resources\edit_alt.png" /> <None Include="Resources\edit_alt.png" />
@@ -188,18 +199,31 @@
<Install>true</Install> <Install>true</Install>
</BootstrapperPackage> </BootstrapperPackage>
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="EPPlus">
<Version>4.5.3.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\EtchBendLines\EtchBendLines\EtchBendLines.csproj"> <ProjectReference Include="..\EtchBendLines\EtchBendLines\EtchBendLines.csproj">
<Project>{229c2fb9-6ad6-4a5d-b83a-d1146573d6f9}</Project> <Project>{229c2fb9-6ad6-4a5d-b83a-d1146573d6f9}</Project>
<Name>EtchBendLines</Name> <Name>EtchBendLines</Name>
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup>
<COMReference Include="AcroPDFLib">
<Guid>{05BFD3F1-6319-4F30-B752-C7A22889BCC4}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
<COMReference Include="AxAcroPDFLib">
<Guid>{05BFD3F1-6319-4F30-B752-C7A22889BCC4}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>aximp</WrapperTool>
<Isolated>False</Isolated>
</COMReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- 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. Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -5,8 +5,8 @@
/// </summary> /// </summary>
public static class UnitConversionExtensions public static class UnitConversionExtensions
{ {
private const double METERS_TO_MM = .0001; private const double METERS_TO_MM = 1000;
private const double METERS_TO_INCHES = 0.0254; private const double METERS_TO_INCHES = 39.37007874;
/// <summary> /// <summary>
/// Converts a SolidWorks dimension (in meters) to millimeters. /// Converts a SolidWorks dimension (in meters) to millimeters.

View File

@@ -0,0 +1,283 @@
namespace ExportDXF.Forms
{
partial class DrawingSelectionForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.equipmentLabel = new System.Windows.Forms.Label();
this.equipmentComboBox = new System.Windows.Forms.ComboBox();
this.drawingLabel = new System.Windows.Forms.Label();
this.drawingComboBox = new System.Windows.Forms.ComboBox();
this.newDrawingButton = new System.Windows.Forms.Button();
this.newDrawingPanel = new System.Windows.Forms.Panel();
this.qtyNumericUpDown = new System.Windows.Forms.NumericUpDown();
this.qtyLabel = new System.Windows.Forms.Label();
this.descriptionTextBox = new System.Windows.Forms.TextBox();
this.descriptionLabel = new System.Windows.Forms.Label();
this.drawingNumberTextBox = new System.Windows.Forms.TextBox();
this.drawingNumberLabel = new System.Windows.Forms.Label();
this.currentDrawingLabel = new System.Windows.Forms.Label();
this.okButton = new System.Windows.Forms.Button();
this.cancelButton = new System.Windows.Forms.Button();
this.statusLabel = new System.Windows.Forms.Label();
this.newDrawingPanel.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.qtyNumericUpDown)).BeginInit();
this.SuspendLayout();
//
// equipmentLabel
//
this.equipmentLabel.AutoSize = true;
this.equipmentLabel.Location = new System.Drawing.Point(12, 15);
this.equipmentLabel.Name = "equipmentLabel";
this.equipmentLabel.Size = new System.Drawing.Size(82, 17);
this.equipmentLabel.TabIndex = 0;
this.equipmentLabel.Text = "Equipment #";
//
// equipmentComboBox
//
this.equipmentComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.equipmentComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.equipmentComboBox.FormattingEnabled = true;
this.equipmentComboBox.Location = new System.Drawing.Point(100, 12);
this.equipmentComboBox.Name = "equipmentComboBox";
this.equipmentComboBox.Size = new System.Drawing.Size(470, 25);
this.equipmentComboBox.TabIndex = 1;
this.equipmentComboBox.SelectedIndexChanged += new System.EventHandler(this.equipmentComboBox_SelectedIndexChanged);
//
// drawingLabel
//
this.drawingLabel.AutoSize = true;
this.drawingLabel.Location = new System.Drawing.Point(12, 48);
this.drawingLabel.Name = "drawingLabel";
this.drawingLabel.Size = new System.Drawing.Size(68, 17);
this.drawingLabel.TabIndex = 2;
this.drawingLabel.Text = "Drawing #";
//
// drawingComboBox
//
this.drawingComboBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.drawingComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.drawingComboBox.FormattingEnabled = true;
this.drawingComboBox.Location = new System.Drawing.Point(100, 45);
this.drawingComboBox.Name = "drawingComboBox";
this.drawingComboBox.Size = new System.Drawing.Size(370, 25);
this.drawingComboBox.TabIndex = 3;
//
// newDrawingButton
//
this.newDrawingButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.newDrawingButton.Location = new System.Drawing.Point(476, 43);
this.newDrawingButton.Name = "newDrawingButton";
this.newDrawingButton.Size = new System.Drawing.Size(94, 27);
this.newDrawingButton.TabIndex = 4;
this.newDrawingButton.Text = "New Drawing";
this.newDrawingButton.UseVisualStyleBackColor = true;
this.newDrawingButton.Click += new System.EventHandler(this.newDrawingButton_Click);
//
// newDrawingPanel
//
this.newDrawingPanel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.newDrawingPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.newDrawingPanel.Controls.Add(this.qtyNumericUpDown);
this.newDrawingPanel.Controls.Add(this.qtyLabel);
this.newDrawingPanel.Controls.Add(this.descriptionTextBox);
this.newDrawingPanel.Controls.Add(this.descriptionLabel);
this.newDrawingPanel.Controls.Add(this.drawingNumberTextBox);
this.newDrawingPanel.Controls.Add(this.drawingNumberLabel);
this.newDrawingPanel.Location = new System.Drawing.Point(15, 85);
this.newDrawingPanel.Name = "newDrawingPanel";
this.newDrawingPanel.Size = new System.Drawing.Size(555, 120);
this.newDrawingPanel.TabIndex = 5;
this.newDrawingPanel.Visible = false;
//
// qtyNumericUpDown
//
this.qtyNumericUpDown.Location = new System.Drawing.Point(124, 72);
this.qtyNumericUpDown.Maximum = new decimal(new int[] {
10000,
0,
0,
0});
this.qtyNumericUpDown.Minimum = new decimal(new int[] {
1,
0,
0,
0});
this.qtyNumericUpDown.Name = "qtyNumericUpDown";
this.qtyNumericUpDown.Size = new System.Drawing.Size(100, 25);
this.qtyNumericUpDown.TabIndex = 5;
this.qtyNumericUpDown.Value = new decimal(new int[] {
1,
0,
0,
0});
//
// qtyLabel
//
this.qtyLabel.AutoSize = true;
this.qtyLabel.Location = new System.Drawing.Point(10, 74);
this.qtyLabel.Name = "qtyLabel";
this.qtyLabel.Size = new System.Drawing.Size(56, 17);
this.qtyLabel.TabIndex = 4;
this.qtyLabel.Text = "Quantity";
//
// descriptionTextBox
//
this.descriptionTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.descriptionTextBox.Location = new System.Drawing.Point(124, 41);
this.descriptionTextBox.Name = "descriptionTextBox";
this.descriptionTextBox.Size = new System.Drawing.Size(411, 25);
this.descriptionTextBox.TabIndex = 3;
//
// descriptionLabel
//
this.descriptionLabel.AutoSize = true;
this.descriptionLabel.Location = new System.Drawing.Point(10, 44);
this.descriptionLabel.Name = "descriptionLabel";
this.descriptionLabel.Size = new System.Drawing.Size(74, 17);
this.descriptionLabel.TabIndex = 2;
this.descriptionLabel.Text = "Description";
//
// drawingNumberTextBox
//
this.drawingNumberTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.drawingNumberTextBox.Location = new System.Drawing.Point(124, 10);
this.drawingNumberTextBox.Name = "drawingNumberTextBox";
this.drawingNumberTextBox.Size = new System.Drawing.Size(411, 25);
this.drawingNumberTextBox.TabIndex = 1;
this.drawingNumberTextBox.TextChanged += new System.EventHandler(this.drawingNumberTextBox_TextChanged);
//
// drawingNumberLabel
//
this.drawingNumberLabel.AutoSize = true;
this.drawingNumberLabel.Location = new System.Drawing.Point(10, 13);
this.drawingNumberLabel.Name = "drawingNumberLabel";
this.drawingNumberLabel.Size = new System.Drawing.Size(108, 17);
this.drawingNumberLabel.TabIndex = 0;
this.drawingNumberLabel.Text = "Drawing Number";
//
// currentDrawingLabel
//
this.currentDrawingLabel.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.currentDrawingLabel.AutoEllipsis = true;
this.currentDrawingLabel.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.currentDrawingLabel.Location = new System.Drawing.Point(15, 217);
this.currentDrawingLabel.Name = "currentDrawingLabel";
this.currentDrawingLabel.Size = new System.Drawing.Size(557, 41);
this.currentDrawingLabel.TabIndex = 9;
this.currentDrawingLabel.Text = "Loading active drawing...";
//
// okButton
//
this.okButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.okButton.Enabled = false;
this.okButton.Location = new System.Drawing.Point(414, 273);
this.okButton.Name = "okButton";
this.okButton.Size = new System.Drawing.Size(75, 30);
this.okButton.TabIndex = 6;
this.okButton.Text = "OK";
this.okButton.UseVisualStyleBackColor = true;
this.okButton.Click += new System.EventHandler(this.okButton_Click);
//
// cancelButton
//
this.cancelButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.cancelButton.Location = new System.Drawing.Point(495, 273);
this.cancelButton.Name = "cancelButton";
this.cancelButton.Size = new System.Drawing.Size(75, 30);
this.cancelButton.TabIndex = 7;
this.cancelButton.Text = "Cancel";
this.cancelButton.UseVisualStyleBackColor = true;
this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click);
//
// statusLabel
//
this.statusLabel.AutoSize = true;
this.statusLabel.Location = new System.Drawing.Point(15, 280);
this.statusLabel.Name = "statusLabel";
this.statusLabel.Size = new System.Drawing.Size(44, 17);
this.statusLabel.TabIndex = 8;
this.statusLabel.Text = "Ready";
//
// DrawingSelectionForm
//
this.AcceptButton = this.okButton;
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.CancelButton = this.cancelButton;
this.ClientSize = new System.Drawing.Size(584, 315);
this.Controls.Add(this.currentDrawingLabel);
this.Controls.Add(this.statusLabel);
this.Controls.Add(this.cancelButton);
this.Controls.Add(this.okButton);
this.Controls.Add(this.newDrawingPanel);
this.Controls.Add(this.newDrawingButton);
this.Controls.Add(this.drawingComboBox);
this.Controls.Add(this.drawingLabel);
this.Controls.Add(this.equipmentComboBox);
this.Controls.Add(this.equipmentLabel);
this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "DrawingSelectionForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Select Drawing - ExportDXF";
this.newDrawingPanel.ResumeLayout(false);
this.newDrawingPanel.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.qtyNumericUpDown)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label equipmentLabel;
private System.Windows.Forms.ComboBox equipmentComboBox;
private System.Windows.Forms.Label drawingLabel;
private System.Windows.Forms.ComboBox drawingComboBox;
private System.Windows.Forms.Button newDrawingButton;
private System.Windows.Forms.Panel newDrawingPanel;
private System.Windows.Forms.TextBox drawingNumberTextBox;
private System.Windows.Forms.Label drawingNumberLabel;
private System.Windows.Forms.TextBox descriptionTextBox;
private System.Windows.Forms.Label descriptionLabel;
private System.Windows.Forms.NumericUpDown qtyNumericUpDown;
private System.Windows.Forms.Label qtyLabel;
private System.Windows.Forms.Label currentDrawingLabel;
private System.Windows.Forms.Button okButton;
private System.Windows.Forms.Button cancelButton;
private System.Windows.Forms.Label statusLabel;
}
}

View File

@@ -0,0 +1,260 @@
using ExportDXF.Services;
using System;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ExportDXF.Forms
{
public partial class DrawingSelectionForm : Form
{
private readonly ICutFabApiClient _apiClient;
private readonly ISolidWorksService _solidWorksService;
public int? SelectedDrawingId { get; private set; }
public string SelectedDrawingNumber { get; private set; }
public DrawingSelectionForm(ICutFabApiClient apiClient, ISolidWorksService solidWorksService)
{
_apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
_solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService));
_solidWorksService.ActiveDocumentChanged += (s, e) => DisplayActiveDrawing();
InitializeComponent();
}
protected override async void OnLoad(EventArgs e)
{
base.OnLoad(e);
DisplayActiveDrawing();
await LoadEquipmentAsync();
}
private void DisplayActiveDrawing()
{
try
{
var activeDoc = _solidWorksService.GetActiveDocument();
if (activeDoc != null && activeDoc.DocumentType == Models.DocumentType.Drawing)
{
currentDrawingLabel.Text = $"Active Drawing: {activeDoc.Title}";
currentDrawingLabel.ForeColor = Color.Green;
}
else if (activeDoc != null)
{
currentDrawingLabel.Text = $"Active Document: {activeDoc.Title} (Not a Drawing)";
currentDrawingLabel.ForeColor = Color.Orange;
}
else
{
currentDrawingLabel.Text = "No active SolidWorks document";
currentDrawingLabel.ForeColor = Color.Gray;
}
}
catch (Exception ex)
{
currentDrawingLabel.Text = $"Error getting active document: {ex.Message}";
currentDrawingLabel.ForeColor = Color.Red;
}
}
private async Task LoadEquipmentAsync()
{
try
{
statusLabel.Text = "Loading equipment...";
statusLabel.ForeColor = Color.Black;
var equipment = await _apiClient.GetEquipmentAsync();
equipmentComboBox.DisplayMember = nameof(CutFabApiClient.ApiEquipment.EquipmentNumber);
equipmentComboBox.ValueMember = nameof(CutFabApiClient.ApiEquipment.ID);
equipmentComboBox.DataSource = equipment;
if (equipment.Count > 0)
{
equipmentComboBox.SelectedIndex = 0;
statusLabel.Text = $"Loaded {equipment.Count} equipment record(s)";
statusLabel.ForeColor = Color.Green;
}
else
{
statusLabel.Text = "No equipment found";
statusLabel.ForeColor = Color.Red;
}
}
catch (Exception ex)
{
statusLabel.Text = $"Error loading equipment: {ex.Message}";
statusLabel.ForeColor = Color.Red;
MessageBox.Show($"Failed to load equipment: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private async void equipmentComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
await LoadDrawingsAsync();
}
private async Task LoadDrawingsAsync()
{
try
{
var selectedEquipment = equipmentComboBox.SelectedItem as CutFabApiClient.ApiEquipment;
if (selectedEquipment == null)
{
drawingComboBox.DataSource = null;
return;
}
statusLabel.Text = "Loading drawings...";
statusLabel.ForeColor = Color.Black;
var drawings = await _apiClient.GetDrawingsForEquipmentAsync(selectedEquipment.ID);
drawingComboBox.DisplayMember = nameof(CutFabApiClient.ApiDrawingSummary.DrawingNumber);
drawingComboBox.ValueMember = nameof(CutFabApiClient.ApiDrawingSummary.ID);
drawingComboBox.DataSource = drawings;
if (drawings.Count > 0)
{
statusLabel.Text = $"Loaded {drawings.Count} drawing(s)";
statusLabel.ForeColor = Color.Green;
drawingComboBox.Enabled = true;
}
else
{
statusLabel.Text = "No drawings found for this equipment";
statusLabel.ForeColor = Color.DarkBlue;
drawingComboBox.Enabled = false;
}
UpdateOkButtonState();
}
catch (Exception ex)
{
statusLabel.Text = $"Error loading drawings: {ex.Message}";
statusLabel.ForeColor = Color.Red;
}
}
private void newDrawingButton_Click(object sender, EventArgs e)
{
ToggleNewDrawingMode(!newDrawingPanel.Visible);
}
private void ToggleNewDrawingMode(bool enabled)
{
newDrawingPanel.Visible = enabled;
drawingComboBox.Enabled = !enabled;
newDrawingButton.Text = enabled ? "Cancel New" : "New Drawing";
if (enabled)
{
drawingNumberTextBox.Focus();
}
UpdateOkButtonState();
}
private void drawingNumberTextBox_TextChanged(object sender, EventArgs e)
{
UpdateOkButtonState();
}
private void UpdateOkButtonState()
{
if (newDrawingPanel.Visible)
{
// Creating new drawing - require drawing number
okButton.Enabled = !string.IsNullOrWhiteSpace(drawingNumberTextBox.Text);
}
else
{
// Selecting existing drawing
okButton.Enabled = drawingComboBox.SelectedItem != null;
}
}
private async void okButton_Click(object sender, EventArgs e)
{
try
{
okButton.Enabled = false;
statusLabel.Text = "Processing...";
statusLabel.ForeColor = Color.Black;
if (newDrawingPanel.Visible)
{
// Create new drawing
await CreateNewDrawingAsync();
}
else
{
// Use existing drawing
var selectedDrawing = drawingComboBox.SelectedItem as CutFabApiClient.ApiDrawingSummary;
if (selectedDrawing != null)
{
SelectedDrawingId = selectedDrawing.ID;
SelectedDrawingNumber = selectedDrawing.DrawingNumber;
DialogResult = DialogResult.OK;
Close();
}
}
}
catch (Exception ex)
{
statusLabel.Text = $"Error: {ex.Message}";
statusLabel.ForeColor = Color.Red;
MessageBox.Show($"Operation failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
okButton.Enabled = true;
}
}
private async Task CreateNewDrawingAsync()
{
var selectedEquipment = equipmentComboBox.SelectedItem as CutFabApiClient.ApiEquipment;
if (selectedEquipment == null)
{
MessageBox.Show("Please select equipment first.", "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var drawingNumber = drawingNumberTextBox.Text.Trim();
if (string.IsNullOrWhiteSpace(drawingNumber))
{
MessageBox.Show("Please enter a drawing number.", "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
statusLabel.Text = "Creating new drawing...";
statusLabel.ForeColor = Color.Black;
var response = await _apiClient.CreateDrawingWithInfoAsync(selectedEquipment.ID, drawingNumber);
if (response.Success && response.Data.HasValue)
{
SelectedDrawingId = response.Data.Value;
SelectedDrawingNumber = drawingNumber;
statusLabel.Text = "Drawing created successfully";
statusLabel.ForeColor = Color.Green;
DialogResult = DialogResult.OK;
Close();
}
else
{
var errorMsg = response.Error ?? "Unknown error occurred";
statusLabel.Text = $"Failed to create drawing: {errorMsg}";
statusLabel.ForeColor = Color.Red;
MessageBox.Show($"Failed to create drawing: {errorMsg}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
okButton.Enabled = true;
}
}
private void cancelButton_Click(object sender, EventArgs e)
{
DialogResult = DialogResult.Cancel;
Close();
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -28,116 +28,189 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
this.activeDocTitleBox = new System.Windows.Forms.TextBox(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.richTextBox1 = new System.Windows.Forms.RichTextBox(); this.runButton = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.prefixTextBox = new System.Windows.Forms.TextBox();
this.button1 = new System.Windows.Forms.Button();
this.label3 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label();
this.comboBox1 = new System.Windows.Forms.ComboBox(); this.viewFlipDeciderBox = new System.Windows.Forms.ComboBox();
this.mainTabControl = new System.Windows.Forms.TabControl();
this.logEventsTab = new System.Windows.Forms.TabPage();
this.logEventsDataGrid = new System.Windows.Forms.DataGridView();
this.bomTab = new System.Windows.Forms.TabPage();
this.bomDataGrid = new System.Windows.Forms.DataGridView();
this.cutTemplatesTab = new System.Windows.Forms.TabPage();
this.cutTemplatesDataGrid = new System.Windows.Forms.DataGridView();
this.dwgDetailsTab = new System.Windows.Forms.TabPage();
this.drawingPdfViewer = new AxAcroPDFLib.AxAcroPDF();
this.mainTabControl.SuspendLayout();
this.logEventsTab.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.logEventsDataGrid)).BeginInit();
this.bomTab.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.bomDataGrid)).BeginInit();
this.cutTemplatesTab.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.cutTemplatesDataGrid)).BeginInit();
this.dwgDetailsTab.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.drawingPdfViewer)).BeginInit();
this.SuspendLayout(); this.SuspendLayout();
// //
// activeDocTitleBox // runButton
// //
this.activeDocTitleBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) this.runButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
| System.Windows.Forms.AnchorStyles.Right))); this.runButton.Location = new System.Drawing.Point(790, 13);
this.activeDocTitleBox.BackColor = System.Drawing.Color.White; this.runButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.activeDocTitleBox.Location = new System.Drawing.Point(130, 13); this.runButton.Name = "runButton";
this.activeDocTitleBox.Name = "activeDocTitleBox"; this.runButton.Size = new System.Drawing.Size(100, 30);
this.activeDocTitleBox.ReadOnly = true; this.runButton.TabIndex = 11;
this.activeDocTitleBox.Size = new System.Drawing.Size(584, 25); this.runButton.Text = "Start";
this.activeDocTitleBox.TabIndex = 2; this.runButton.UseVisualStyleBackColor = true;
this.activeDocTitleBox.TextChanged += new System.EventHandler(this.activeDocTitleBox_TextChanged); this.runButton.Click += new System.EventHandler(this.button1_Click);
//
// richTextBox1
//
this.richTextBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.richTextBox1.BackColor = System.Drawing.Color.White;
this.richTextBox1.Location = new System.Drawing.Point(12, 106);
this.richTextBox1.Name = "richTextBox1";
this.richTextBox1.ReadOnly = true;
this.richTextBox1.Size = new System.Drawing.Size(754, 342);
this.richTextBox1.TabIndex = 3;
this.richTextBox1.Text = "";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(13, 16);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(111, 17);
this.label1.TabIndex = 4;
this.label1.Text = "Active document :";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(23, 47);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(101, 17);
this.label2.TabIndex = 4;
this.label2.Text = "Prefix files with :";
//
// prefixTextBox
//
this.prefixTextBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.prefixTextBox.Location = new System.Drawing.Point(130, 44);
this.prefixTextBox.Name = "prefixTextBox";
this.prefixTextBox.Size = new System.Drawing.Size(584, 25);
this.prefixTextBox.TabIndex = 2;
//
// button1
//
this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.button1.Image = global::ExportDXF.Properties.Resources.play;
this.button1.Location = new System.Drawing.Point(720, 13);
this.button1.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(46, 56);
this.button1.TabIndex = 0;
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
// //
// label3 // label3
// //
this.label3.AutoSize = true; this.label3.AutoSize = true;
this.label3.Location = new System.Drawing.Point(12, 78); this.label3.Location = new System.Drawing.Point(12, 20);
this.label3.Name = "label3"; this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(112, 17); this.label3.Size = new System.Drawing.Size(112, 17);
this.label3.TabIndex = 4; this.label3.TabIndex = 2;
this.label3.Text = "View flip decider :"; this.label3.Text = "View flip decider :";
// //
// comboBox1 // viewFlipDeciderBox
// //
this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; this.viewFlipDeciderBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBox1.FormattingEnabled = true; this.viewFlipDeciderBox.FormattingEnabled = true;
this.comboBox1.Location = new System.Drawing.Point(130, 75); this.viewFlipDeciderBox.Location = new System.Drawing.Point(130, 17);
this.comboBox1.Name = "comboBox1"; this.viewFlipDeciderBox.Name = "viewFlipDeciderBox";
this.comboBox1.Size = new System.Drawing.Size(353, 25); this.viewFlipDeciderBox.Size = new System.Drawing.Size(375, 25);
this.comboBox1.TabIndex = 5; this.viewFlipDeciderBox.TabIndex = 3;
//
// mainTabControl
//
this.mainTabControl.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.mainTabControl.Controls.Add(this.logEventsTab);
this.mainTabControl.Controls.Add(this.bomTab);
this.mainTabControl.Controls.Add(this.cutTemplatesTab);
this.mainTabControl.Controls.Add(this.dwgDetailsTab);
this.mainTabControl.Location = new System.Drawing.Point(15, 50);
this.mainTabControl.Name = "mainTabControl";
this.mainTabControl.Padding = new System.Drawing.Point(20, 5);
this.mainTabControl.SelectedIndex = 0;
this.mainTabControl.Size = new System.Drawing.Size(879, 594);
this.mainTabControl.TabIndex = 12;
//
// logEventsTab
//
this.logEventsTab.Controls.Add(this.logEventsDataGrid);
this.logEventsTab.Location = new System.Drawing.Point(4, 30);
this.logEventsTab.Name = "logEventsTab";
this.logEventsTab.Padding = new System.Windows.Forms.Padding(3);
this.logEventsTab.Size = new System.Drawing.Size(871, 560);
this.logEventsTab.TabIndex = 0;
this.logEventsTab.Text = "Log Events";
this.logEventsTab.UseVisualStyleBackColor = true;
//
// logEventsDataGrid
//
this.logEventsDataGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.logEventsDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.logEventsDataGrid.Location = new System.Drawing.Point(6, 6);
this.logEventsDataGrid.Name = "logEventsDataGrid";
this.logEventsDataGrid.Size = new System.Drawing.Size(859, 548);
this.logEventsDataGrid.TabIndex = 0;
//
// bomTab
//
this.bomTab.Controls.Add(this.bomDataGrid);
this.bomTab.Location = new System.Drawing.Point(4, 30);
this.bomTab.Name = "bomTab";
this.bomTab.Padding = new System.Windows.Forms.Padding(3);
this.bomTab.Size = new System.Drawing.Size(871, 560);
this.bomTab.TabIndex = 1;
this.bomTab.Text = "Bill Of Materials";
this.bomTab.UseVisualStyleBackColor = true;
//
// bomDataGrid
//
this.bomDataGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.bomDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.bomDataGrid.Location = new System.Drawing.Point(6, 6);
this.bomDataGrid.Name = "bomDataGrid";
this.bomDataGrid.Size = new System.Drawing.Size(859, 548);
this.bomDataGrid.TabIndex = 1;
//
// cutTemplatesTab
//
this.cutTemplatesTab.Controls.Add(this.cutTemplatesDataGrid);
this.cutTemplatesTab.Location = new System.Drawing.Point(4, 30);
this.cutTemplatesTab.Name = "cutTemplatesTab";
this.cutTemplatesTab.Padding = new System.Windows.Forms.Padding(3);
this.cutTemplatesTab.Size = new System.Drawing.Size(871, 560);
this.cutTemplatesTab.TabIndex = 3;
this.cutTemplatesTab.Text = "Cut Templates";
this.cutTemplatesTab.UseVisualStyleBackColor = true;
//
// cutTemplatesDataGrid
//
this.cutTemplatesDataGrid.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.cutTemplatesDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.cutTemplatesDataGrid.Location = new System.Drawing.Point(6, 6);
this.cutTemplatesDataGrid.Name = "cutTemplatesDataGrid";
this.cutTemplatesDataGrid.Size = new System.Drawing.Size(859, 548);
this.cutTemplatesDataGrid.TabIndex = 2;
//
// dwgDetailsTab
//
this.dwgDetailsTab.Controls.Add(this.drawingPdfViewer);
this.dwgDetailsTab.Location = new System.Drawing.Point(4, 30);
this.dwgDetailsTab.Name = "dwgDetailsTab";
this.dwgDetailsTab.Padding = new System.Windows.Forms.Padding(3);
this.dwgDetailsTab.Size = new System.Drawing.Size(871, 560);
this.dwgDetailsTab.TabIndex = 2;
this.dwgDetailsTab.Text = "Drawing Details";
this.dwgDetailsTab.UseVisualStyleBackColor = true;
//
// drawingPdfViewer
//
this.drawingPdfViewer.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.drawingPdfViewer.Enabled = true;
this.drawingPdfViewer.Location = new System.Drawing.Point(6, 6);
this.drawingPdfViewer.Name = "drawingPdfViewer";
this.drawingPdfViewer.OcxState = ((System.Windows.Forms.AxHost.State)(resources.GetObject("drawingPdfViewer.OcxState")));
this.drawingPdfViewer.Size = new System.Drawing.Size(859, 548);
this.drawingPdfViewer.TabIndex = 0;
// //
// MainForm // MainForm
// //
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.ClientSize = new System.Drawing.Size(778, 460); this.ClientSize = new System.Drawing.Size(906, 656);
this.Controls.Add(this.comboBox1); this.Controls.Add(this.mainTabControl);
this.Controls.Add(this.viewFlipDeciderBox);
this.Controls.Add(this.label3); this.Controls.Add(this.label3);
this.Controls.Add(this.label2); this.Controls.Add(this.runButton);
this.Controls.Add(this.label1);
this.Controls.Add(this.richTextBox1);
this.Controls.Add(this.prefixTextBox);
this.Controls.Add(this.activeDocTitleBox);
this.Controls.Add(this.button1);
this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.MaximizeBox = false; this.MaximizeBox = false;
this.MinimumSize = new System.Drawing.Size(643, 355);
this.Name = "MainForm"; this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "ExportDXF"; this.Text = "ExportDXF";
this.mainTabControl.ResumeLayout(false);
this.logEventsTab.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.logEventsDataGrid)).EndInit();
this.bomTab.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.bomDataGrid)).EndInit();
this.cutTemplatesTab.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.cutTemplatesDataGrid)).EndInit();
this.dwgDetailsTab.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.drawingPdfViewer)).EndInit();
this.ResumeLayout(false); this.ResumeLayout(false);
this.PerformLayout(); this.PerformLayout();
@@ -145,14 +218,18 @@
#endregion #endregion
private System.Windows.Forms.Button button1; private System.Windows.Forms.Button runButton;
private System.Windows.Forms.TextBox activeDocTitleBox;
private System.Windows.Forms.RichTextBox richTextBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.TextBox prefixTextBox;
private System.Windows.Forms.Label label3; private System.Windows.Forms.Label label3;
private System.Windows.Forms.ComboBox comboBox1; private System.Windows.Forms.ComboBox viewFlipDeciderBox;
private System.Windows.Forms.TabControl mainTabControl;
private System.Windows.Forms.TabPage logEventsTab;
private System.Windows.Forms.TabPage bomTab;
private System.Windows.Forms.TabPage dwgDetailsTab;
private System.Windows.Forms.DataGridView logEventsDataGrid;
private AxAcroPDFLib.AxAcroPDF drawingPdfViewer;
private System.Windows.Forms.DataGridView bomDataGrid;
private System.Windows.Forms.TabPage cutTemplatesTab;
private System.Windows.Forms.DataGridView cutTemplatesDataGrid;
} }
} }

View File

@@ -1,35 +1,47 @@
using ExportDXF.Extensions; using ExportDXF.Extensions;
using ExportDXF.Models; using ExportDXF.Models;
using ExportDXF.Services; using ExportDXF.Services;
using ExportDXF.ViewFlipDeciders; using ExportDXF.ViewFlipDeciders;
using System; using System;
using System.ComponentModel;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace ExportDXF.Forms namespace ExportDXF.Forms
{ {
public partial class MainForm : Form public partial class MainForm : Form
{ {
private readonly ISolidWorksService _solidWorksService; private readonly ISolidWorksService _solidWorksService;
private readonly IDxfExportService _exportService; private readonly IDxfExportService _exportService;
private readonly ICutFabApiClient _apiClient;
private CancellationTokenSource _cancellationTokenSource; private CancellationTokenSource _cancellationTokenSource;
private readonly BindingList<LogEvent> _logEvents;
private readonly BindingList<BomItem> _bomItems;
private readonly BindingList<CutFabApiClient.ApiCutTemplate> _cutTemplates;
private readonly int _selectedDrawingId;
private readonly string _selectedDrawingNumber;
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService) public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, ICutFabApiClient apiClient, int selectedDrawingId, string selectedDrawingNumber)
{ {
InitializeComponent(); InitializeComponent();
_solidWorksService = solidWorksService ?? _solidWorksService = solidWorksService ??
throw new ArgumentNullException(nameof(solidWorksService)); throw new ArgumentNullException(nameof(solidWorksService));
_exportService = exportService ?? _exportService = exportService ??
throw new ArgumentNullException(nameof(exportService)); throw new ArgumentNullException(nameof(exportService));
_apiClient = apiClient ??
throw new ArgumentNullException(nameof(apiClient));
_selectedDrawingId = selectedDrawingId;
_selectedDrawingNumber = selectedDrawingNumber ?? throw new ArgumentNullException(nameof(selectedDrawingNumber));
_logEvents = new BindingList<LogEvent>();
_bomItems = new BindingList<BomItem>();
_cutTemplates = new BindingList<CutFabApiClient.ApiCutTemplate>();
InitializeViewFlipDeciders(); InitializeViewFlipDeciders();
InitializeLogEventsGrid();
InitializeBomGrid();
InitializeCutTemplatesGrid();
} }
~MainForm() ~MainForm()
{ {
_cancellationTokenSource?.Dispose(); _cancellationTokenSource?.Dispose();
@@ -37,29 +49,29 @@ namespace ExportDXF.Forms
components?.Dispose(); components?.Dispose();
Dispose(false); Dispose(false);
} }
protected override async void OnLoad(EventArgs e) protected override async void OnLoad(EventArgs e)
{ {
base.OnLoad(e); base.OnLoad(e);
runButton.Enabled = false;
button1.Enabled = false;
await InitializeAsync(); await InitializeAsync();
} }
private async Task InitializeAsync() private async Task InitializeAsync()
{ {
try try
{ {
LogMessage("Connecting to SolidWorks, this may take a minute..."); LogMessage("Connecting to SolidWorks, this may take a minute...");
await _solidWorksService.ConnectAsync(); await _solidWorksService.ConnectAsync();
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged; _solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
LogMessage("Ready", Color.Green); LogMessage("Ready", Color.Green);
UpdateActiveDocumentDisplay(); UpdateActiveDocumentDisplay();
button1.Enabled = true; runButton.Enabled = true;
// Load the selected drawing's BOM items
LogMessage($"Loading BOM items for drawing {_selectedDrawingNumber}...");
await LoadBomItemsForDrawingAsync(_selectedDrawingId);
// Load cut templates
await LoadCutTemplatesAsync();
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -68,6 +80,69 @@ namespace ExportDXF.Forms
Application.Exit(); Application.Exit();
} }
} }
private async Task LoadBomItemsForDrawingAsync(int drawingId)
{
try
{
_bomItems.Clear();
var apiBomItems = await _apiClient.GetBomItemsForDrawingAsync(drawingId);
foreach (var apiItem in apiBomItems)
{
var bomItem = new BomItem
{
ID = apiItem.ID,
ItemNo = apiItem.ItemNo ?? "",
PartNo = apiItem.PartNo ?? "",
SortOrder = apiItem.SortOrder,
Qty = apiItem.Qty,
TotalQty = apiItem.TotalQty,
Description = apiItem.Description ?? "",
PartName = apiItem.PartName ?? "",
ConfigurationName = apiItem.ConfigurationName ?? "",
Material = apiItem.Material ?? "",
DrawingID = apiItem.DrawingID,
CutTemplateID = apiItem.CutTemplateID,
CutTemplateName = apiItem.CutTemplateName ?? "",
Thickness = apiItem.Thickness,
KFactor = apiItem.KFactor,
DefaultBendRadius = apiItem.DefaultBendRadius
};
_bomItems.Add(bomItem);
}
LogMessage($"Loaded {apiBomItems.Count} BOM item(s)", Color.Green);
}
catch (Exception ex)
{
LogMessage($"Failed to load BOM items: {ex.Message}", Color.Red);
}
}
private async Task LoadCutTemplatesAsync()
{
try
{
_cutTemplates.Clear();
LogMessage("Loading cut templates...");
var apiCutTemplates = await _apiClient.GetCutTemplatesAsync();
foreach (var template in apiCutTemplates)
{
_cutTemplates.Add(template);
}
LogMessage($"Loaded {apiCutTemplates.Count} cut template(s)", Color.Green);
}
catch (Exception ex)
{
LogMessage($"Failed to load cut templates: {ex.Message}", Color.Red);
}
}
private void InitializeViewFlipDeciders() private void InitializeViewFlipDeciders()
{ {
@@ -78,7 +153,6 @@ namespace ExportDXF.Forms
ViewFlipDecider = d ViewFlipDecider = d
}) })
.ToList(); .ToList();
// Move "Automatic" to the top if it exists // Move "Automatic" to the top if it exists
var automatic = items.FirstOrDefault(i => i.Name == "Automatic"); var automatic = items.FirstOrDefault(i => i.Name == "Automatic");
if (automatic != null) if (automatic != null)
@@ -86,9 +160,188 @@ namespace ExportDXF.Forms
items.Remove(automatic); items.Remove(automatic);
items.Insert(0, automatic); items.Insert(0, automatic);
} }
viewFlipDeciderBox.DataSource = items;
viewFlipDeciderBox.DisplayMember = "Name";
}
private void InitializeLogEventsGrid()
{
// Clear any existing columns first
logEventsDataGrid.Columns.Clear();
comboBox1.DataSource = items; // Configure grid settings
comboBox1.DisplayMember = "Name"; logEventsDataGrid.AutoGenerateColumns = false;
logEventsDataGrid.AllowUserToAddRows = false;
logEventsDataGrid.AllowUserToDeleteRows = false;
logEventsDataGrid.ReadOnly = true;
logEventsDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
// Add columns
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(LogEvent.Time),
HeaderText = "Time",
Width = 80,
DefaultCellStyle = new DataGridViewCellStyle { Format = "HH:mm:ss" }
});
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(LogEvent.Level),
HeaderText = "Level",
Width = 70
});
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(LogEvent.Message),
HeaderText = "Message",
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
});
// Set the data source AFTER adding columns
logEventsDataGrid.DataSource = _logEvents;
}
private void InitializeBomGrid()
{
// Clear any existing columns first
bomDataGrid.Columns.Clear();
// Configure grid settings
bomDataGrid.AutoGenerateColumns = false;
bomDataGrid.AllowUserToAddRows = false;
bomDataGrid.AllowUserToDeleteRows = false;
bomDataGrid.ReadOnly = true;
bomDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
// Add columns
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(BomItem.ItemNo),
HeaderText = "Item #",
Width = 60
});
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(BomItem.PartNo),
HeaderText = "Part #",
Width = 100
});
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(BomItem.Qty),
HeaderText = "Qty",
Width = 50
});
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(BomItem.Description),
HeaderText = "Description",
Width = 200
});
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(BomItem.PartName),
HeaderText = "Part Name",
Width = 150
});
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(BomItem.ConfigurationName),
HeaderText = "Configuration",
Width = 120
});
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(BomItem.Material),
HeaderText = "Material",
Width = 120
});
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(BomItem.CutTemplateName),
HeaderText = "Cut Template",
Width = 120
});
// Set the data source AFTER adding columns
bomDataGrid.DataSource = _bomItems;
}
private void InitializeCutTemplatesGrid()
{
// Clear any existing columns first
cutTemplatesDataGrid.Columns.Clear();
// Configure grid settings
cutTemplatesDataGrid.AutoGenerateColumns = false;
cutTemplatesDataGrid.AllowUserToAddRows = false;
cutTemplatesDataGrid.AllowUserToDeleteRows = false;
cutTemplatesDataGrid.ReadOnly = true;
cutTemplatesDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
// Add columns
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Name),
HeaderText = "Name",
Width = 200
});
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Material),
HeaderText = "Material",
Width = 120
});
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Thickness),
HeaderText = "Thickness",
Width = 80,
DefaultCellStyle = new DataGridViewCellStyle { Format = "0.##" }
});
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.KFactor),
HeaderText = "K-Factor",
Width = 80,
DefaultCellStyle = new DataGridViewCellStyle { Format = "0.####" }
});
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.DefaultBendRadius),
HeaderText = "Bend Radius",
Width = 90,
DefaultCellStyle = new DataGridViewCellStyle { Format = "0.##" }
});
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Version),
HeaderText = "Version",
Width = 70
});
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
{
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Description),
HeaderText = "Description",
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
});
// Set the data source AFTER adding columns
cutTemplatesDataGrid.DataSource = _cutTemplates;
} }
private async void button1_Click(object sender, EventArgs e) private async void button1_Click(object sender, EventArgs e)
@@ -102,39 +355,31 @@ namespace ExportDXF.Forms
await StartExportAsync(); await StartExportAsync();
} }
} }
private async Task StartExportAsync() private async Task StartExportAsync()
{ {
try try
{ {
_cancellationTokenSource = new CancellationTokenSource(); _cancellationTokenSource = new CancellationTokenSource();
var token = _cancellationTokenSource.Token; var token = _cancellationTokenSource.Token;
UpdateUIForExportStart(); UpdateUIForExportStart();
var activeDoc = _solidWorksService.GetActiveDocument(); var activeDoc = _solidWorksService.GetActiveDocument();
if (activeDoc == null) if (activeDoc == null)
{ {
LogMessage("No active document.", Color.Red); LogMessage("No active document.", Color.Red);
return; return;
} }
var viewFlipDecider = GetSelectedViewFlipDecider(); var viewFlipDecider = GetSelectedViewFlipDecider();
var prefix = prefixTextBox.Text;
var exportContext = new ExportContext var exportContext = new ExportContext
{ {
ActiveDocument = activeDoc, ActiveDocument = activeDoc,
ViewFlipDecider = viewFlipDecider, ViewFlipDecider = viewFlipDecider,
FilePrefix = prefix, FilePrefix = _selectedDrawingNumber,
EquipmentId = null, // Not needed for export
CancellationToken = token, CancellationToken = token,
ProgressCallback = LogMessage ProgressCallback = LogMessage
}; };
LogMessage($"Started at {DateTime.Now:t}"); LogMessage($"Started at {DateTime.Now:t}");
await Task.Run(() => _exportService.Export(exportContext), token); await Task.Run(() => _exportService.Export(exportContext), token);
LogMessage("Done.", Color.Green); LogMessage("Done.", Color.Green);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -153,41 +398,27 @@ namespace ExportDXF.Forms
_cancellationTokenSource = null; _cancellationTokenSource = null;
} }
} }
private void CancelExport() private void CancelExport()
{ {
button1.Enabled = false; runButton.Enabled = false;
_cancellationTokenSource?.Cancel(); _cancellationTokenSource?.Cancel();
} }
private IViewFlipDecider GetSelectedViewFlipDecider() private IViewFlipDecider GetSelectedViewFlipDecider()
{ {
var item = comboBox1.SelectedItem as ViewFlipDeciderComboboxItem; var item = viewFlipDeciderBox.SelectedItem as ViewFlipDeciderComboboxItem;
return item?.ViewFlipDecider; return item?.ViewFlipDecider;
} }
private void UpdateUIForExportStart() private void UpdateUIForExportStart()
{ {
activeDocTitleBox.Enabled = false; viewFlipDeciderBox.Enabled = false;
prefixTextBox.Enabled = false; runButton.Image = Properties.Resources.stop_alt;
comboBox1.Enabled = false;
button1.Image = Properties.Resources.stop_alt;
if (richTextBox1.TextLength != 0)
{
richTextBox1.AppendText("\n\n");
}
} }
private void UpdateUIForExportComplete() private void UpdateUIForExportComplete()
{ {
activeDocTitleBox.Enabled = true; viewFlipDeciderBox.Enabled = true;
prefixTextBox.Enabled = true; runButton.Image = Properties.Resources.play;
comboBox1.Enabled = true; runButton.Enabled = true;
button1.Image = Properties.Resources.play;
button1.Enabled = true;
} }
private void OnActiveDocumentChanged(object sender, EventArgs e) private void OnActiveDocumentChanged(object sender, EventArgs e)
{ {
if (InvokeRequired) if (InvokeRequired)
@@ -195,65 +426,53 @@ namespace ExportDXF.Forms
Invoke(new Action(() => OnActiveDocumentChanged(sender, e))); Invoke(new Action(() => OnActiveDocumentChanged(sender, e)));
return; return;
} }
UpdateActiveDocumentDisplay(); UpdateActiveDocumentDisplay();
UpdatePrefixFromActiveDocument();
} }
private void UpdateActiveDocumentDisplay() private void UpdateActiveDocumentDisplay()
{ {
var activeDoc = _solidWorksService.GetActiveDocument(); var activeDoc = _solidWorksService.GetActiveDocument();
activeDocTitleBox.Text = activeDoc?.Title ?? "<No Document Open>"; var docTitle = activeDoc?.Title ?? "No Document Open";
this.Text = $"ExportDXF - {docTitle}";
} }
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)
{
prefixTextBox.Text = $"{drawingInfo.JobNo} {drawingInfo.DrawingNo} PT";
prefixTextBox.SelectionStart = prefixTextBox.Text.Length;
}
}
else
{
prefixTextBox.Text = string.Empty;
}
}
private void activeDocTitleBox_TextChanged(object sender, EventArgs e)
{
UpdatePrefixFromActiveDocument();
}
private void LogMessage(string message, Color? color = null) private void LogMessage(string message, Color? color = null)
{
var level = color.HasValue && color.Value == Color.Red ? LogLevel.Error :
color.HasValue && color.Value == Color.Green ? LogLevel.Info :
color.HasValue && color.Value == Color.DarkBlue ? LogLevel.Warning :
LogLevel.Info;
AddLogEvent(level, LogAction.Start, message);
}
private void AddLogEvent(LogLevel level, LogAction action, string message, string equipment = "", string drawing = "", string part = "", string target = "", string result = "OK", int durationMs = 0)
{ {
if (InvokeRequired) if (InvokeRequired)
{ {
Invoke(new Action(() => LogMessage(message, color))); Invoke(new Action(() => AddLogEvent(level, action, message, equipment, drawing, part, target, result, durationMs)));
return; return;
} }
if (color.HasValue) var logEvent = new LogEvent
{ {
richTextBox1.AppendText(message + System.Environment.NewLine, color.Value); Time = DateTime.Now,
} Level = level,
else Action = action,
{ Message = message,
richTextBox1.AppendText(message + System.Environment.NewLine); Equipment = equipment,
} Drawing = drawing,
Part = part,
Target = target,
Result = result,
DurationMs = durationMs
};
richTextBox1.ScrollToCaret(); _logEvents.Add(logEvent);
// Auto-scroll to the last row
if (logEventsDataGrid.Rows.Count > 0)
{
logEventsDataGrid.FirstDisplayedScrollingRowIndex = logEventsDataGrid.Rows.Count - 1;
}
} }
} }
} }

View File

@@ -117,4 +117,12 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="drawingPdfViewer.OcxState" mimetype="application/x-microsoft.net.object.binary.base64">
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACFTeXN0
ZW0uV2luZG93cy5Gb3Jtcy5BeEhvc3QrU3RhdGUBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAIQAAAAIB
AAAAAQAAAAAAAAAAAAAAAAwAAAAADgAASGsAANA3AAAL
</value>
</data>
</root> </root>

View File

@@ -0,0 +1,32 @@
using System;
namespace ExportDXF.Models
{
public class BomItem
{
public int ID { get; set; }
public string ItemNo { get; set; } = "";
public string PartNo { get; set; } = "";
public int SortOrder { get; set; }
public int? Qty { get; set; }
public int? TotalQty { get; set; }
public string Description { get; set; } = "";
public string PartName { get; set; } = "";
public string ConfigurationName { get; set; } = "";
public string Material { get; set; } = "";
public int DrawingID { get; set; }
public int? CutTemplateID { get; set; }
public string CutTemplateName { get; set; } = "";
// Sheet metal properties from CutTemplate
public double? Thickness { get; set; }
public double? KFactor { get; set; }
public double? DefaultBendRadius { get; set; }
}
public struct Size
{
public double Width { get; set; }
public double Height { get; set; }
}
}

View File

@@ -1,7 +1,11 @@
using ExportDXF.ViewFlipDeciders; using ExportDXF.ViewFlipDeciders;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using System; using System;
using System.Drawing; using System.Drawing;
using System.IO;
using System.Threading; using System.Threading;
using System.Windows.Forms;
namespace ExportDXF.Services namespace ExportDXF.Services
{ {
@@ -10,6 +14,9 @@ namespace ExportDXF.Services
/// </summary> /// </summary>
public class ExportContext public class ExportContext
{ {
private const string DRAWING_TEMPLATE_FOLDER = "Templates";
private const string DRAWING_TEMPLATE_FILE = "Blank.drwdot";
/// <summary> /// <summary>
/// The document to be exported. /// The document to be exported.
/// </summary> /// </summary>
@@ -25,6 +32,11 @@ namespace ExportDXF.Services
/// </summary> /// </summary>
public string FilePrefix { get; set; } public string FilePrefix { get; set; }
/// <summary>
/// Selected Equipment ID for API operations (optional).
/// </summary>
public int? EquipmentId { get; set; }
/// <summary> /// <summary>
/// Cancellation token for canceling the export operation. /// Cancellation token for canceling the export operation.
/// </summary> /// </summary>
@@ -34,5 +46,90 @@ namespace ExportDXF.Services
/// Callback for reporting progress and status messages. /// Callback for reporting progress and status messages.
/// </summary> /// </summary>
public Action<string, Color?> ProgressCallback { get; set; } public Action<string, Color?> ProgressCallback { get; set; }
public void LogProgress(string message, Color? color = null)
{
ProgressCallback?.Invoke(message, color);
}
public SldWorks SolidWorksApp { get; set; }
public DrawingDoc TemplateDrawing { get; set; }
private string DrawingTemplatePath
{
get
{
return Path.Combine(
Application.StartupPath,
DRAWING_TEMPLATE_FOLDER,
DRAWING_TEMPLATE_FILE);
}
}
public DrawingDoc GetOrCreateTemplateDrawing()
{
if (TemplateDrawing != null)
return TemplateDrawing;
TemplateDrawing = SolidWorksApp.NewDocument(
DrawingTemplatePath,
(int)swDwgPaperSizes_e.swDwgPaperDsize,
1,
1) as DrawingDoc;
return TemplateDrawing;
}
public void CleanupTemplateDrawing()
{
try
{
if (TemplateDrawing == null)
return;
if (SolidWorksApp == null)
{
ProgressCallback?.Invoke("Warning: Cannot cleanup template drawing - SolidWorks app not available", Color.DarkBlue);
TemplateDrawing = null;
return;
}
var model = TemplateDrawing as ModelDoc2;
if (model != null)
{
var title = model.GetTitle();
if (!string.IsNullOrEmpty(title))
{
// Close the document without saving
SolidWorksApp.CloseDoc(title);
ProgressCallback?.Invoke("Closed template drawing", null);
}
}
// Clear the reference regardless of success/failure
TemplateDrawing = null;
}
catch (Exception ex)
{
ProgressCallback?.Invoke($"Failed to close template drawing: {ex.Message}", Color.Red);
// Still clear the reference to prevent further issues
TemplateDrawing = null;
// Don't throw here as this is cleanup code - log the error but continue
}
}
public void CloseDocument(string title)
{
SolidWorksApp?.CloseDoc(title);
}
public ModelDoc2 CreateDocument(string templatePath, int paperSize, double width, double height)
{
return SolidWorksApp?.NewDocument(templatePath, paperSize, width, height) as ModelDoc2;
}
} }
} }

View File

@@ -0,0 +1,22 @@
using System;
namespace ExportDXF.Models
{
public enum LogLevel { Info, Warning, Error }
public enum LogAction { Start, FindBom, CreateFlat, FlipView, SavePdf, UploadPdf, UploadDxf, CreateBomItem }
public sealed class LogEvent
{
public DateTime Time { get; set; } = DateTime.Now;
public LogLevel Level { get; set; }
public string Equipment { get; set; } = "";
public string Drawing { get; set; } = "";
public string Part { get; set; } = "";
public LogAction Action { get; set; }
public string Target { get; set; } = "";
public string Result { get; set; } = "OK";
public int DurationMs { get; set; }
public string Message { get; set; } = "";
}
}

View File

@@ -1,6 +1,8 @@
using ExportDXF.Forms; using ExportDXF.Forms;
using ExportDXF.Services; using ExportDXF.Services;
using System; using System;
using System.Configuration;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace ExportDXF namespace ExportDXF
@@ -19,9 +21,20 @@ namespace ExportDXF
// Simple DI setup - could use a DI container like Microsoft.Extensions.DependencyInjection // Simple DI setup - could use a DI container like Microsoft.Extensions.DependencyInjection
var container = new ServiceContainer(); var container = new ServiceContainer();
var mainForm = container.Resolve<MainForm>(); // Show drawing selection dialog first
var drawingSelectionForm = container.ResolveDrawingSelectionAsync().GetAwaiter().GetResult();
var result = drawingSelectionForm.ShowDialog();
Application.Run(mainForm); if (result == DialogResult.OK && drawingSelectionForm.SelectedDrawingId.HasValue)
{
// User selected a drawing, proceed to main form
var mainForm = container.Resolve<MainForm>(
drawingSelectionForm.SelectedDrawingId.Value,
drawingSelectionForm.SelectedDrawingNumber);
Application.Run(mainForm);
}
// If user cancelled, just exit
} }
} }
@@ -31,7 +44,36 @@ namespace ExportDXF
/// </summary> /// </summary>
public class ServiceContainer public class ServiceContainer
{ {
public MainForm Resolve<T>() where T : MainForm private readonly string _baseUrl;
private readonly CutFabApiClient _apiClient;
public ServiceContainer()
{
_baseUrl = ConfigurationManager.AppSettings["CutFab.ApiBaseUrl"] ?? "http://localhost:7027";
_apiClient = new CutFabApiClient(_baseUrl);
}
public async Task<DrawingSelectionForm> ResolveDrawingSelectionAsync()
{
// Connect to SolidWorks first
var solidWorksService = new SolidWorksService();
try
{
await solidWorksService.ConnectAsync();
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(
$"Warning: Could not connect to SolidWorks: {ex.Message}\n\nYou can still select a drawing, but the active document will not be displayed.",
"SolidWorks Connection Warning",
System.Windows.Forms.MessageBoxButtons.OK,
System.Windows.Forms.MessageBoxIcon.Warning);
}
return new DrawingSelectionForm(_apiClient, solidWorksService);
}
public MainForm Resolve<T>(int selectedDrawingId, string selectedDrawingNumber) where T : MainForm
{ {
// Create the dependency tree // Create the dependency tree
var solidWorksService = new SolidWorksService(); var solidWorksService = new SolidWorksService();
@@ -39,16 +81,15 @@ namespace ExportDXF
var bomExtractor = new BomExtractor(); var bomExtractor = new BomExtractor();
var partExporter = new PartExporter(); var partExporter = new PartExporter();
var drawingExporter = new DrawingExporter(); var drawingExporter = new DrawingExporter();
var bomExcelExporter = new BomExcelExporter();
var exportService = new DxfExportService( var exportService = new DxfExportService(
solidWorksService, solidWorksService,
bomExtractor, bomExtractor,
partExporter, partExporter,
drawingExporter, drawingExporter,
bomExcelExporter); _apiClient);
return new MainForm(solidWorksService, exportService); return new MainForm(solidWorksService, exportService, _apiClient, selectedDrawingId, selectedDrawingNumber);
} }
} }
} }

View File

@@ -1,396 +0,0 @@
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
}
}

View File

@@ -1,64 +0,0 @@
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;
}
}

View File

@@ -0,0 +1,468 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
using static ExportDXF.Services.CutFabApiClient;
namespace ExportDXF.Services
{
public interface ICutFabApiClient
{
string BaseUrl { get; }
Task<int?> ResolveDrawingIdAsync(string drawingNumber);
Task<int?> CreateDrawingAsync(int equipmentId, string drawingNumber);
Task<CutFabApiClient.ApiResponse<int?>> CreateDrawingWithInfoAsync(int equipmentId, string drawingNumber);
Task<bool> UploadDrawingPdfAsync(string drawingNumber, string pdfPath, string uploadedBy = null, string notes = null);
Task<bool> UploadDxfZipAsync(
int drawingId,
string zipPath,
double? thickness = null,
double? kfactor = null,
double? defaultBendRadius = null,
string material = null);
Task<int?> CreateBomItemAsync(object upsertBomItemDto);
Task<bool> AutoLinkTemplatesAsync(int drawingId);
Task<List<ApiEquipment>> GetEquipmentAsync();
Task<List<ApiDrawingSummary>> GetDrawingsForEquipmentAsync(int equipmentId);
Task<List<ApiBomItem>> GetBomItemsForDrawingAsync(int drawingId);
Task<List<ApiCutTemplate>> GetCutTemplatesAsync();
}
public class CutFabApiClient : ICutFabApiClient, IDisposable
{
private readonly HttpClient _http;
private readonly string _baseUrl;
public string BaseUrl => _baseUrl;
public class ApiResponse<T>
{
public bool Success { get; set; }
public int StatusCode { get; set; }
public T Data { get; set; }
public string RawBody { get; set; }
public string Error { get; set; }
}
public CutFabApiClient(string baseUrl)
{
_baseUrl = (baseUrl ?? string.Empty).TrimEnd('/');
if (string.IsNullOrWhiteSpace(_baseUrl))
{
// Default to deployed API port from deployment script
_baseUrl = "http://localhost:7027";
}
_http = new HttpClient
{
Timeout = TimeSpan.FromSeconds(100)
};
}
public async Task<int?> ResolveDrawingIdAsync(string drawingNumber)
{
try
{
var url = $"{_baseUrl}/api/Drawings/resolve?drawingNumber={Uri.EscapeDataString(drawingNumber)}";
var resp = await _http.GetAsync(url).ConfigureAwait(false);
if (!resp.IsSuccessStatusCode) return null;
var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(json);
if (dict != null && dict.ContainsKey("ID"))
{
var idObj = dict["ID"]; // serializer returns int or double depending
if (idObj is int i) return i;
if (idObj is double d) return (int)d;
}
return null;
}
catch
{
return null;
}
}
public async Task<int?> CreateDrawingAsync(int equipmentId, string drawingNumber)
{
var payload = new { DrawingNumber = drawingNumber, Description = (string)null, Qty = 1, EquipmentID = equipmentId };
var json = new JavaScriptSerializer().Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var resp = await _http.PostAsync($"{_baseUrl}/api/Drawings", content).ConfigureAwait(false);
if (!resp.IsSuccessStatusCode) return null;
var body = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(body);
if (dict != null && dict.ContainsKey("ID"))
{
var idObj = dict["ID"]; if (idObj is int i) return i; if (idObj is double d) return (int)d;
}
}
catch { }
return null;
}
public async Task<ApiResponse<int?>> CreateDrawingWithInfoAsync(int equipmentId, string drawingNumber)
{
var result = new ApiResponse<int?> { Success = false, StatusCode = 0, Data = null, RawBody = null, Error = null };
try
{
var payload = new { DrawingNumber = drawingNumber, Description = (string)null, Qty = 1, EquipmentID = equipmentId };
var json = new JavaScriptSerializer().Serialize(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var resp = await _http.PostAsync($"{_baseUrl}/api/Drawings", content).ConfigureAwait(false);
result.StatusCode = (int)resp.StatusCode;
result.Success = resp.IsSuccessStatusCode;
var body = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
result.RawBody = body;
if (!resp.IsSuccessStatusCode)
{
result.Error = "HTTP " + ((int)resp.StatusCode) + " " + resp.ReasonPhrase;
return result;
}
try
{
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(body);
if (dict != null)
{
object v;
if (TryGetCI(dict, new[] { "ID", "id" }, out v))
{
if (v is int i) { result.Data = i; return result; }
if (v is double d) { result.Data = (int)d; return result; }
int parsed; if (int.TryParse(Convert.ToString(v), out parsed)) { result.Data = parsed; return result; }
}
}
}
catch (Exception ex)
{
result.Error = ex.Message;
}
return result;
}
catch (Exception ex)
{
result.Error = ex.Message;
return result;
}
}
public async Task<bool> UploadDrawingPdfAsync(string drawingNumber, string pdfPath, string uploadedBy = null, string notes = null)
{
if (!File.Exists(pdfPath)) return false;
using (var form = new MultipartFormDataContent())
{
form.Add(new StringContent(drawingNumber ?? string.Empty), "drawingNumber");
if (!string.IsNullOrWhiteSpace(uploadedBy)) form.Add(new StringContent(uploadedBy), "uploadedBy");
if (!string.IsNullOrWhiteSpace(notes)) form.Add(new StringContent(notes), "notes");
var fileContent = new StreamContent(File.OpenRead(pdfPath));
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/pdf");
var fileName = Path.GetFileName(pdfPath);
form.Add(fileContent, "file", fileName);
var resp = await _http.PostAsync($"{_baseUrl}/api/DrawingRevisions/upload", form).ConfigureAwait(false);
return resp.IsSuccessStatusCode;
}
}
public async Task<bool> UploadDxfZipAsync(int drawingId, string zipPath, double? thickness = null, double? kfactor = null, double? defaultBendRadius = null, string material = null)
{
if (!File.Exists(zipPath)) return false;
using (var form = new MultipartFormDataContent())
{
var fileContent = new StreamContent(File.OpenRead(zipPath));
fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("application/zip");
var fileName = Path.GetFileName(zipPath);
form.Add(fileContent, "file", fileName);
// Add thickness and kfactor if provided
if (thickness.HasValue)
form.Add(new StringContent(thickness.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)), "thickness");
if (kfactor.HasValue)
form.Add(new StringContent(kfactor.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)), "kfactor");
// Add default bend radius and material if provided
if (defaultBendRadius.HasValue)
form.Add(new StringContent(defaultBendRadius.Value.ToString(System.Globalization.CultureInfo.InvariantCulture)), "defaultBendRadius");
if (!string.IsNullOrWhiteSpace(material))
form.Add(new StringContent(material), "material");
var resp = await _http.PostAsync($"{_baseUrl}/api/Drawings/{drawingId}/upload-dxf-templates", form).ConfigureAwait(false);
return resp.IsSuccessStatusCode;
}
}
public async Task<int?> CreateBomItemAsync(object upsertBomItemDto)
{
var json = new JavaScriptSerializer().Serialize(upsertBomItemDto);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var resp = await _http.PostAsync($"{_baseUrl}/api/BomItems", content).ConfigureAwait(false);
if (!resp.IsSuccessStatusCode) return null;
var body = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
try
{
var dict = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(body);
if (dict != null)
{
object v;
if (TryGetCI(dict, new[] { "ID", "id" }, out v))
{
if (v is int i) return i;
if (v is double d) return (int)d;
int parsed; if (int.TryParse(Convert.ToString(v), out parsed)) return parsed;
}
}
}
catch { }
// Successful HTTP with empty/minimal body: treat as success
return 0;
}
public async Task<bool> AutoLinkTemplatesAsync(int drawingId)
{
var url = $"{_baseUrl}/api/Drawings/{drawingId}/auto-link-templates";
var resp = await _http.PostAsync(url, new ByteArrayContent(new byte[0])).ConfigureAwait(false);
return resp.IsSuccessStatusCode;
}
public async Task<List<ApiBomItem>> GetBomItemsForDrawingAsync(int drawingId)
{
var url = $"{_baseUrl}/api/BomItems/drawing/{drawingId}";
var resp = await _http.GetAsync(url).ConfigureAwait(false);
if (!resp.IsSuccessStatusCode) return new List<ApiBomItem>();
var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
var serializer = new JavaScriptSerializer();
var raw = serializer.DeserializeObject(json);
var result = new List<ApiBomItem>();
if (raw is System.Collections.IEnumerable enumerable && !(raw is string))
{
foreach (var item in enumerable)
{
var dict = item as Dictionary<string, object>;
if (dict == null) continue;
var bomItem = new ApiBomItem();
object v;
if (TryGetCI(dict, new[] { "ID", "id" }, out v)) bomItem.ID = ToInt(v);
if (TryGetCI(dict, new[] { "ItemNo", "itemNo" }, out v)) bomItem.ItemNo = v?.ToString();
if (TryGetCI(dict, new[] { "PartNo", "partNo" }, out v)) bomItem.PartNo = v?.ToString();
if (TryGetCI(dict, new[] { "SortOrder", "sortOrder" }, out v)) bomItem.SortOrder = ToInt(v);
if (TryGetCI(dict, new[] { "Qty", "qty" }, out v)) bomItem.Qty = v != null ? (int?)ToInt(v) : null;
if (TryGetCI(dict, new[] { "TotalQty", "totalQty" }, out v)) bomItem.TotalQty = v != null ? (int?)ToInt(v) : null;
if (TryGetCI(dict, new[] { "Description", "description" }, out v)) bomItem.Description = v?.ToString();
if (TryGetCI(dict, new[] { "PartName", "partName" }, out v)) bomItem.PartName = v?.ToString();
if (TryGetCI(dict, new[] { "ConfigurationName", "configurationName" }, out v)) bomItem.ConfigurationName = v?.ToString();
if (TryGetCI(dict, new[] { "Material", "material" }, out v)) bomItem.Material = v?.ToString();
if (TryGetCI(dict, new[] { "DrawingID", "drawingID", "drawingId" }, out v)) bomItem.DrawingID = ToInt(v);
if (TryGetCI(dict, new[] { "CutTemplateID", "cutTemplateID", "cutTemplateId" }, out v)) bomItem.CutTemplateID = v != null ? (int?)ToInt(v) : null;
// Try to get CutTemplate info if available
if (TryGetCI(dict, new[] { "CutTemplate", "cutTemplate" }, out v))
{
var templateDict = v as Dictionary<string, object>;
if (templateDict != null)
{
object tv;
if (TryGetCI(templateDict, new[] { "Name", "name" }, out tv)) bomItem.CutTemplateName = tv?.ToString();
if (TryGetCI(templateDict, new[] { "Thickness", "thickness" }, out tv)) bomItem.Thickness = ToDouble(tv);
if (TryGetCI(templateDict, new[] { "KFactor", "kFactor", "kfactor" }, out tv)) bomItem.KFactor = ToDouble(tv);
if (TryGetCI(templateDict, new[] { "DefaultBendRadius", "defaultBendRadius" }, out tv)) bomItem.DefaultBendRadius = ToDouble(tv);
}
}
result.Add(bomItem);
}
}
return result;
}
public async Task<List<ApiCutTemplate>> GetCutTemplatesAsync()
{
var url = $"{_baseUrl}/api/CutTemplates";
var resp = await _http.GetAsync(url).ConfigureAwait(false);
if (!resp.IsSuccessStatusCode) return new List<ApiCutTemplate>();
var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
var serializer = new JavaScriptSerializer();
var raw = serializer.DeserializeObject(json);
var result = new List<ApiCutTemplate>();
if (raw is System.Collections.IEnumerable enumerable && !(raw is string))
{
foreach (var item in enumerable)
{
var dict = item as Dictionary<string, object>;
if (dict == null) continue;
var template = new ApiCutTemplate();
object v;
if (TryGetCI(dict, new[] { "ID", "id" }, out v)) template.ID = ToInt(v);
if (TryGetCI(dict, new[] { "Name", "name" }, out v)) template.Name = v?.ToString();
if (TryGetCI(dict, new[] { "FilePath", "filePath" }, out v)) template.FilePath = v?.ToString();
if (TryGetCI(dict, new[] { "Description", "description" }, out v)) template.Description = v?.ToString();
if (TryGetCI(dict, new[] { "Material", "material" }, out v)) template.Material = v?.ToString();
if (TryGetCI(dict, new[] { "Thickness", "thickness" }, out v)) template.Thickness = ToDouble(v);
if (TryGetCI(dict, new[] { "KFactor", "kFactor", "kfactor" }, out v)) template.KFactor = ToDouble(v);
if (TryGetCI(dict, new[] { "DefaultBendRadius", "defaultBendRadius" }, out v)) template.DefaultBendRadius = ToDouble(v);
if (TryGetCI(dict, new[] { "Version", "version" }, out v)) template.Version = ToInt(v);
result.Add(template);
}
}
return result;
}
public void Dispose()
{
_http?.Dispose();
}
// Lightweight DTOs for UI binding
public class ApiEquipment
{
public int ID { get; set; }
public string EquipmentNumber { get; set; }
public string Description { get; set; }
public override string ToString() => EquipmentNumber ?? base.ToString();
}
public class ApiDrawingSummary
{
public int ID { get; set; }
public string DrawingNumber { get; set; }
public string Description { get; set; }
public override string ToString() => DrawingNumber ?? base.ToString();
}
public class ApiBomItem
{
public int ID { get; set; }
public string ItemNo { get; set; }
public string PartNo { get; set; }
public int SortOrder { get; set; }
public int? Qty { get; set; }
public int? TotalQty { get; set; }
public string Description { get; set; }
public string PartName { get; set; }
public string ConfigurationName { get; set; }
public string Material { get; set; }
public int DrawingID { get; set; }
public int? CutTemplateID { get; set; }
public string CutTemplateName { get; set; }
public double? Thickness { get; set; }
public double? KFactor { get; set; }
public double? DefaultBendRadius { get; set; }
}
public class ApiCutTemplate
{
public int ID { get; set; }
public string Name { get; set; }
public string FilePath { get; set; }
public string Description { get; set; }
public string Material { get; set; }
public double? Thickness { get; set; }
public double? KFactor { get; set; }
public double? DefaultBendRadius { get; set; }
public int Version { get; set; }
}
public async Task<List<ApiEquipment>> GetEquipmentAsync()
{
var url = $"{_baseUrl}/api/Equipment";
var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
var resp = await _http.SendAsync(req).ConfigureAwait(false);
resp.EnsureSuccessStatusCode();
var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
var serializer = new JavaScriptSerializer();
var raw = serializer.DeserializeObject(json);
var result = new List<ApiEquipment>();
if (raw is System.Collections.IEnumerable enumerable && !(raw is string))
{
foreach (var item in enumerable)
{
var dict = item as Dictionary<string, object>;
if (dict == null) continue;
var eq = new ApiEquipment();
object v;
if (TryGetCI(dict, new[] { "ID", "id" }, out v)) eq.ID = ToInt(v);
if (TryGetCI(dict, new[] { "EquipmentNumber", "equipmentNumber", "equipmentNo" }, out v)) eq.EquipmentNumber = v?.ToString();
if (TryGetCI(dict, new[] { "Description", "description" }, out v)) eq.Description = v?.ToString();
result.Add(eq);
}
}
return result;
}
public async Task<List<ApiDrawingSummary>> GetDrawingsForEquipmentAsync(int equipmentId)
{
var url = $"{_baseUrl}/api/Equipment/{equipmentId}";
var resp = await _http.GetAsync(url).ConfigureAwait(false);
if (!resp.IsSuccessStatusCode) return new List<ApiDrawingSummary>();
var json = await resp.Content.ReadAsStringAsync().ConfigureAwait(false);
var serializer = new JavaScriptSerializer();
var root = serializer.DeserializeObject(json) as Dictionary<string, object>;
var results = new List<ApiDrawingSummary>();
if (root != null)
{
object dval;
if (!TryGetCI(root, new[] { "Drawings", "drawings" }, out dval)) return results;
if (dval is System.Collections.IEnumerable arr && !(dval is string))
{
foreach (var item in arr)
{
var d = item as Dictionary<string, object>;
if (d == null) continue;
var summary = new ApiDrawingSummary();
object v;
if (TryGetCI(d, new[] { "ID", "id" }, out v)) summary.ID = ToInt(v);
if (TryGetCI(d, new[] { "DrawingNumber", "drawingNumber" }, out v)) summary.DrawingNumber = v?.ToString();
if (TryGetCI(d, new[] { "Description", "description" }, out v)) summary.Description = v?.ToString();
results.Add(summary);
}
}
}
return results;
}
private static bool TryGetCI(Dictionary<string, object> dict, IEnumerable<string> keys, out object value)
{
foreach (var k in keys)
{
if (dict.ContainsKey(k)) { value = dict[k]; return true; }
foreach (var dk in dict.Keys)
{
if (string.Equals(dk, k, StringComparison.OrdinalIgnoreCase)) { value = dict[dk]; return true; }
}
}
value = null; return false;
}
private static int ToInt(object v)
{
if (v == null) return 0;
if (v is int i) return i;
if (v is long l) return (int)l;
if (v is double d) return (int)d;
int parsed; if (int.TryParse(v.ToString(), out parsed)) return parsed; return 0;
}
private static double? ToDouble(object v)
{
if (v == null) return null;
if (v is double d) return d;
if (v is int i) return (double)i;
if (v is long l) return (double)l;
if (v is float f) return (double)f;
double parsed; if (double.TryParse(v.ToString(), out parsed)) return parsed;
return null;
}
}
}

View File

@@ -16,14 +16,13 @@ namespace ExportDXF.Services
/// </summary> /// </summary>
/// <param name="drawing">The drawing document to export.</param> /// <param name="drawing">The drawing document to export.</param>
/// <param name="saveDirectory">The directory where the PDF file will be saved.</param> /// <param name="saveDirectory">The directory where the PDF file will be saved.</param>
/// <param name="progressCallback">Optional callback for progress updates.</param> /// <param name="context">The export context containing SolidWorks app and callbacks.</param>
void ExportToPdf(DrawingDoc drawing, string saveDirectory, Action<string, Color?> progressCallback); void ExportToPdf(DrawingDoc drawing, string saveDirectory, ExportContext context);
} }
public class DrawingExporter : IDrawingExporter public class DrawingExporter : IDrawingExporter
{ {
public void ExportToPdf(DrawingDoc drawing, string saveDirectory, Action<string, Color?> progressCallback) public void ExportToPdf(DrawingDoc drawing, string saveDirectory, ExportContext context)
{ {
if (drawing == null) if (drawing == null)
throw new ArgumentNullException(nameof(drawing)); throw new ArgumentNullException(nameof(drawing));
@@ -31,27 +30,40 @@ namespace ExportDXF.Services
if (string.IsNullOrWhiteSpace(saveDirectory)) if (string.IsNullOrWhiteSpace(saveDirectory))
throw new ArgumentException("Save directory cannot be null or empty.", nameof(saveDirectory)); throw new ArgumentException("Save directory cannot be null or empty.", nameof(saveDirectory));
if (context == null)
throw new ArgumentNullException(nameof(context));
if (context.SolidWorksApp == null)
throw new ArgumentException("SolidWorksApp cannot be null in context.", nameof(context));
try try
{ {
var pdfFileName = GetPdfFileName(drawing); var pdfFileName = GetPdfFileName(drawing);
var pdfPath = Path.Combine(saveDirectory, pdfFileName); var pdfPath = Path.Combine(saveDirectory, pdfFileName);
var model = drawing as ModelDoc2; var model = drawing as ModelDoc2;
var sldWorks = (SldWorks)Activator.CreateInstance(Type.GetTypeFromProgID("SldWorks.Application")); var sldWorks = context.SolidWorksApp;
var exportData = sldWorks.GetExportFileData( var exportData = sldWorks.GetExportFileData(
(int)swExportDataFileType_e.swExportPdfData) as ExportPdfData; (int)swExportDataFileType_e.swExportPdfData) as ExportPdfData;
if (exportData == null)
{
throw new InvalidOperationException("Failed to get PDF export data from SolidWorks.");
}
exportData.ViewPdfAfterSaving = false; exportData.ViewPdfAfterSaving = false;
exportData.SetSheets( exportData.SetSheets(
(int)swExportDataSheetsToExport_e.swExportData_ExportAllSheets, (int)swExportDataSheetsToExport_e.swExportData_ExportAllSheets,
drawing); drawing);
context.ProgressCallback?.Invoke($"Exporting drawing to PDF: \"{pdfFileName}\"", null);
int errors = 0; int errors = 0;
int warnings = 0; int warnings = 0;
var modelExtension = model.Extension; var modelExtension = model.Extension;
modelExtension.SaveAs( var success = modelExtension.SaveAs(
pdfPath, pdfPath,
(int)swSaveAsVersion_e.swSaveAsCurrentVersion, (int)swSaveAsVersion_e.swSaveAsCurrentVersion,
(int)swSaveAsOptions_e.swSaveAsOptions_Silent, (int)swSaveAsOptions_e.swSaveAsOptions_Silent,
@@ -59,19 +71,29 @@ namespace ExportDXF.Services
ref errors, ref errors,
ref warnings); ref warnings);
if (errors == 0) if (success && errors == 0)
{ {
progressCallback?.Invoke($"Saved drawing to PDF: \"{pdfFileName}\"", Color.Green); context.ProgressCallback?.Invoke($"Saved drawing to PDF: \"{pdfFileName}\"", Color.Green);
}
else if (success && warnings > 0)
{
context.ProgressCallback?.Invoke(
$"PDF export completed with warnings: {warnings}",
Color.DarkBlue);
} }
else else
{ {
progressCallback?.Invoke($"PDF export completed with errors: {errors}", Color.Yellow); context.ProgressCallback?.Invoke(
$"PDF export failed. Errors: {errors}, Warnings: {warnings}",
Color.Red);
throw new InvalidOperationException($"PDF export failed with {errors} errors and {warnings} warnings.");
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
progressCallback?.Invoke($"Failed to export PDF: {ex.Message}", Color.Red); var errorMessage = $"Failed to export PDF: {ex.Message}";
throw; context.ProgressCallback?.Invoke(errorMessage, Color.Red);
throw new InvalidOperationException(errorMessage, ex);
} }
} }
@@ -79,6 +101,14 @@ namespace ExportDXF.Services
{ {
var model = drawing as ModelDoc2; var model = drawing as ModelDoc2;
var modelFilePath = model.GetPathName(); var modelFilePath = model.GetPathName();
if (string.IsNullOrEmpty(modelFilePath))
{
// Handle unsaved documents
var title = model.GetTitle();
return string.IsNullOrEmpty(title) ? "Untitled.pdf" : Path.GetFileNameWithoutExtension(title) + ".pdf";
}
return Path.GetFileNameWithoutExtension(modelFilePath) + ".pdf"; return Path.GetFileNameWithoutExtension(modelFilePath) + ".pdf";
} }
} }

View File

@@ -1,13 +1,17 @@
using ExportDXF.Extensions; using ExportDXF.Extensions;
using ExportDXF.ItemExtractors; using ExportDXF.ItemExtractors;
using ExportDXF.Models; using ExportDXF.Models;
using ExportDXF;
using SolidWorks.Interop.sldworks; using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst; using SolidWorks.Interop.swconst;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
using Environment = System.Environment;
using System.IO.Compression;
namespace ExportDXF.Services namespace ExportDXF.Services
{ {
@@ -25,27 +29,24 @@ namespace ExportDXF.Services
/// </summary> /// </summary>
public class DxfExportService : IDxfExportService 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 ISolidWorksService _solidWorksService;
private readonly IBomExtractor _bomExtractor; private readonly IBomExtractor _bomExtractor;
private readonly IPartExporter _partExporter; private readonly IPartExporter _partExporter;
private readonly IDrawingExporter _drawingExporter; private readonly IDrawingExporter _drawingExporter;
private readonly IBomExcelExporter _bomExcelExporter; private readonly ICutFabApiClient _apiClient;
public DxfExportService( public DxfExportService(
ISolidWorksService solidWorksService, ISolidWorksService solidWorksService,
IBomExtractor bomExtractor, IBomExtractor bomExtractor,
IPartExporter partExporter, IPartExporter partExporter,
IDrawingExporter drawingExporter, IDrawingExporter drawingExporter,
IBomExcelExporter bomExcelExporter) ICutFabApiClient apiClient)
{ {
_solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService)); _solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService));
_bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor)); _bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor));
_partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter)); _partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter));
_drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter)); _drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter));
_bomExcelExporter = bomExcelExporter ?? throw new ArgumentNullException(nameof(bomExcelExporter)); _apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
} }
/// <summary> /// <summary>
@@ -57,6 +58,7 @@ namespace ExportDXF.Services
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
ValidateContext(context); ValidateContext(context);
SetupExportContext(context);
var startTime = DateTime.Now; var startTime = DateTime.Now;
@@ -85,6 +87,7 @@ namespace ExportDXF.Services
} }
finally finally
{ {
CleanupExportContext(context);
_solidWorksService.EnableUserControl(true); _solidWorksService.EnableUserControl(true);
var duration = DateTime.Now - startTime; var duration = DateTime.Now - startTime;
@@ -105,14 +108,8 @@ namespace ExportDXF.Services
return; return;
} }
var saveDirectory = PromptUserForDirectory(context); var tempDir = CreateTempWorkDir();
if (saveDirectory == null) _partExporter.ExportSinglePart(part, tempDir, context);
{
LogProgress(context, "Canceled", Color.Red);
return;
}
_partExporter.ExportSinglePart(part, saveDirectory, context);
} }
private void ExportAssembly(ExportContext context) private void ExportAssembly(ExportContext context)
@@ -131,20 +128,14 @@ namespace ExportDXF.Services
if (items == null || items.Count == 0) if (items == null || items.Count == 0)
{ {
LogProgress(context, "No items found in assembly.", Color.Yellow); LogProgress(context, "No items found in assembly.", Color.DarkBlue);
return; return;
} }
LogProgress(context, $"Found {items.Count} item(s).", null); LogProgress(context, $"Found {items.Count} item(s).", null);
var saveDirectory = PromptUserForDirectory(context); var tempDir = CreateTempWorkDir();
if (saveDirectory == null) ExportItems(items, tempDir, context, drawingId: null);
{
LogProgress(context, "Canceled", Color.Red);
return;
}
ExportItems(items, saveDirectory, context);
} }
private void ExportDrawing(ExportContext context) private void ExportDrawing(ExportContext context)
@@ -169,18 +160,154 @@ namespace ExportDXF.Services
LogProgress(context, $"Found {items.Count} component(s)", null); LogProgress(context, $"Found {items.Count} component(s)", null);
var saveDirectory = PromptUserForDirectory(context); var tempDir = CreateTempWorkDir();
if (saveDirectory == null)
// Determine drawing number
var drawingNumber = ParseDrawingNumber(context);
if (string.IsNullOrWhiteSpace(drawingNumber))
{ {
LogProgress(context, "Canceled", Color.Red); LogProgress(context, "Warning: Could not determine drawing number for API upload.", Color.DarkBlue);
return;
} }
// Export drawing to PDF first // Resolve drawing ID if possible
_drawingExporter.ExportToPdf(drawing, saveDirectory, context.ProgressCallback); int? drawingId = null;
if (!string.IsNullOrWhiteSpace(drawingNumber))
{
drawingId = _apiClient.ResolveDrawingIdAsync(drawingNumber).GetAwaiter().GetResult();
// Fallback: if resolve endpoint not available or failed, search equipment details
if (drawingId == null && context.EquipmentId.HasValue)
{
try
{
var drawings = _apiClient.GetDrawingsForEquipmentAsync(context.EquipmentId.Value).GetAwaiter().GetResult();
if (drawings != null)
{
// Match by exact DrawingNumber (case-insensitive, trimmed)
var match = drawings.FirstOrDefault(d => string.Equals(d.DrawingNumber?.Trim(), drawingNumber.Trim(), StringComparison.OrdinalIgnoreCase));
if (match != null) drawingId = match.ID;
}
}
catch { }
}
if (drawingId == null)
{
// If equipment is provided, create the drawing on the API
if (context.EquipmentId.HasValue)
{
var create = _apiClient.CreateDrawingWithInfoAsync(context.EquipmentId.Value, drawingNumber).GetAwaiter().GetResult();
if (create != null && create.Success && create.Data.HasValue)
{
drawingId = create.Data;
LogProgress(context, "Created drawing '" + drawingNumber + "' (ID " + drawingId + ") for equipment " + context.EquipmentId, Color.Green);
}
else
{
var code = create != null ? create.StatusCode.ToString() : "?";
var err = create != null ? (create.Error ?? create.RawBody) : null;
if (!string.IsNullOrWhiteSpace(err) && err.Length > 180) err = err.Substring(0, 180) + "...";
LogProgress(context, "Warning: Could not create drawing '" + drawingNumber + "' on API (status " + code + "). " + (err ?? string.Empty), Color.DarkBlue);
}
}
else
{
LogProgress(context, $"Warning: Drawing '{drawingNumber}' not found in API; uploads will be skipped.", Color.DarkBlue);
}
}
// Then export parts to DXF // Export drawing to PDF first
ExportItems(items, saveDirectory, context); _drawingExporter.ExportToPdf(drawing, tempDir, context);
// Upload PDF if we have a drawing number
try
{
if (!string.IsNullOrWhiteSpace(drawingNumber))
{
var pdfs = Directory.GetFiles(tempDir, "*.pdf");
var pdfName = pdfs.Length > 0 ? pdfs[0] : null;
if (pdfName != null)
{
var uploadedBy = Environment.UserName;
var ok = _apiClient.UploadDrawingPdfAsync(drawingNumber, pdfName, uploadedBy, null).GetAwaiter().GetResult();
LogProgress(context, ok ? $"Uploaded PDF for '{drawingNumber}'" : $"Failed to upload PDF for '{drawingNumber}'", ok ? Color.Green : Color.Red);
}
}
}
catch (Exception ex)
{
LogProgress(context, $"PDF upload error: {ex.Message}", Color.Red);
}
// If we still don't have an ID, resolve again after PDF upload (server may create on upload)
if (!drawingId.HasValue && !string.IsNullOrWhiteSpace(drawingNumber))
{
var resolved = _apiClient.ResolveDrawingIdAsync(drawingNumber).GetAwaiter().GetResult();
if (!resolved.HasValue && context.EquipmentId.HasValue)
{
try
{
var drawings = _apiClient.GetDrawingsForEquipmentAsync(context.EquipmentId.Value).GetAwaiter().GetResult();
if (drawings != null)
{
var match = drawings.FirstOrDefault(d => string.Equals(d.DrawingNumber?.Trim(), drawingNumber.Trim(), StringComparison.OrdinalIgnoreCase));
if (match != null) resolved = match.ID;
}
}
catch { }
}
if (resolved.HasValue)
{
drawingId = resolved;
LogProgress(context, $"Resolved drawing ID after PDF upload: {drawingId}", Color.Green);
}
}
// Then export parts to DXF and upload per-file
ExportItems(items, tempDir, context, drawingId);
// Attempt to auto-link templates at the end
try
{
if (drawingId.HasValue)
{
_apiClient.AutoLinkTemplatesAsync(drawingId.Value).GetAwaiter().GetResult();
}
}
catch { }
}
}
#endregion
#region Context Management
private void SetupExportContext(ExportContext context)
{
// Set up SolidWorks application reference
context.SolidWorksApp = _solidWorksService.GetNativeSldWorks();
if (context.SolidWorksApp == null)
{
throw new InvalidOperationException("SolidWorks service is not connected.");
}
// Set up drawing template path
context.TemplateDrawing = null;
LogProgress(context, "Export context initialized", null);
}
private void CleanupExportContext(ExportContext context)
{
try
{
// Clean up template drawing if it was created
context.CleanupTemplateDrawing();
}
catch (Exception ex)
{
LogProgress(context, $"Warning: Failed to cleanup template drawing: {ex.Message}", Color.DarkBlue);
// Don't throw - this is cleanup code
}
} }
#endregion #endregion
@@ -205,189 +332,127 @@ namespace ExportDXF.Services
} }
} }
private void ExportItems(List<Item> items, string saveDirectory, ExportContext context) private void ExportItems(List<Item> items, string saveDirectory, ExportContext context, int? drawingId)
{ {
DrawingDoc templateDrawing = null; LogProgress(context, "", null);
try int successCount = 0;
int failureCount = 0;
foreach (var item in items)
{ {
templateDrawing = CreateTemplateDrawing(); if (context.CancellationToken.IsCancellationRequested)
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.DarkBlue);
return;
}
try
{
// PartExporter will handle template drawing creation through context
_partExporter.ExportItem(item, saveDirectory, context);
// Always create BOM item first if we have a drawing
if (drawingId.HasValue)
{ {
LogProgress(context, "Export canceled by user.", Color.Yellow); var dto = new
return; {
DrawingID = drawingId.Value,
ItemNo = item.ItemNo,
PartNo = !string.IsNullOrEmpty(item.FileName) ? item.FileName : item.PartName,
Qty = (int?)item.Quantity,
Description = string.IsNullOrWhiteSpace(item.Description) ? null : item.Description,
PartName = string.IsNullOrWhiteSpace(item.PartName) ? null : item.PartName,
ConfigurationName = string.IsNullOrWhiteSpace(item.Configuration) ? null : item.Configuration,
Material = string.IsNullOrWhiteSpace(item.Material) ? null : item.Material,
SortOrder = 0,
CutTemplateID = (int?)null,
FormProgramID = (int?)null
};
var bomId = _apiClient.CreateBomItemAsync(dto).GetAwaiter().GetResult();
LogProgress(context, bomId.HasValue ? $"Created BOM item for {item.ItemNo ?? item.PartName}" : $"Failed to create BOM item for {item.ItemNo ?? item.PartName}", bomId.HasValue ? Color.Green : Color.Red);
} }
try if (!string.IsNullOrEmpty(item.FileName))
{ {
_partExporter.ExportItem(item, templateDrawing, saveDirectory, context); successCount++;
if (!string.IsNullOrEmpty(item.FileName)) // If we know the drawing, upload DXF
successCount++; if (drawingId.HasValue)
else {
failureCount++; var dxfPath = Path.Combine(saveDirectory, item.FileName + ".dxf");
if (File.Exists(dxfPath))
{
// Zip just this file
string zipPath = CreateZipWithSingleFile(dxfPath);
try
{
// Pass thickness, kfactor, default bend radius and material from the item
double? thickness = item.Thickness > 0 ? item.Thickness : (double?)null;
double? kfactor = item.KFactor > 0 ? item.KFactor : (double?)null;
double? defaultBendRadius = item.BendRadius > 0 ? item.BendRadius : (double?)null;
string material = string.IsNullOrWhiteSpace(item.Material) ? null : item.Material;
var okZip = _apiClient.UploadDxfZipAsync(drawingId.Value, zipPath, thickness, kfactor, defaultBendRadius, material).GetAwaiter().GetResult();
LogProgress(context, okZip ? $"Uploaded DXF: {Path.GetFileName(dxfPath)}" : $"Failed to upload DXF: {Path.GetFileName(dxfPath)}", okZip ? Color.Green : Color.Red);
}
finally
{
try { if (File.Exists(zipPath)) File.Delete(zipPath); } catch { }
}
}
}
} }
catch (Exception ex) else
{ {
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", Color.Red);
failureCount++; failureCount++;
} }
LogProgress(context, "", null);
} }
catch (Exception ex)
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); LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", Color.Red);
failureCount++;
} }
}
catch
{
// Ignore errors during cleanup
}
}
private string GetDrawingTemplatePath() LogProgress(context, "", null);
{ }
return Path.Combine(
Application.StartupPath, LogProgress(context, $"Export complete: {successCount} succeeded, {failureCount} failed",
DRAWING_TEMPLATE_FOLDER, failureCount > 0 ? Color.DarkBlue : Color.Green);
DRAWING_TEMPLATE_FILE);
} }
#endregion #endregion
#region BOM Excel Creation #region Temp + Upload Helpers
private void CreateBomExcelFile(List<Item> items, string saveDirectory, ExportContext context) private string CreateTempWorkDir()
{ {
try var path = Path.Combine(Path.GetTempPath(), "ExportDXF-" + Guid.NewGuid().ToString("N"));
{ Directory.CreateDirectory(path);
var bomFileName = GetBomFileName(context.FilePrefix); return path;
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) private string ParseDrawingNumber(ExportContext context)
{ {
if (string.IsNullOrWhiteSpace(prefix)) // Prefer prefix (e.g., "5007 A02 PT"), fallback to active document title
return "BOM"; var candidate = context?.FilePrefix;
var info = string.IsNullOrWhiteSpace(candidate) ? null : DrawingInfo.Parse(candidate);
var drawingInfo = DrawingInfo.Parse(prefix); if (info == null)
if (drawingInfo != null)
{ {
return $"{drawingInfo.JobNo} {drawingInfo.DrawingNo} BOM"; var title = context?.ActiveDocument?.Title;
info = string.IsNullOrWhiteSpace(title) ? null : DrawingInfo.Parse(title);
} }
return info != null ? ($"{info.EquipmentNo} {info.DrawingNo}") : null;
return prefix.Trim() + " BOM";
} }
#endregion private string CreateZipWithSingleFile(string filePath)
#region User Interaction
private string PromptUserForDirectory(ExportContext context)
{ {
// Check if already canceled var zipPath = Path.Combine(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath) + ".zip");
if (context.CancellationToken.IsCancellationRequested) if (File.Exists(zipPath)) File.Delete(zipPath);
return null; using (var zip = System.IO.Compression.ZipFile.Open(zipPath, System.IO.Compression.ZipArchiveMode.Create))
string selectedPath = null;
// Must run on STA thread for FolderBrowserDialog
var thread = new System.Threading.Thread(() =>
{ {
using (var dialog = new FolderBrowserDialog()) zip.CreateEntryFromFile(filePath, Path.GetFileName(filePath));
{ }
dialog.Description = "Where do you want to save the DXF files?"; return zipPath;
dialog.ShowNewFolderButton = true;
if (dialog.ShowDialog() == DialogResult.OK)
{
selectedPath = dialog.SelectedPath;
}
}
});
thread.SetApartmentState(System.Threading.ApartmentState.STA);
thread.Start();
thread.Join();
return selectedPath;
} }
#endregion #endregion
@@ -408,6 +473,10 @@ namespace ExportDXF.Services
context.ProgressCallback?.Invoke(message, color); context.ProgressCallback?.Invoke(message, color);
} }
#endregion #endregion
} }
} }

View File

@@ -25,10 +25,9 @@ namespace ExportDXF.Services
/// Exports an item (component from BOM or assembly) to DXF. /// Exports an item (component from BOM or assembly) to DXF.
/// </summary> /// </summary>
/// <param name="item">The item to export.</param> /// <param name="item">The item to export.</param>
/// <param name="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="saveDirectory">The directory where the DXF file will be saved.</param>
/// <param name="context">The export context.</param> /// <param name="context">The export context.</param>
void ExportItem(Item item, DrawingDoc templateDrawing, string saveDirectory, ExportContext context); void ExportItem(Item item, string saveDirectory, ExportContext context);
} }
public class PartExporter : IPartExporter public class PartExporter : IPartExporter
@@ -38,6 +37,12 @@ namespace ExportDXF.Services
if (part == null) if (part == null)
throw new ArgumentNullException(nameof(part)); throw new ArgumentNullException(nameof(part));
if (string.IsNullOrWhiteSpace(saveDirectory))
throw new ArgumentException("Save directory cannot be null or empty.", nameof(saveDirectory));
if (context == null)
throw new ArgumentNullException(nameof(context));
var model = part as ModelDoc2; var model = part as ModelDoc2;
var activeConfig = model.GetActiveConfiguration() as SolidWorks.Interop.sldworks.Configuration; var activeConfig = model.GetActiveConfiguration() as SolidWorks.Interop.sldworks.Configuration;
var originalConfigName = activeConfig?.Name; var originalConfigName = activeConfig?.Name;
@@ -47,16 +52,9 @@ namespace ExportDXF.Services
var fileName = GetSinglePartFileName(model, context.FilePrefix); var fileName = GetSinglePartFileName(model, context.FilePrefix);
var savePath = Path.Combine(saveDirectory, fileName + ".dxf"); var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
var templateDrawing = CreateTemplateDrawing(context); context.GetOrCreateTemplateDrawing();
try ExportPartToDxf(part, originalConfigName, savePath, context);
{
ExportPartToDxf(part, originalConfigName, templateDrawing, savePath, context);
}
finally
{
CloseTemplateDrawing(templateDrawing, context);
}
} }
finally finally
{ {
@@ -67,8 +65,14 @@ namespace ExportDXF.Services
} }
} }
public void ExportItem(Item item, DrawingDoc templateDrawing, string saveDirectory, ExportContext context) public void ExportItem(Item item, string saveDirectory, ExportContext context)
{ {
if (string.IsNullOrWhiteSpace(saveDirectory))
throw new ArgumentException("Save directory cannot be null or empty.", nameof(saveDirectory));
if (context == null)
throw new ArgumentNullException(nameof(context));
if (item?.Component == null) if (item?.Component == null)
{ {
context.ProgressCallback?.Invoke($"Item {item?.ItemNo} - skipped, no component", Color.Yellow); context.ProgressCallback?.Invoke($"Item {item?.ItemNo} - skipped, no component", Color.Yellow);
@@ -93,7 +97,9 @@ namespace ExportDXF.Services
var fileName = GetItemFileName(item, context.FilePrefix); var fileName = GetItemFileName(item, context.FilePrefix);
var savePath = Path.Combine(saveDirectory, fileName + ".dxf"); var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, templateDrawing, savePath, context)) var templateDrawing = context.GetOrCreateTemplateDrawing();
if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, savePath, context))
{ {
item.FileName = Path.GetFileNameWithoutExtension(savePath); item.FileName = Path.GetFileNameWithoutExtension(savePath);
} }
@@ -116,8 +122,18 @@ namespace ExportDXF.Services
// Get description from custom properties // Get description from custom properties
var config = item.Component.ReferencedConfiguration; var config = item.Component.ReferencedConfiguration;
item.Description = model.Extension.CustomPropertyManager[config].Get("Description");
item.Description = model.Extension.CustomPropertyManager[""].Get("Description"); // Try configuration-specific properties first
var configPropertyManager = model.Extension.CustomPropertyManager[config];
item.Description = configPropertyManager?.Get("Description");
// Fall back to document-level properties if no config-specific description
if (string.IsNullOrEmpty(item.Description))
{
var docPropertyManager = model.Extension.CustomPropertyManager[""];
item.Description = docPropertyManager?.Get("Description");
}
item.Description = TextHelper.RemoveXmlTags(item.Description); item.Description = TextHelper.RemoveXmlTags(item.Description);
// Get material // Get material
@@ -127,7 +143,6 @@ namespace ExportDXF.Services
private bool ExportPartToDxf( private bool ExportPartToDxf(
PartDoc part, PartDoc part,
string configName, string configName,
DrawingDoc templateDrawing,
string savePath, string savePath,
ExportContext context) ExportContext context)
{ {
@@ -141,6 +156,8 @@ namespace ExportDXF.Services
return false; return false;
} }
var templateDrawing = context.GetOrCreateTemplateDrawing();
SolidWorksHelper.ConfigureFlatPatternSettings(model); SolidWorksHelper.ConfigureFlatPatternSettings(model);
var sheet = templateDrawing.IGetCurrentSheet(); var sheet = templateDrawing.IGetCurrentSheet();
@@ -266,7 +283,11 @@ namespace ExportDXF.Services
return prefix + item.PartName; return prefix + item.PartName;
} }
return prefix + item.ItemNo.PadLeft(2, '0'); var num = item.ItemNo.PadLeft(2, '0');
// Expected format: {DrawingNo} PT{ItemNo}
return string.IsNullOrWhiteSpace(prefix)
? $"PT{num}"
: $"{prefix} PT{num}";
} }
private void LogExportFailure(Item item, ExportContext context) private void LogExportFailure(Item item, ExportContext context)
@@ -286,15 +307,5 @@ namespace ExportDXF.Services
Color.Red); 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.");
}
} }
} }

Binary file not shown.

View File

@@ -6,7 +6,7 @@
public class SheetMetalProperties public class SheetMetalProperties
{ {
/// <summary> /// <summary>
/// Material thickness in millimeters. /// Material thickness
/// </summary> /// </summary>
public double Thickness { get; set; } public double Thickness { get; set; }
@@ -16,12 +16,12 @@
public double KFactor { get; set; } public double KFactor { get; set; }
/// <summary> /// <summary>
/// Bend radius in millimeters. /// Inside bend radius
/// </summary> /// </summary>
public double BendRadius { get; set; } public double BendRadius { get; set; }
/// <summary> /// <summary>
/// Bend allowance in millimeters. /// Bend allowance
/// </summary> /// </summary>
public double BendAllowance { get; set; } public double BendAllowance { get; set; }
@@ -37,7 +37,7 @@
public override string ToString() public override string ToString()
{ {
return $"Thickness: {Thickness:F2}mm, K-Factor: {KFactor:F3}, Bend Radius: {BendRadius:F2}mm"; return $"Thickness: {Thickness:F2}\", K-Factor: {KFactor:F3}, Bend Radius: {BendRadius:F2}\"";
} }
} }
} }

View File

@@ -5,5 +5,7 @@
</startup> </startup>
<appSettings> <appSettings>
<add key="MaxBendRadius" value="2.0"/> <add key="MaxBendRadius" value="2.0"/>
<!-- Deployed API base URL (default port from Deploy-CutFabApi.ps1) -->
<add key="CutFab.ApiBaseUrl" value="http://localhost:7027"/>
</appSettings> </appSettings>
</configuration> </configuration>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.