Compare commits

...

51 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
AJ
2d5ffdf5c0 Refactored MainForm 2025-09-29 13:29:50 -04:00
AJ
6b37f0f6f7 Refactored BomToExcel 2024-12-23 14:10:12 -05:00
AJ
c1aaaf07ee Set flat pattern suppression state to fully resolved. 2023-07-22 08:23:53 -04:00
AJ
63b96e1451 Remove xml tags from BOM items. 2023-07-22 08:21:59 -04:00
AJ
97f45b2fcc Set SimplifyBends to true
When set to false, some bend reliefs are made of splines
2023-03-02 09:19:14 -05:00
AJ
2e6ecd11a1 Uncheck flat pattern corner treatment 2023-03-01 08:07:06 -05:00
AJ
db481baa89 Changed target framework to version 4.8 2023-03-01 08:06:34 -05:00
AJ
636818ed33 Submodule EtchBendLines updated
Submodule EtchBendLines:
    > 04031a7 - Changed target framework to version 4.8
    > 3e4ab60 - Allow multiple paths passed through args
    > 2b30498 - Wrap main in try catch
2023-03-01 08:05:48 -05:00
AJ
979067db21 Save drawing pdf in same directory as DXF files. 2022-06-23 12:44:44 -04:00
AJ
a0080c8a68 Print errors when getting BOM items. 2022-06-23 11:06:23 -04:00
AJ
aeec611f78 Include 5000 range in drawing numbers. 2022-06-23 10:47:30 -04:00
AJ
d3f6791b53 UpdatePrefix() 2021-10-12 20:51:40 -04:00
AJ
15edbf6fec Reset prefix if no drawing info is found. 2021-10-11 06:25:01 -04:00
AJ
0d1fd67b8d Updated publish settings 2021-04-14 07:15:23 -04:00
AJ
4fe7e0e8d7 Show automatic view flip decider first. 2021-03-24 06:47:46 -04:00
AJ
e39b7292cb Fixed missing argument for ExportDrawingToPDF 2021-03-22 07:15:30 -04:00
AJ
d7e4eb7e87 Specify savepath for ExportDrawingToPDF 2021-03-21 23:46:57 -04:00
AJ
60bd4ff645 Moved HasSupressedBends to ViewHelper class. 2021-03-21 22:21:40 -04:00
AJ
e9a7b51d24 Moved HideModelSketches to ViewHelper class. 2021-03-21 22:19:58 -04:00
AJ
016e32c2e3 Moved ViewFlipDeciders to separate namespace. 2021-03-21 22:19:25 -04:00
AJ
16c9d97e22 Moved ItemExtractors to separate namespace. 2021-03-21 22:09:31 -04:00
AJ
b693d8a092 Updated submodule EtchBendLines 2021-03-08 22:39:23 -05:00
AJ
96e54a686b Renamed ExportBomToExcel to BomToExcel 2021-03-05 20:42:20 -05:00
AJ
0b4173014f ExportDrawingToPDF 2021-03-04 21:47:25 -05:00
AJ
eee6bfdff3 ExportBomToExcel 2021-03-04 21:30:10 -05:00
60 changed files with 4996 additions and 1054 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/

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "EtchBendLines"] [submodule "EtchBendLines"]
path = EtchBendLines path = EtchBendLines
url = https://git.nforge.net/aj/etchbendlines.git url = https://git.thecozycat.net/aj/etchbendlines.git

View File

@@ -4,9 +4,9 @@ namespace ExportDXF
{ {
public class DrawingInfo public class DrawingInfo
{ {
private static Regex drawingFormatRegex = new Regex(@"(?<jobNo>[34]\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 string.Format("{0} {1}", 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

@@ -9,11 +9,11 @@
<AppDesignerFolder>Properties</AppDesignerFolder> <AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ExportDXF</RootNamespace> <RootNamespace>ExportDXF</RootNamespace>
<AssemblyName>ExportDXF</AssemblyName> <AssemblyName>ExportDXF</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion> <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<IsWebBootstrapper>false</IsWebBootstrapper> <IsWebBootstrapper>false</IsWebBootstrapper>
<TargetFrameworkProfile /> <TargetFrameworkProfile />
<PublishUrl>\\SERVER4\Data\Software\ExportDXF\</PublishUrl> <PublishUrl>\\REMCOSRV0\Data\Software\ExportDXF\</PublishUrl>
<Install>true</Install> <Install>true</Install>
<InstallFrom>Unc</InstallFrom> <InstallFrom>Unc</InstallFrom>
<UpdateEnabled>true</UpdateEnabled> <UpdateEnabled>true</UpdateEnabled>
@@ -24,8 +24,10 @@
<UpdateRequired>false</UpdateRequired> <UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions> <MapFileExtensions>true</MapFileExtensions>
<PublisherName>Rogers Engineering</PublisherName> <PublisherName>Rogers Engineering</PublisherName>
<ApplicationRevision>0</ApplicationRevision> <CreateWebPageOnPublish>true</CreateWebPageOnPublish>
<ApplicationVersion>1.2.2.%2a</ApplicationVersion> <WebPage>publish.htm</WebPage>
<ApplicationRevision>8</ApplicationRevision>
<ApplicationVersion>1.6.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust> <UseApplicationTrust>false</UseApplicationTrust>
<PublishWizardCompleted>true</PublishWizardCompleted> <PublishWizardCompleted>true</PublishWizardCompleted>
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
@@ -68,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>
@@ -83,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>
@@ -92,23 +98,54 @@
<Compile Include="BendOrientation.cs" /> <Compile Include="BendOrientation.cs" />
<Compile Include="Bounds.cs" /> <Compile Include="Bounds.cs" />
<Compile Include="DrawingInfo.cs" /> <Compile Include="DrawingInfo.cs" />
<Compile Include="Extensions.cs" /> <Compile Include="Extensions\StringExtensions.cs" />
<Compile Include="ItemExtractor.cs" /> <Compile Include="Extensions\SolidWorksExtensions.cs" />
<Compile Include="Forms\ViewFlipDeciderComboboxItem.cs" /> <Compile Include="Extensions\TimeSpanExtensions.cs" />
<Compile Include="Item.cs" /> <Compile Include="Extensions\UIExtensions.cs" />
<Compile Include="IViewFlipDecider.cs" /> <Compile Include="Extensions\UnitConversionExtensions.cs" />
<Compile Include="Helper.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"> <Compile Include="Forms\MainForm.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="Forms\MainForm.Designer.cs"> <Compile Include="Forms\MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon> <DependentUpon>MainForm.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="ItemExtractors\AssemblyItemExtractor.cs" />
<Compile Include="ItemExtractors\BomColumnIndices.cs" />
<Compile Include="ItemExtractors\BomItemExtractor.cs" />
<Compile Include="ItemExtractors\ItemExtractor.cs" />
<Compile Include="Forms\ViewFlipDeciderComboboxItem.cs" />
<Compile Include="Models\DocumentType.cs" />
<Compile Include="Models\ExportContext.cs" />
<Compile Include="Models\BomItem.cs" />
<Compile Include="Models\Item.cs" />
<Compile Include="Models\LogEvent.cs" />
<Compile Include="Models\SolidWorksDocument.cs" />
<Compile Include="Services\BomExtractor.cs" />
<Compile Include="Services\DrawingExporter.cs" />
<Compile Include="Services\DxfExportService.cs" />
<Compile Include="Services\CutFabApiClient.cs" />
<Compile Include="Services\PartExporter.cs" />
<Compile Include="Services\SolidWorksService.cs" />
<Compile Include="Utilities\SheetMetalProperties.cs" />
<Compile Include="Utilities\SolidWorksHelper.cs" />
<Compile Include="Utilities\TextHelper.cs" />
<Compile Include="Utilities\ViewHelper.cs" />
<Compile Include="ViewFlipDeciders\AskViewFlipDecider.cs" />
<Compile Include="ViewFlipDeciders\AutoViewFlipDecider.cs" />
<Compile Include="ViewFlipDeciders\IViewFlipDecider.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SolidWorksExtensions.cs" /> <Compile Include="ViewFlipDeciders\PreferUpViewFlipDecider.cs" />
<Compile Include="Units.cs" /> <Compile Include="ViewFlipDeciders\ViewFlipDeciderFactory.cs" />
<Compile Include="ViewHelper.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>
@@ -135,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" />
@@ -165,17 +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>
<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

@@ -1,74 +0,0 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace ExportDXF
{
public static class Extensions
{
public static void AppendText(this RichTextBox box, string text, Color color)
{
box.SelectionStart = box.TextLength;
box.SelectionLength = 0;
box.SelectionColor = color;
box.AppendText(text);
box.SelectionColor = box.ForeColor;
}
public static string ToReadableFormat(this TimeSpan ts)
{
var s = new StringBuilder();
if (ts.TotalHours >= 1)
{
var hrs = ts.Hours + ts.Days * 24.0;
s.Append(string.Format("{0}hrs ", hrs));
s.Append(string.Format("{0}min ", ts.Minutes));
s.Append(string.Format("{0}sec", ts.Seconds));
}
else if (ts.TotalMinutes >= 1)
{
s.Append(string.Format("{0}min ", ts.Minutes));
s.Append(string.Format("{0}sec", ts.Seconds));
}
else
{
s.Append(string.Format("{0} seconds", ts.Seconds));
}
return s.ToString();
}
public static string PunctuateList(this IEnumerable<string> stringList)
{
var list = stringList.ToList();
switch (list.Count)
{
case 0:
return string.Empty;
case 1:
return list[0];
case 2:
return string.Format("{0} and {1}", list[0], list[1]);
default:
var s = string.Empty;
for (int i = 0; i < list.Count - 1; i++)
s += list[i] + ", ";
s += "and " + list.Last();
return s;
}
}
}
}

View File

@@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
namespace ExportDXF namespace ExportDXF.Extensions
{ {
public static class SolidWorksExtensions public static class SolidWorksExtensions
{ {

View File

@@ -0,0 +1,48 @@
namespace ExportDXF.Extensions
{
/// <summary>
/// Extension methods for string manipulation.
/// </summary>
public static class StringExtensions
{
/// <summary>
/// Extension method to remove XML tags from a string.
/// </summary>
public static string RemoveXmlTags(this string input)
{
return ExportDXF.Utilities.TextHelper.RemoveXmlTags(input);
}
/// <summary>
/// Extension method to clean text (remove XML and normalize whitespace).
/// </summary>
public static string CleanText(this string input)
{
return ExportDXF.Utilities.TextHelper.CleanText(input);
}
/// <summary>
/// Extension method to sanitize a filename.
/// </summary>
public static string SanitizeFileName(this string input)
{
return ExportDXF.Utilities.TextHelper.SanitizeFileName(input);
}
/// <summary>
/// Extension method to truncate a string.
/// </summary>
public static string Truncate(this string input, int maxLength, bool useEllipsis = true)
{
return ExportDXF.Utilities.TextHelper.Truncate(input, maxLength, useEllipsis);
}
/// <summary>
/// Extension method to convert to title case.
/// </summary>
public static string ToTitleCase(this string input)
{
return ExportDXF.Utilities.TextHelper.ToTitleCase(input);
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
namespace ExportDXF.Extensions
{
/// <summary>
/// Extension methods for TimeSpan formatting.
/// </summary>
public static class TimeSpanExtensions
{
/// <summary>
/// Formats a TimeSpan into a human-readable string.
/// </summary>
/// <param name="ts">The TimeSpan to format.</param>
/// <returns>A human-readable duration string.</returns>
public static string ToReadableFormat(this TimeSpan ts)
{
if (ts.TotalHours >= 1)
{
var totalHours = (int)(ts.TotalDays * 24 + ts.Hours);
return $"{totalHours}hrs {ts.Minutes}min {ts.Seconds}sec";
}
if (ts.TotalMinutes >= 1)
{
return $"{ts.Minutes}min {ts.Seconds}sec";
}
return $"{ts.Seconds} seconds";
}
}
}

View File

@@ -0,0 +1,18 @@
using System.Drawing;
using System.Windows.Forms;
namespace ExportDXF.Extensions
{
public static class UIExtensions
{
public static void AppendText(this RichTextBox box, string text, Color color)
{
box.SelectionStart = box.TextLength;
box.SelectionLength = 0;
box.SelectionColor = color;
box.AppendText(text);
box.SelectionColor = box.ForeColor;
}
}
}

View File

@@ -0,0 +1,41 @@
namespace ExportDXF.Extensions
{
/// <summary>
/// Extension methods for unit conversion between SolidWorks (meters) and millimeters.
/// </summary>
public static class UnitConversionExtensions
{
private const double METERS_TO_MM = 1000;
private const double METERS_TO_INCHES = 39.37007874;
/// <summary>
/// Converts a SolidWorks dimension (in meters) to millimeters.
/// </summary>
/// <param name="meters">The value in meters.</param>
/// <returns>The value in millimeters.</returns>
public static double FromSolidWorksToMM(this double meters)
{
return meters * METERS_TO_MM;
}
/// <summary>
/// Converts millimeters to SolidWorks dimension (meters).
/// </summary>
/// <param name="millimeters">The value in millimeters.</param>
/// <returns>The value in meters.</returns>
public static double FromMMToSolidWorks(this double millimeters)
{
return millimeters / METERS_TO_MM;
}
public static double FromSolidWorksToInches(this double meters)
{
return meters * METERS_TO_INCHES;
}
public static double FromInchesToSolidWorks(this double inches)
{
return inches / METERS_TO_INCHES;
}
}
}

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.textBox1_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, 119);
this.richTextBox1.Name = "richTextBox1";
this.richTextBox1.ReadOnly = true;
this.richTextBox1.Size = new System.Drawing.Size(754, 329);
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;
} }
} }

File diff suppressed because it is too large Load Diff

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

@@ -1,4 +1,6 @@
namespace ExportDXF.Forms using ExportDXF.ViewFlipDeciders;
namespace ExportDXF.Forms
{ {
public class ViewFlipDeciderComboboxItem public class ViewFlipDeciderComboboxItem
{ {

View File

@@ -1,21 +0,0 @@
namespace ExportDXF
{
public static class Helper
{
public static string GetNumWithSuffix(int i)
{
if (i >= 11 && i <= 13)
return i.ToString() + "th";
var j = i % 10;
switch (j)
{
case 1: return i.ToString() + "st";
case 2: return i.ToString() + "nd";
case 3: return i.ToString() + "rd";
default: return i.ToString() + "th";
}
}
}
}

View File

@@ -1,78 +0,0 @@
using System.Linq;
using System.Windows.Forms;
namespace ExportDXF
{
public interface IViewFlipDecider
{
bool ShouldFlip(SolidWorks.Interop.sldworks.View view);
string Name { get; }
}
public class AutoViewFlipDecider : IViewFlipDecider
{
public string Name => "Automatic";
public bool ShouldFlip(SolidWorks.Interop.sldworks.View view)
{
var orientation = ViewHelper.GetOrientation(view);
var bounds = ViewHelper.GetBounds(view);
var bends = ViewHelper.GetBends(view);
var up = bends.Where(b => b.Direction == BendDirection.Up).ToList();
var down = bends.Where(b => b.Direction == BendDirection.Down).ToList();
if (down.Count == 0)
return false;
if (up.Count == 0)
return true;
var bend = ViewHelper.GetBendClosestToBounds(bounds, bends);
return bend.Direction == BendDirection.Down;
}
}
public class AskViewFlipDecider : IViewFlipDecider
{
public string Name => "Ask to flip";
public bool ShouldFlip(SolidWorks.Interop.sldworks.View view)
{
var bends = ViewHelper.GetBends(view);
if (bends.Count == 0)
return false;
return MessageBox.Show("Flip view?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes;
}
public override string ToString()
{
return Name;
}
}
public class PreferUpViewFlipDecider : IViewFlipDecider
{
public string Name => "Prefer up bends, ask if up/down";
public bool ShouldFlip(SolidWorks.Interop.sldworks.View view)
{
var bends = ViewHelper.GetBends(view);
var up = bends.Where(b => b.Direction == BendDirection.Up).ToList();
var down = bends.Where(b => b.Direction == BendDirection.Down).ToList();
if (up.Count > 0 && down.Count > 0)
{
return MessageBox.Show("Flip view?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes;
}
else
{
return down.Count > 0;
}
}
}
}

View File

@@ -1,29 +0,0 @@
using SolidWorks.Interop.sldworks;
namespace ExportDXF
{
public class Item
{
public string ItemNo { get; set; }
public string FileName { get; set; }
public string PartName { get; set; }
public string Configuration { get; set; }
public int Quantity { get; set; }
public string Description { get; set; }
public double Thickness { get; set; }
public double KFactor { get; set; }
public double BendRadius { get; set; }
public string Material { get; set; }
public Component2 Component { get; set; }
}
}

View File

@@ -0,0 +1,66 @@
using ExportDXF.Extensions;
using ExportDXF.Services;
using SolidWorks.Interop.sldworks;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace ExportDXF.ItemExtractors
{
public class AssemblyItemExtractor : ItemExtractor
{
private AssemblyDoc assembly;
public AssemblyItemExtractor(AssemblyDoc assembly)
{
this.assembly = assembly;
}
public bool TopLevelOnly { get; set; }
private string GetComponentName(Component2 component)
{
var filepath = component.GetTitle();
var filename = Path.GetFileNameWithoutExtension(filepath);
var isDefaultConfig = component.ReferencedConfiguration.ToLower() == "default";
return isDefaultConfig ? filename : $"{filename} [{component.ReferencedConfiguration}]";
}
public List<Item> GetItems()
{
var list = new List<Item>();
assembly.ResolveAllLightWeightComponents(false);
var assemblyComponents = ((Array)assembly.GetComponents(TopLevelOnly))
.Cast<Component2>()
.Where(c => !c.IsHidden(true));
var componentGroups = assemblyComponents
.GroupBy(c => c.GetTitle() + c.ReferencedConfiguration);
foreach (var group in componentGroups)
{
var component = group.First();
var model = component.GetModelDoc2() as ModelDoc2;
if (model == null)
continue;
var name = GetComponentName(component);
list.Add(new Item
{
PartName = name,
Quantity = group.Count(),
Component = component,
Configuration = component.ReferencedConfiguration
});
}
return list;
}
}
}

View File

@@ -0,0 +1,13 @@
namespace ExportDXF.ItemExtractors
{
public class BomColumnIndices
{
public int ItemNumber { get; set; } = -1;
public int Quantity { get; set; } = -1;
public int Description { get; set; } = -1;
public int PartNumber { get; set; } = -1;
}
}

View File

@@ -1,17 +1,14 @@
using SolidWorks.Interop.sldworks; using ExportDXF.Extensions;
using ExportDXF.Services;
using ExportDXF.Utilities;
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.IO;
using System.Linq; using System.Linq;
namespace ExportDXF namespace ExportDXF.ItemExtractors
{ {
public interface ItemExtractor
{
List<Item> GetItems();
}
public class BomItemExtractor : ItemExtractor public class BomItemExtractor : ItemExtractor
{ {
private BomTableAnnotation bom; private BomTableAnnotation bom;
@@ -57,22 +54,42 @@ namespace ExportDXF
if (columnIndices.ItemNumber != -1) if (columnIndices.ItemNumber != -1)
{ {
item.ItemNo = table.DisplayedText[rowIndex, columnIndices.ItemNumber]; var x = table.DisplayedText[rowIndex, columnIndices.ItemNumber];
x = TextHelper.RemoveXmlTags(x);
double d;
if (double.TryParse(x, out d))
{
item.ItemNo = d.ToString();
}
else
{
item.ItemNo = x;
}
} }
if (columnIndices.PartNumber != -1) if (columnIndices.PartNumber != -1)
{ {
item.PartName = table.DisplayedText[rowIndex, columnIndices.PartNumber]; var x = table.DisplayedText[rowIndex, columnIndices.PartNumber];
x = TextHelper.RemoveXmlTags(x);
item.PartName = x;
} }
if (columnIndices.Description != -1) if (columnIndices.Description != -1)
{ {
item.Description = table.DisplayedText[rowIndex, columnIndices.Description]; var x = table.DisplayedText[rowIndex, columnIndices.Description];
x = TextHelper.RemoveXmlTags(x);
item.Description = x;
} }
if (columnIndices.Quantity != -1) if (columnIndices.Quantity != -1)
{ {
var qtyString = table.DisplayedText[rowIndex, columnIndices.Quantity]; var qtyString = table.DisplayedText[rowIndex, columnIndices.Quantity];
qtyString = TextHelper.RemoveXmlTags(qtyString);
int qty = 0; int qty = 0;
int.TryParse(qtyString, out qty); int.TryParse(qtyString, out qty);
@@ -142,71 +159,4 @@ namespace ExportDXF
return items; return items;
} }
} }
public class AssemblyItemExtractor : ItemExtractor
{
private AssemblyDoc assembly;
public AssemblyItemExtractor(AssemblyDoc assembly)
{
this.assembly = assembly;
}
public bool TopLevelOnly { get; set; }
private string GetComponentName(Component2 component)
{
var filepath = component.GetTitle();
var filename = Path.GetFileNameWithoutExtension(filepath);
var isDefaultConfig = component.ReferencedConfiguration.ToLower() == "default";
return isDefaultConfig ? filename : $"{filename} [{component.ReferencedConfiguration}]";
}
public List<Item> GetItems()
{
var list = new List<Item>();
assembly.ResolveAllLightWeightComponents(false);
var assemblyComponents = ((Array)assembly.GetComponents(TopLevelOnly))
.Cast<Component2>()
.Where(c => !c.IsHidden(true));
var componentGroups = assemblyComponents
.GroupBy(c => c.GetTitle() + c.ReferencedConfiguration);
foreach (var group in componentGroups)
{
var component = group.First();
var model = component.GetModelDoc2() as ModelDoc2;
if (model == null)
continue;
var name = GetComponentName(component);
list.Add(new Item
{
PartName = name,
Quantity = group.Count(),
Component = component,
Configuration = component.ReferencedConfiguration
});
}
return list;
}
}
public class BomColumnIndices
{
public int ItemNumber { get; set; } = -1;
public int Quantity { get; set; } = -1;
public int Description { get; set; } = -1;
public int PartNumber { get; set; } = -1;
}
} }

View File

@@ -0,0 +1,10 @@
using ExportDXF.Services;
using System.Collections.Generic;
namespace ExportDXF.ItemExtractors
{
public interface ItemExtractor
{
List<Item> GetItems();
}
}

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

@@ -0,0 +1,13 @@
namespace ExportDXF.Models
{
/// <summary>
/// Enumeration of SolidWorks document types.
/// </summary>
public enum DocumentType
{
Unknown,
Part,
Assembly,
Drawing
}
}

View File

@@ -0,0 +1,135 @@
using ExportDXF.ViewFlipDeciders;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using System;
using System.Drawing;
using System.IO;
using System.Threading;
using System.Windows.Forms;
namespace ExportDXF.Services
{
/// <summary>
/// Context object containing all information needed for an export operation.
/// </summary>
public class ExportContext
{
private const string DRAWING_TEMPLATE_FOLDER = "Templates";
private const string DRAWING_TEMPLATE_FILE = "Blank.drwdot";
/// <summary>
/// The document to be exported.
/// </summary>
public SolidWorksDocument ActiveDocument { get; set; }
/// <summary>
/// The view flip decider to determine if views should be flipped.
/// </summary>
public IViewFlipDecider ViewFlipDecider { get; set; }
/// <summary>
/// Prefix to prepend to exported filenames.
/// </summary>
public string FilePrefix { get; set; }
/// <summary>
/// Selected Equipment ID for API operations (optional).
/// </summary>
public int? EquipmentId { get; set; }
/// <summary>
/// Cancellation token for canceling the export operation.
/// </summary>
public CancellationToken CancellationToken { get; set; }
/// <summary>
/// Callback for reporting progress and status messages.
/// </summary>
public Action<string, Color?> ProgressCallback { get; set; }
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;
}
}
}

65
ExportDXF/Models/Item.cs Normal file
View File

@@ -0,0 +1,65 @@
using SolidWorks.Interop.sldworks;
namespace ExportDXF.Services
{
/// <summary>
/// Represents an item extracted from a BOM or assembly.
/// </summary>
public class Item
{
/// <summary>
/// Item number from the BOM.
/// </summary>
public string ItemNo { get; set; }
/// <summary>
/// Part name or file name.
/// </summary>
public string PartName { get; set; }
/// <summary>
/// Configuration name.
/// </summary>
public string Configuration { get; set; }
/// <summary>
/// Item description.
/// </summary>
public string Description { get; set; }
/// <summary>
/// Quantity of this item.
/// </summary>
public int Quantity { get; set; }
/// <summary>
/// Material specification.
/// </summary>
public string Material { get; set; }
/// <summary>
/// Sheet metal thickness in millimeters.
/// </summary>
public double Thickness { get; set; }
/// <summary>
/// Sheet metal K-factor.
/// </summary>
public double KFactor { get; set; }
/// <summary>
/// Bend radius in millimeters.
/// </summary>
public double BendRadius { get; set; }
/// <summary>
/// The exported DXF filename (without path or extension).
/// </summary>
public string FileName { get; set; }
/// <summary>
/// The SolidWorks component reference.
/// </summary>
public Component2 Component { get; set; }
}
}

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

@@ -0,0 +1,30 @@
using ExportDXF.Models;
namespace ExportDXF.Services
{
/// <summary>
/// Represents a SolidWorks document with essential metadata.
/// </summary>
public class SolidWorksDocument
{
/// <summary>
/// The title/name of the document.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The full file path of the document.
/// </summary>
public string FilePath { get; set; }
/// <summary>
/// The type of document (Part, Assembly, or Drawing).
/// </summary>
public DocumentType DocumentType { get; set; }
/// <summary>
/// The native SolidWorks document object (ModelDoc2, PartDoc, etc.).
/// </summary>
public object NativeDocument { get; set; }
}
}

View File

@@ -1,19 +1,95 @@
using System; using ExportDXF.Forms;
using ExportDXF.Services;
using System;
using System.Configuration;
using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace ExportDXF namespace ExportDXF
{ {
internal static class Program static class Program
{ {
/// <summary> /// <summary>
/// The main entry point for the application. /// The main entry point for the application.
/// </summary> /// </summary>
[STAThread] [STAThread]
private static void Main() static void Main()
{ {
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Forms.MainForm());
// Simple DI setup - could use a DI container like Microsoft.Extensions.DependencyInjection
var container = new ServiceContainer();
// Show drawing selection dialog first
var drawingSelectionForm = container.ResolveDrawingSelectionAsync().GetAwaiter().GetResult();
var result = drawingSelectionForm.ShowDialog();
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
} }
} }
}
/// <summary>
/// Simple dependency injection container.
/// For production, consider using Microsoft.Extensions.DependencyInjection or similar.
/// </summary>
public class ServiceContainer
{
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
var solidWorksService = new SolidWorksService();
var bomExtractor = new BomExtractor();
var partExporter = new PartExporter();
var drawingExporter = new DrawingExporter();
var exportService = new DxfExportService(
solidWorksService,
bomExtractor,
partExporter,
drawingExporter,
_apiClient);
return new MainForm(solidWorksService, exportService, _apiClient, selectedDrawingId, selectedDrawingNumber);
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
This file is automatically generated by Visual Studio .Net. It is
used to store generic object data source configuration information.
Renaming the file extension or editing the content of this file may
cause the file to be unrecognizable by the program.
-->
<GenericObjectDataSource DisplayName="Item" Version="1.0" xmlns="urn:schemas-microsoft-com:xml-msdatasource">
<TypeInfo>ExportDXF.Item, ExportDXF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</TypeInfo>
</GenericObjectDataSource>

View File

@@ -19,7 +19,7 @@ namespace ExportDXF.Properties {
// class via a tool like ResGen or Visual Studio. // class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen // To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project. // with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources { internal class Resources {

View File

@@ -12,7 +12,7 @@ namespace ExportDXF.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.4.0.0")] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

View File

@@ -0,0 +1,66 @@
using ExportDXF.Extensions;
using ExportDXF.ItemExtractors;
using SolidWorks.Interop.sldworks;
using System;
using System.Collections.Generic;
using System.Drawing;
namespace ExportDXF.Services
{
/// <summary>
/// Service for extracting items from a Bill of Materials (BOM).
/// </summary>
public interface IBomExtractor
{
/// <summary>
/// Extracts items from all BOM tables in a drawing document.
/// </summary>
/// <param name="drawing">The drawing document containing BOM tables.</param>
/// <param name="progressCallback">Optional callback for progress updates.</param>
/// <returns>A list of extracted items.</returns>
List<Item> ExtractFromDrawing(DrawingDoc drawing, Action<string, Color?> progressCallback);
}
public class BomExtractor : IBomExtractor
{
public List<Item> ExtractFromDrawing(DrawingDoc drawing, Action<string, Color?> progressCallback)
{
if (drawing == null)
throw new ArgumentNullException(nameof(drawing));
var bomTables = drawing.GetBomTables();
if (bomTables.Count == 0)
{
progressCallback?.Invoke("Error: Bill of materials not found.", Color.Red);
return new List<Item>();
}
progressCallback?.Invoke($"Found {bomTables.Count} BOM table(s)", null);
var allItems = new List<Item>();
foreach (var bom in bomTables)
{
try
{
var extractor = new BomItemExtractor(bom)
{
SkipHiddenRows = true
};
progressCallback?.Invoke($"Fetching components from {bom.BomFeature.Name}", null);
var items = extractor.GetItems();
allItems.AddRange(items);
}
catch (Exception ex)
{
progressCallback?.Invoke($"Failed to extract from {bom.BomFeature.Name}: {ex.Message}", Color.Red);
}
}
return allItems;
}
}
}

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

@@ -0,0 +1,115 @@
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using System;
using System.Drawing;
using System.IO;
namespace ExportDXF.Services
{
/// <summary>
/// Service for exporting drawing documents to PDF format.
/// </summary>
public interface IDrawingExporter
{
/// <summary>
/// Exports a drawing document to PDF format.
/// </summary>
/// <param name="drawing">The drawing document to export.</param>
/// <param name="saveDirectory">The directory where the PDF file will be saved.</param>
/// <param name="context">The export context containing SolidWorks app and callbacks.</param>
void ExportToPdf(DrawingDoc drawing, string saveDirectory, ExportContext context);
}
public class DrawingExporter : IDrawingExporter
{
public void ExportToPdf(DrawingDoc drawing, string saveDirectory, ExportContext context)
{
if (drawing == null)
throw new ArgumentNullException(nameof(drawing));
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 (context.SolidWorksApp == null)
throw new ArgumentException("SolidWorksApp cannot be null in context.", nameof(context));
try
{
var pdfFileName = GetPdfFileName(drawing);
var pdfPath = Path.Combine(saveDirectory, pdfFileName);
var model = drawing as ModelDoc2;
var sldWorks = context.SolidWorksApp;
var exportData = sldWorks.GetExportFileData(
(int)swExportDataFileType_e.swExportPdfData) as ExportPdfData;
if (exportData == null)
{
throw new InvalidOperationException("Failed to get PDF export data from SolidWorks.");
}
exportData.ViewPdfAfterSaving = false;
exportData.SetSheets(
(int)swExportDataSheetsToExport_e.swExportData_ExportAllSheets,
drawing);
context.ProgressCallback?.Invoke($"Exporting drawing to PDF: \"{pdfFileName}\"", null);
int errors = 0;
int warnings = 0;
var modelExtension = model.Extension;
var success = modelExtension.SaveAs(
pdfPath,
(int)swSaveAsVersion_e.swSaveAsCurrentVersion,
(int)swSaveAsOptions_e.swSaveAsOptions_Silent,
exportData,
ref errors,
ref warnings);
if (success && errors == 0)
{
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
{
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)
{
var errorMessage = $"Failed to export PDF: {ex.Message}";
context.ProgressCallback?.Invoke(errorMessage, Color.Red);
throw new InvalidOperationException(errorMessage, ex);
}
}
private string GetPdfFileName(DrawingDoc drawing)
{
var model = drawing as ModelDoc2;
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";
}
}
}

View File

@@ -0,0 +1,482 @@
using ExportDXF.Extensions;
using ExportDXF.ItemExtractors;
using ExportDXF.Models;
using ExportDXF;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Environment = System.Environment;
using System.IO.Compression;
namespace ExportDXF.Services
{
public interface IDxfExportService
{
/// <summary>
/// Exports the document specified in the context to DXF format.
/// </summary>
/// <param name="context">The export context containing all necessary information.</param>
void Export(ExportContext context);
}
/// <summary>
/// Service responsible for orchestrating the export of SolidWorks documents to DXF format.
/// </summary>
public class DxfExportService : IDxfExportService
{
private readonly ISolidWorksService _solidWorksService;
private readonly IBomExtractor _bomExtractor;
private readonly IPartExporter _partExporter;
private readonly IDrawingExporter _drawingExporter;
private readonly ICutFabApiClient _apiClient;
public DxfExportService(
ISolidWorksService solidWorksService,
IBomExtractor bomExtractor,
IPartExporter partExporter,
IDrawingExporter drawingExporter,
ICutFabApiClient apiClient)
{
_solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService));
_bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor));
_partExporter = partExporter ?? throw new ArgumentNullException(nameof(partExporter));
_drawingExporter = drawingExporter ?? throw new ArgumentNullException(nameof(drawingExporter));
_apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
}
/// <summary>
/// Exports the document specified in the context to DXF format.
/// </summary>
public void Export(ExportContext context)
{
if (context == null)
throw new ArgumentNullException(nameof(context));
ValidateContext(context);
SetupExportContext(context);
var startTime = DateTime.Now;
try
{
_solidWorksService.EnableUserControl(false);
switch (context.ActiveDocument.DocumentType)
{
case DocumentType.Part:
ExportPart(context);
break;
case DocumentType.Assembly:
ExportAssembly(context);
break;
case DocumentType.Drawing:
ExportDrawing(context);
break;
default:
LogProgress(context, "Unknown document type.", Color.Red);
break;
}
}
finally
{
CleanupExportContext(context);
_solidWorksService.EnableUserControl(true);
var duration = DateTime.Now - startTime;
LogProgress(context, $"Run time: {duration.ToReadableFormat()}", null);
}
}
#region Export Methods by Document Type
private void ExportPart(ExportContext context)
{
LogProgress(context, "Active document is a Part", null);
var part = context.ActiveDocument.NativeDocument as PartDoc;
if (part == null)
{
LogProgress(context, "Failed to get part document.", Color.Red);
return;
}
var tempDir = CreateTempWorkDir();
_partExporter.ExportSinglePart(part, tempDir, context);
}
private void ExportAssembly(ExportContext context)
{
LogProgress(context, "Active document is an Assembly", null);
LogProgress(context, "Fetching components...", null);
var assembly = context.ActiveDocument.NativeDocument as AssemblyDoc;
if (assembly == null)
{
LogProgress(context, "Failed to get assembly document.", Color.Red);
return;
}
var items = ExtractItemsFromAssembly(assembly, context);
if (items == null || items.Count == 0)
{
LogProgress(context, "No items found in assembly.", Color.DarkBlue);
return;
}
LogProgress(context, $"Found {items.Count} item(s).", null);
var tempDir = CreateTempWorkDir();
ExportItems(items, tempDir, context, drawingId: null);
}
private void ExportDrawing(ExportContext context)
{
LogProgress(context, "Active document is a Drawing", null);
LogProgress(context, "Finding BOM tables...", null);
var drawing = context.ActiveDocument.NativeDocument as DrawingDoc;
if (drawing == null)
{
LogProgress(context, "Failed to get drawing document.", Color.Red);
return;
}
var items = _bomExtractor.ExtractFromDrawing(drawing, context.ProgressCallback);
if (items == null || items.Count == 0)
{
LogProgress(context, "Error: Bill of materials not found.", Color.Red);
return;
}
LogProgress(context, $"Found {items.Count} component(s)", null);
var tempDir = CreateTempWorkDir();
// Determine drawing number
var drawingNumber = ParseDrawingNumber(context);
if (string.IsNullOrWhiteSpace(drawingNumber))
{
LogProgress(context, "Warning: Could not determine drawing number for API upload.", Color.DarkBlue);
}
// Resolve drawing ID if possible
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);
}
}
// Export drawing to PDF first
_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
#region Item Processing
private List<Item> ExtractItemsFromAssembly(AssemblyDoc assembly, ExportContext context)
{
try
{
var extractor = new AssemblyItemExtractor(assembly)
{
TopLevelOnly = false
};
return extractor.GetItems();
}
catch (Exception ex)
{
LogProgress(context, $"Failed to extract items from assembly: {ex.Message}", Color.Red);
return new List<Item>();
}
}
private void ExportItems(List<Item> items, string saveDirectory, ExportContext context, int? drawingId)
{
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)
{
var dto = new
{
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);
}
if (!string.IsNullOrEmpty(item.FileName))
{
successCount++;
// If we know the drawing, upload DXF
if (drawingId.HasValue)
{
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 { }
}
}
}
}
else
{
failureCount++;
}
}
catch (Exception ex)
{
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", Color.Red);
failureCount++;
}
LogProgress(context, "", null);
}
LogProgress(context, $"Export complete: {successCount} succeeded, {failureCount} failed",
failureCount > 0 ? Color.DarkBlue : Color.Green);
}
#endregion
#region Temp + Upload Helpers
private string CreateTempWorkDir()
{
var path = Path.Combine(Path.GetTempPath(), "ExportDXF-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(path);
return path;
}
private string ParseDrawingNumber(ExportContext context)
{
// Prefer prefix (e.g., "5007 A02 PT"), fallback to active document title
var candidate = context?.FilePrefix;
var info = string.IsNullOrWhiteSpace(candidate) ? null : DrawingInfo.Parse(candidate);
if (info == null)
{
var title = context?.ActiveDocument?.Title;
info = string.IsNullOrWhiteSpace(title) ? null : DrawingInfo.Parse(title);
}
return info != null ? ($"{info.EquipmentNo} {info.DrawingNo}") : null;
}
private string CreateZipWithSingleFile(string filePath)
{
var zipPath = Path.Combine(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath) + ".zip");
if (File.Exists(zipPath)) File.Delete(zipPath);
using (var zip = System.IO.Compression.ZipFile.Open(zipPath, System.IO.Compression.ZipArchiveMode.Create))
{
zip.CreateEntryFromFile(filePath, Path.GetFileName(filePath));
}
return zipPath;
}
#endregion
#region Helper Methods
private void ValidateContext(ExportContext context)
{
if (context.ActiveDocument == null)
throw new ArgumentException("ActiveDocument cannot be null.", nameof(context));
if (context.ProgressCallback == null)
throw new ArgumentException("ProgressCallback cannot be null.", nameof(context));
}
private void LogProgress(ExportContext context, string message, Color? color)
{
context.ProgressCallback?.Invoke(message, color);
}
#endregion
}
}

View File

@@ -0,0 +1,311 @@
using ExportDXF.Extensions;
using ExportDXF.Utilities;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using System;
using System.Drawing;
using System.IO;
namespace ExportDXF.Services
{
/// <summary>
/// Service for exporting parts to DXF format.
/// </summary>
public interface IPartExporter
{
/// <summary>
/// Exports a single part document to DXF.
/// </summary>
/// <param name="part">The part document to export.</param>
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param>
/// <param name="context">The export context.</param>
void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
/// <summary>
/// Exports an item (component from BOM or assembly) to DXF.
/// </summary>
/// <param name="item">The item to export.</param>
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param>
/// <param name="context">The export context.</param>
void ExportItem(Item item, string saveDirectory, ExportContext context);
}
public class PartExporter : IPartExporter
{
public void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
{
if (part == null)
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 activeConfig = model.GetActiveConfiguration() as SolidWorks.Interop.sldworks.Configuration;
var originalConfigName = activeConfig?.Name;
try
{
var fileName = GetSinglePartFileName(model, context.FilePrefix);
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
context.GetOrCreateTemplateDrawing();
ExportPartToDxf(part, originalConfigName, savePath, context);
}
finally
{
if (originalConfigName != null)
{
model.ShowConfiguration(originalConfigName);
}
}
}
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)
{
context.ProgressCallback?.Invoke($"Item {item?.ItemNo} - skipped, no component", Color.Yellow);
return;
}
context.CancellationToken.ThrowIfCancellationRequested();
item.Component.SetLightweightToResolved();
var model = item.Component.GetModelDoc2() as ModelDoc2;
var part = model as PartDoc;
if (part == null)
{
context.ProgressCallback?.Invoke($"{item.ItemNo} - skipped, not a part document", null);
return;
}
EnrichItemWithMetadata(item, model, part);
var fileName = GetItemFileName(item, context.FilePrefix);
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
var templateDrawing = context.GetOrCreateTemplateDrawing();
if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, savePath, context))
{
item.FileName = Path.GetFileNameWithoutExtension(savePath);
}
else
{
LogExportFailure(item, context);
}
}
private void EnrichItemWithMetadata(Item item, ModelDoc2 model, PartDoc part)
{
// Get sheet metal properties
var sheetMetalProps = SolidWorksHelper.GetSheetMetalProperties(model);
if (sheetMetalProps != null)
{
item.Thickness = sheetMetalProps.Thickness;
item.KFactor = sheetMetalProps.KFactor;
item.BendRadius = sheetMetalProps.BendRadius;
}
// Get description from custom properties
var config = item.Component.ReferencedConfiguration;
// 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);
// Get material
item.Material = part.GetMaterialPropertyName2(config, out _);
}
private bool ExportPartToDxf(
PartDoc part,
string configName,
string savePath,
ExportContext context)
{
try
{
var model = part as ModelDoc2;
if (!model.IsSheetMetal())
{
context.ProgressCallback?.Invoke($"{model.GetTitle()} - skipped, not sheet metal", null);
return false;
}
var templateDrawing = context.GetOrCreateTemplateDrawing();
SolidWorksHelper.ConfigureFlatPatternSettings(model);
var sheet = templateDrawing.IGetCurrentSheet();
var modelName = Path.GetFileNameWithoutExtension(model.GetPathName());
sheet.SetName(modelName);
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Creating flat pattern", null);
var view = CreateFlatPatternView(templateDrawing, model, configName);
if (view == null)
{
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Failed to create flat pattern", Color.Red);
return false;
}
ConfigureFlatPatternView(view, templateDrawing, model, configName, context);
if (context.ViewFlipDecider?.ShouldFlip(view) == true)
{
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Flipped view", Color.Blue);
view.FlipView = true;
}
var drawingModel = templateDrawing as ModelDoc2;
drawingModel.SaveAs(savePath);
AddEtchLines(savePath);
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Saved to \"{savePath}\"", Color.Green);
DeleteView(drawingModel, view);
return true;
}
catch (Exception ex)
{
context.ProgressCallback?.Invoke($"Export failed: {ex.Message}", Color.Red);
return false;
}
}
private SolidWorks.Interop.sldworks.View CreateFlatPatternView(
DrawingDoc drawing,
ModelDoc2 part,
string configName)
{
return drawing.CreateFlatPatternViewFromModelView3(
part.GetPathName(),
configName,
0, 0, 0,
false,
false);
}
private void ConfigureFlatPatternView(
SolidWorks.Interop.sldworks.View view,
DrawingDoc drawing,
ModelDoc2 partModel,
string configName,
ExportContext context)
{
view.ShowSheetMetalBendNotes = true;
var drawingModel = drawing as ModelDoc2;
drawingModel.ViewZoomtofit2();
var flatPatternModel = ViewHelper.GetModelFromView(view);
SolidWorksHelper.SetFlatPatternSuppressionState(
flatPatternModel,
swComponentSuppressionState_e.swComponentFullyResolved);
if (ViewHelper.HasSupressedBends(view))
{
context.ProgressCallback?.Invoke("A bend is suppressed, please check flat pattern", Color.Red);
}
if (ViewHelper.HideModelSketches(view))
{
// Recreate view without sketches
DeleteView(drawingModel, view);
view = CreateFlatPatternView(drawing, partModel, configName);
view.ShowSheetMetalBendNotes = true;
}
}
private void DeleteView(ModelDoc2 drawing, SolidWorks.Interop.sldworks.View view)
{
drawing.SelectByName(0, view.Name);
drawing.DeleteSelection(false);
}
private void AddEtchLines(string dxfPath)
{
try
{
var etcher = new EtchBendLines.Etcher();
etcher.AddEtchLines(dxfPath);
}
catch (Exception)
{
// Silently fail if etch lines can't be added
}
}
private string GetSinglePartFileName(ModelDoc2 model, string prefix)
{
var title = model.GetTitle().Replace(".SLDPRT", "");
var config = model.ConfigurationManager.ActiveConfiguration.Name;
var isDefaultConfig = string.Equals(config, "default", StringComparison.OrdinalIgnoreCase);
var name = isDefaultConfig ? title : $"{title} [{config}]";
return prefix + name;
}
private string GetItemFileName(Item item, string prefix)
{
prefix = prefix?.Replace("\"", "''") ?? string.Empty;
if (string.IsNullOrWhiteSpace(item.ItemNo))
{
return prefix + item.PartName;
}
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)
{
var desc = item.Description?.ToLower() ?? string.Empty;
if (desc.Contains("laser"))
{
context.ProgressCallback?.Invoke(
$"Failed to export item #{item.ItemNo} but description says it is laser cut.",
Color.Red);
}
else if (desc.Contains("plasma"))
{
context.ProgressCallback?.Invoke(
$"Failed to export item #{item.ItemNo} but description says it is plasma cut.",
Color.Red);
}
}
}
}

View File

@@ -0,0 +1,406 @@
using ExportDXF.Models;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace ExportDXF.Services
{
/// <summary>
/// Service for managing SolidWorks application connection and document lifecycle.
/// </summary>
public interface ISolidWorksService : IDisposable
{
/// <summary>
/// Connects to the SolidWorks application asynchronously.
/// </summary>
Task ConnectAsync();
/// <summary>
/// Gets the currently active SolidWorks document.
/// </summary>
/// <returns>The active document or null if no document is open.</returns>
SolidWorksDocument GetActiveDocument();
/// <summary>
/// Enables or disables user control of the SolidWorks application.
/// </summary>
/// <param name="enable">True to enable user control, false to disable.</param>
void EnableUserControl(bool enable);
/// <summary>
/// Gets the SolidWorks application instance.
/// </summary>
/// <returns>The SldWorks instance.</returns>
SldWorks GetNativeSldWorks();
/// <summary>
/// Closes the document with the specified title.
/// </summary>
/// <param name="documentTitle"></param>
/// <returns></returns>
bool CloseDocument(string documentTitle);
/// <summary>
/// Event raised when the active document changes.
/// </summary>
event EventHandler ActiveDocumentChanged;
}
/// <summary>
/// Service for managing SolidWorks application connection and document lifecycle.
/// </summary>
public class SolidWorksService : ISolidWorksService
{
private SldWorks _sldWorks;
private bool _disposed;
/// <summary>
/// Event raised when the active document changes in SolidWorks.
/// </summary>
public event EventHandler ActiveDocumentChanged;
/// <summary>
/// Connects to the SolidWorks application asynchronously.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when connection to SolidWorks fails.</exception>
public async Task ConnectAsync()
{
await Task.Run(() =>
{
try
{
// Try to get running instance first
_sldWorks = GetRunningInstance();
// If no running instance, create new one
if (_sldWorks == null)
{
_sldWorks = CreateNewInstance();
}
if (_sldWorks == null)
{
throw new InvalidOperationException(
"Failed to connect to SolidWorks. Please ensure SolidWorks is installed.");
}
// Make SolidWorks visible
_sldWorks.Visible = true;
// Subscribe to document change events
_sldWorks.ActiveModelDocChangeNotify += OnSolidWorksActiveDocChanged;
}
catch (COMException ex)
{
throw new InvalidOperationException(
$"COM error while connecting to SolidWorks: {ex.Message}", ex);
}
});
}
/// <summary>
/// Gets the currently active SolidWorks document.
/// </summary>
/// <returns>The active document wrapper, or null if no document is open.</returns>
public SolidWorksDocument GetActiveDocument()
{
if (_sldWorks == null)
return null;
var model = _sldWorks.ActiveDoc as ModelDoc2;
if (model == null)
return null;
return CreateDocumentWrapper(model);
}
/// <summary>
/// Enables or disables user control of the SolidWorks application.
/// When disabled, SolidWorks won't show dialogs or allow user interaction.
/// </summary>
/// <param name="enable">True to enable user control, false to disable.</param>
public void EnableUserControl(bool enable)
{
if (_sldWorks != null)
{
_sldWorks.UserControl = enable;
}
}
/// <summary>
/// Gets the native SolidWorks application instance.
/// Use this when you need direct access to the SolidWorks API.
/// </summary>
/// <returns>The SldWorks instance, or null if not connected.</returns>
public SldWorks GetNativeSldWorks()
{
return _sldWorks;
}
/// <summary>
/// Checks if SolidWorks is connected and ready.
/// </summary>
public bool IsConnected => _sldWorks != null;
/// <summary>
/// Gets the version of the connected SolidWorks instance.
/// </summary>
public string GetSolidWorksVersion()
{
if (_sldWorks == null)
return null;
try
{
return _sldWorks.RevisionNumber();
}
catch
{
return "Unknown";
}
}
/// <summary>
/// Closes a document by its title.
/// </summary>
/// <param name="documentTitle">The title of the document to close.</param>
/// <returns>True if successfully closed, false otherwise.</returns>
public bool CloseDocument(string documentTitle)
{
if (_sldWorks == null || string.IsNullOrWhiteSpace(documentTitle))
return false;
try
{
_sldWorks.CloseDoc(documentTitle);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Creates a new document from a template.
/// </summary>
/// <param name="templatePath">Path to the template file.</param>
/// <param name="paperSize">Paper size for drawings.</param>
/// <param name="width">Width dimension.</param>
/// <param name="height">Height dimension.</param>
/// <returns>The created document, or null if creation failed.</returns>
public ModelDoc2 CreateDocument(string templatePath, int paperSize, double width, double height)
{
if (_sldWorks == null)
return null;
try
{
return _sldWorks.NewDocument(templatePath, paperSize, width, height) as ModelDoc2;
}
catch
{
return null;
}
}
/// <summary>
/// Opens an existing document.
/// </summary>
/// <param name="filePath">Full path to the document.</param>
/// <param name="options">Open options.</param>
/// <param name="configuration">Configuration to open (empty for default).</param>
/// <returns>The opened document, or null if open failed.</returns>
public ModelDoc2 OpenDocument(string filePath, int options = 0, string configuration = "")
{
if (_sldWorks == null || string.IsNullOrWhiteSpace(filePath))
return null;
try
{
int errors = 0;
int warnings = 0;
var doc = _sldWorks.OpenDoc6(
filePath,
(int)GetDocumentType(filePath),
options,
configuration,
ref errors,
ref warnings);
return doc as ModelDoc2;
}
catch
{
return null;
}
}
#region Private Methods
private SldWorks GetRunningInstance()
{
try
{
return Marshal.GetActiveObject("SldWorks.Application") as SldWorks;
}
catch (COMException)
{
// No running instance
return null;
}
}
private SldWorks CreateNewInstance()
{
try
{
var type = Type.GetTypeFromProgID("SldWorks.Application");
if (type == null)
return null;
return Activator.CreateInstance(type) as SldWorks;
}
catch
{
return null;
}
}
private SolidWorksDocument CreateDocumentWrapper(ModelDoc2 model)
{
return new SolidWorksDocument
{
Title = model.GetTitle(),
FilePath = model.GetPathName(),
DocumentType = DetermineDocumentType(model),
NativeDocument = model
};
}
private DocumentType DetermineDocumentType(ModelDoc2 model)
{
if (model is PartDoc) return DocumentType.Part;
if (model is AssemblyDoc) return DocumentType.Assembly;
if (model is DrawingDoc) return DocumentType.Drawing;
return DocumentType.Unknown;
}
private swDocumentTypes_e GetDocumentType(string filePath)
{
var extension = System.IO.Path.GetExtension(filePath)?.ToLowerInvariant();
switch (extension)
{
case ".sldprt":
return swDocumentTypes_e.swDocPART;
case ".sldasm":
return swDocumentTypes_e.swDocASSEMBLY;
case ".slddrw":
return swDocumentTypes_e.swDocDRAWING;
default:
return swDocumentTypes_e.swDocNONE;
}
}
private int OnSolidWorksActiveDocChanged()
{
try
{
ActiveDocumentChanged?.Invoke(this, EventArgs.Empty);
}
catch
{
// Swallow exceptions from event handlers
}
return 1; // SolidWorks expects 1 to be returned
}
#endregion
#region IDisposable Implementation
/// <summary>
/// Releases all resources used by the SolidWorksService.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged and optionally managed resources.
/// </summary>
/// <param name="disposing">True to release both managed and unmanaged resources;
/// false to release only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
// Unsubscribe from events
if (_sldWorks != null)
{
try
{
_sldWorks.ActiveModelDocChangeNotify -= OnSolidWorksActiveDocChanged;
}
catch
{
// Ignore errors during cleanup
}
}
}
// Release COM object
if (_sldWorks != null)
{
try
{
Marshal.ReleaseComObject(_sldWorks);
}
catch
{
// Ignore errors during cleanup
}
_sldWorks = null;
}
_disposed = true;
}
public SldWorks GetSldWorks()
{
if (_sldWorks == null)
{
_sldWorks = GetRunningInstance();
return _sldWorks;
}
else
{
_sldWorks = CreateNewInstance();
return _sldWorks;
}
}
/// <summary>
/// Finalizer to ensure COM objects are released.
/// </summary>
~SolidWorksService()
{
Dispose(false);
}
#endregion
}
}

Binary file not shown.

View File

@@ -1,22 +0,0 @@
using System;
namespace ExportDXF
{
public static class Units
{
/// <summary>
/// Multiply factor needed to convert the desired units to meters.
/// </summary>
public static double ScaleFactor = 0.0254; // inches to meters
public static double ToSldWorks(this double d)
{
return Math.Round(d * ScaleFactor, 8);
}
public static double FromSldWorks(this double d)
{
return Math.Round(d / ScaleFactor, 8);
}
}
}

View File

@@ -0,0 +1,43 @@
namespace ExportDXF.Utilities
{
/// <summary>
/// Contains sheet metal properties extracted from a SolidWorks part.
/// </summary>
public class SheetMetalProperties
{
/// <summary>
/// Material thickness
/// </summary>
public double Thickness { get; set; }
/// <summary>
/// K-factor for bend calculations.
/// </summary>
public double KFactor { get; set; }
/// <summary>
/// Inside bend radius
/// </summary>
public double BendRadius { get; set; }
/// <summary>
/// Bend allowance
/// </summary>
public double BendAllowance { get; set; }
/// <summary>
/// Whether auto relief is enabled.
/// </summary>
public bool AutoRelief { get; set; }
/// <summary>
/// Relief ratio for auto relief.
/// </summary>
public double ReliefRatio { get; set; }
public override string ToString()
{
return $"Thickness: {Thickness:F2}\", K-Factor: {KFactor:F3}, Bend Radius: {BendRadius:F2}\"";
}
}
}

View File

@@ -0,0 +1,513 @@
using ExportDXF.Extensions;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ExportDXF.Utilities
{
/// <summary>
/// Utility class for SolidWorks-specific operations and helper functions.
/// </summary>
public static class SolidWorksHelper
{
#region Feature Names Constants
private const string FLAT_PATTERN_FEATURE = "FlatPattern";
private const string SHEET_METAL_FEATURE = "SheetMetal";
private const string BASE_FLANGE_FEATURE = "BaseFlange";
private const string FLAT_PATTERN_FOLDER = "Flat-Pattern";
#endregion
#region Sheet Metal Operations
/// <summary>
/// Gets sheet metal properties from the model.
/// </summary>
/// <param name="model">The model to extract properties from.</param>
/// <returns>Sheet metal properties, or null if not a sheet metal part.</returns>
public static SheetMetalProperties GetSheetMetalProperties(ModelDoc2 model)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
var sheetMetalFeature = model.GetFeatureByTypeName(SHEET_METAL_FEATURE);
if (sheetMetalFeature == null)
return null;
var sheetMetalData = sheetMetalFeature.GetDefinition() as SheetMetalFeatureData;
if (sheetMetalData == null)
return null;
return new SheetMetalProperties
{
Thickness = sheetMetalData.Thickness.FromSolidWorksToInches(),
KFactor = sheetMetalData.KFactor,
BendRadius = sheetMetalData.BendRadius.FromSolidWorksToInches(),
BendAllowance = sheetMetalData.BendAllowance.FromSolidWorksToInches(),
AutoRelief = sheetMetalData.UseAutoRelief,
ReliefRatio = sheetMetalData.ReliefRatio
};
}
/// <summary>
/// Configures flat pattern settings for optimal DXF export.
/// Unchecks corner treatment and enables simplify bends.
/// </summary>
/// <param name="model">The model containing the flat pattern.</param>
/// <returns>True if settings were successfully modified.</returns>
public static bool ConfigureFlatPatternSettings(ModelDoc2 model)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
var flatPattern = model.GetFeatureByTypeName(FLAT_PATTERN_FEATURE);
if (flatPattern == null)
return false;
var featureData = flatPattern.GetDefinition() as FlatPatternFeatureData;
if (featureData == null)
return false;
try
{
// Configure for cleaner DXF output
featureData.CornerTreatment = false; // Remove corner treatments
featureData.SimplifyBends = true; // Simplify bend representations
return flatPattern.ModifyDefinition(featureData, model, null);
}
catch
{
return false;
}
}
/// <summary>
/// Sets the suppression state of the flat pattern feature.
/// </summary>
/// <param name="model">The model containing the flat pattern.</param>
/// <param name="suppressionState">The desired suppression state.</param>
/// <returns>True if the flat pattern is suppressed after the operation.</returns>
public static bool SetFlatPatternSuppressionState(
ModelDoc2 model,
swComponentSuppressionState_e suppressionState)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
var flatPattern = model.GetFeatureByTypeName(FLAT_PATTERN_FEATURE);
if (flatPattern == null)
return false;
try
{
flatPattern.SetSuppression((int)suppressionState);
return flatPattern.IsSuppressed();
}
catch
{
return false;
}
}
/// <summary>
/// Gets the flat pattern feature from a model.
/// </summary>
/// <param name="model">The model to search.</param>
/// <returns>The flat pattern feature, or null if not found.</returns>
public static Feature GetFlatPatternFeature(ModelDoc2 model)
{
return model?.GetFeatureByTypeName(FLAT_PATTERN_FEATURE);
}
/// <summary>
/// Checks if a model has a flat pattern feature.
/// </summary>
/// <param name="model">The model to check.</param>
/// <returns>True if the model has a flat pattern feature.</returns>
public static bool HasFlatPattern(ModelDoc2 model)
{
return GetFlatPatternFeature(model) != null;
}
#endregion
#region Feature Operations
/// <summary>
/// Gets all features of a specific type from a model.
/// </summary>
/// <param name="model">The model to search.</param>
/// <param name="featureTypeName">The name of the feature type to find.</param>
/// <returns>A list of features of the specified type.</returns>
public static List<Feature> GetFeaturesByTypeName(ModelDoc2 model, string featureTypeName)
{
if (model == null || string.IsNullOrEmpty(featureTypeName))
return new List<Feature>();
var features = new List<Feature>();
var feature = model.FirstFeature() as Feature;
while (feature != null)
{
if (string.Equals(feature.GetTypeName2(), featureTypeName, StringComparison.OrdinalIgnoreCase))
{
features.Add(feature);
}
// Check sub-features
var subFeature = feature.GetFirstSubFeature() as Feature;
while (subFeature != null)
{
if (string.Equals(subFeature.GetTypeName2(), featureTypeName, StringComparison.OrdinalIgnoreCase))
{
features.Add(subFeature);
}
subFeature = subFeature.GetNextSubFeature() as Feature;
}
feature = feature.GetNextFeature() as Feature;
}
return features;
}
/// <summary>
/// Suppresses or unsuppresses a feature by name.
/// </summary>
/// <param name="model">The model containing the feature.</param>
/// <param name="featureName">The name of the feature to suppress/unsuppress.</param>
/// <param name="suppress">True to suppress, false to unsuppress.</param>
/// <returns>True if the operation was successful.</returns>
public static bool SetFeatureSuppressionByName(ModelDoc2 model, string featureName, bool suppress)
{
if (model == null || string.IsNullOrEmpty(featureName))
return false;
try
{
// Use Extension.SelectByID2 to find and select the feature
var modelExtension = model.Extension;
bool selected = modelExtension.SelectByID2(featureName, "BODYFEATURE", 0, 0, 0, false, 0, null, 0);
if (!selected)
return false;
var selectionManager = model.SelectionManager as SelectionMgr;
var feature = selectionManager.GetSelectedObject6(1, -1) as Feature;
if (feature == null)
return false;
var suppressionState = suppress
? swFeatureSuppressionAction_e.swSuppressFeature
: swFeatureSuppressionAction_e.swUnSuppressFeature;
feature.SetSuppression((int)suppressionState);
// Clear the selection
model.ClearSelection2(true);
return true;
}
catch
{
return false;
}
}
#endregion
#region Configuration Operations
/// <summary>
/// Gets all configuration names from a model.
/// </summary>
/// <param name="model">The model to query.</param>
/// <returns>A list of configuration names.</returns>
public static List<string> GetConfigurationNames(ModelDoc2 model)
{
if (model == null)
return new List<string>();
var configNames = (string[])model.GetConfigurationNames();
return configNames?.ToList() ?? new List<string>();
}
/// <summary>
/// Activates a specific configuration.
/// </summary>
/// <param name="model">The model to modify.</param>
/// <param name="configurationName">The name of the configuration to activate.</param>
/// <returns>True if the configuration was successfully activated.</returns>
public static bool ActivateConfiguration(ModelDoc2 model, string configurationName)
{
if (model == null || string.IsNullOrEmpty(configurationName))
return false;
try
{
return model.ShowConfiguration2(configurationName);
}
catch
{
return false;
}
}
/// <summary>
/// Gets the active configuration name.
/// </summary>
/// <param name="model">The model to query.</param>
/// <returns>The name of the active configuration, or null if unavailable.</returns>
public static string GetActiveConfigurationName(ModelDoc2 model)
{
if (model == null)
return null;
try
{
var config = model.GetActiveConfiguration() as Configuration;
return config?.Name;
}
catch
{
return null;
}
}
#endregion
#region Custom Properties
/// <summary>
/// Gets a custom property value from a model.
/// </summary>
/// <param name="model">The model to query.</param>
/// <param name="propertyName">The name of the property.</param>
/// <param name="configurationName">The configuration name (empty string for file-level properties).</param>
/// <returns>The property value, or null if not found.</returns>
public static string GetCustomProperty(ModelDoc2 model, string propertyName, string configurationName = "")
{
if (model == null || string.IsNullOrEmpty(propertyName))
return null;
try
{
var customPropertyManager = model.Extension.CustomPropertyManager[configurationName ?? ""];
return customPropertyManager.Get(propertyName);
}
catch
{
return null;
}
}
/// <summary>
/// Sets a custom property value on a model.
/// </summary>
/// <param name="model">The model to modify.</param>
/// <param name="propertyName">The name of the property.</param>
/// <param name="propertyValue">The value to set.</param>
/// <param name="configurationName">The configuration name (empty string for file-level properties).</param>
/// <returns>True if the property was successfully set.</returns>
public static bool SetCustomProperty(ModelDoc2 model, string propertyName, string propertyValue, string configurationName = "")
{
if (model == null || string.IsNullOrEmpty(propertyName))
return false;
try
{
var customPropertyManager = model.Extension.CustomPropertyManager[configurationName ?? ""];
var result = customPropertyManager.Add3(propertyName, (int)swCustomInfoType_e.swCustomInfoText, propertyValue, (int)swCustomPropertyAddOption_e.swCustomPropertyReplaceValue);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Gets all custom property names from a model.
/// </summary>
/// <param name="model">The model to query.</param>
/// <param name="configurationName">The configuration name (empty string for file-level properties).</param>
/// <returns>A list of custom property names.</returns>
public static List<string> GetCustomPropertyNames(ModelDoc2 model, string configurationName = "")
{
if (model == null)
return new List<string>();
try
{
var customPropertyManager = model.Extension.CustomPropertyManager[configurationName ?? ""];
var propertyNames = (string[])customPropertyManager.GetNames();
return propertyNames?.ToList() ?? new List<string>();
}
catch
{
return new List<string>();
}
}
#endregion
#region Material Operations
/// <summary>
/// Gets the material name for a part document.
/// </summary>
/// <param name="part">The part document.</param>
/// <param name="configurationName">The configuration name.</param>
/// <returns>The material name, or null if not assigned.</returns>
public static string GetMaterial(PartDoc part, string configurationName)
{
if (part == null)
return null;
try
{
return part.GetMaterialPropertyName2(configurationName, out _);
}
catch
{
return null;
}
}
/// <summary>
/// Sets the material for a part document.
/// </summary>
/// <param name="part">The part document.</param>
/// <param name="materialName">The name of the material to assign.</param>
/// <param name="databasePath">The path to the material database.</param>
/// <returns>True if the material was successfully assigned.</returns>
public static bool SetMaterial(PartDoc part, string materialName, string databasePath)
{
if (part == null || string.IsNullOrEmpty(materialName))
return false;
try
{
part.SetMaterialPropertyName2("", databasePath, materialName);
return true;
}
catch
{
return false;
}
}
#endregion
#region Document Operations
/// <summary>
/// Gets the file path of a model document.
/// </summary>
/// <param name="model">The model document.</param>
/// <returns>The full file path, or empty string if not saved.</returns>
public static string GetFilePath(ModelDoc2 model)
{
return model?.GetPathName() ?? string.Empty;
}
/// <summary>
/// Gets the document title (filename without path).
/// </summary>
/// <param name="model">The model document.</param>
/// <returns>The document title.</returns>
public static string GetTitle(ModelDoc2 model)
{
return model?.GetTitle() ?? string.Empty;
}
/// <summary>
/// Checks if a document has been saved.
/// </summary>
/// <param name="model">The model document.</param>
/// <returns>True if the document has been saved to disk.</returns>
public static bool IsSaved(ModelDoc2 model)
{
return !string.IsNullOrEmpty(GetFilePath(model));
}
/// <summary>
/// Saves a document.
/// </summary>
/// <param name="model">The model document to save.</param>
/// <returns>True if the save operation was successful.</returns>
public static bool Save(ModelDoc2 model)
{
if (model == null)
return false;
try
{
int errors = 0;
int warnings = 0;
return model.Save3((int)swSaveAsOptions_e.swSaveAsOptions_Silent, ref errors, ref warnings);
}
catch
{
return false;
}
}
#endregion
#region Component Operations
/// <summary>
/// Resolves a lightweight component to fully loaded.
/// </summary>
/// <param name="component">The component to resolve.</param>
/// <returns>True if the component was successfully resolved.</returns>
public static bool ResolveComponent(Component2 component)
{
if (component == null)
return false;
try
{
component.SetLightweightToResolved();
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Checks if a component is suppressed.
/// </summary>
/// <param name="component">The component to check.</param>
/// <returns>True if the component is suppressed.</returns>
public static bool IsComponentSuppressed(Component2 component)
{
return component?.IsSuppressed() ?? false;
}
/// <summary>
/// Gets the model document from a component.
/// </summary>
/// <param name="component">The component.</param>
/// <returns>The model document, or null if unavailable.</returns>
public static ModelDoc2 GetModelFromComponent(Component2 component)
{
return component?.GetModelDoc2() as ModelDoc2;
}
#endregion
}
}

View File

@@ -0,0 +1,274 @@
using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
namespace ExportDXF.Utilities
{
/// <summary>
/// Utility class for text processing and string manipulation operations.
/// </summary>
public static class TextHelper
{
private static readonly Regex XmlTagRegex = new Regex(@"<[^>]+>", RegexOptions.Compiled);
private static readonly Regex WhitespaceRegex = new Regex(@"\s+", RegexOptions.Compiled);
private static readonly Regex FontTagRegex = new Regex(@"<FONT.*?>", RegexOptions.Compiled | RegexOptions.IgnoreCase);
/// <summary>
/// Removes all XML tags from the input string.
/// </summary>
/// <param name="input">The string containing XML tags to remove.</param>
/// <returns>The string with all XML tags removed, or the original input if null/empty.</returns>
public static string RemoveXmlTags(string input)
{
if (string.IsNullOrEmpty(input))
return input;
return XmlTagRegex.Replace(input, string.Empty);
}
/// <summary>
/// Removes specific SolidWorks font XML tags from the input string.
/// This is more targeted than RemoveXmlTags and handles SolidWorks-specific formatting.
/// </summary>
/// <param name="input">The string containing font tags to remove.</param>
/// <returns>The string with font tags removed.</returns>
public static string RemoveFontXmlTags(string input)
{
if (string.IsNullOrEmpty(input))
return input;
var result = input;
var matches = FontTagRegex.Matches(result);
// Process matches in reverse order to maintain indices
for (int i = matches.Count - 1; i >= 0; i--)
{
var match = matches[i];
result = result.Remove(match.Index, match.Length);
}
return result;
}
/// <summary>
/// Normalizes whitespace in a string by replacing multiple consecutive whitespace characters with a single space.
/// </summary>
/// <param name="input">The string to normalize.</param>
/// <returns>The string with normalized whitespace.</returns>
public static string NormalizeWhitespace(string input)
{
if (string.IsNullOrEmpty(input))
return input;
return WhitespaceRegex.Replace(input.Trim(), " ");
}
/// <summary>
/// Cleans text by removing XML tags and normalizing whitespace.
/// This is a common operation for processing text from SolidWorks.
/// </summary>
/// <param name="input">The text to clean.</param>
/// <returns>Cleaned text with XML tags removed and whitespace normalized.</returns>
public static string CleanText(string input)
{
if (string.IsNullOrEmpty(input))
return input;
var cleaned = RemoveXmlTags(input);
return NormalizeWhitespace(cleaned);
}
/// <summary>
/// Returns a number with its ordinal suffix (1st, 2nd, 3rd, 4th, etc.).
/// </summary>
/// <param name="number">The number to format.</param>
/// <returns>The number with appropriate ordinal suffix.</returns>
public static string GetOrdinalSuffix(int number)
{
if (number <= 0)
return number.ToString();
// Special cases for 11th, 12th, 13th
if (number >= 11 && number <= 13)
return number + "th";
return number + GetSuffix(number % 10);
}
/// <summary>
/// Converts a string to title case (first letter of each word capitalized).
/// </summary>
/// <param name="input">The string to convert.</param>
/// <returns>The string in title case.</returns>
public static string ToTitleCase(string input)
{
if (string.IsNullOrEmpty(input))
return input;
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(input.ToLowerInvariant());
}
/// <summary>
/// Truncates a string to the specified maximum length, optionally adding an ellipsis.
/// </summary>
/// <param name="input">The string to truncate.</param>
/// <param name="maxLength">The maximum length of the result.</param>
/// <param name="useEllipsis">Whether to add "..." when truncating.</param>
/// <returns>The truncated string.</returns>
public static string Truncate(string input, int maxLength, bool useEllipsis = true)
{
if (string.IsNullOrEmpty(input))
return input;
if (input.Length <= maxLength)
return input;
if (useEllipsis && maxLength > 3)
{
return input.Substring(0, maxLength - 3) + "...";
}
return input.Substring(0, maxLength);
}
/// <summary>
/// Removes invalid filename characters from a string, replacing them with underscores.
/// </summary>
/// <param name="filename">The filename to sanitize.</param>
/// <returns>A safe filename with invalid characters replaced.</returns>
public static string SanitizeFileName(string filename)
{
if (string.IsNullOrEmpty(filename))
return filename;
var invalidChars = System.IO.Path.GetInvalidFileNameChars();
var sb = new StringBuilder(filename);
foreach (var invalidChar in invalidChars)
{
sb.Replace(invalidChar, '_');
}
// Also replace some additional problematic characters
sb.Replace(' ', '_'); // Spaces can be problematic
sb.Replace('"', '\''); // Double quotes to single quotes
return sb.ToString();
}
/// <summary>
/// Checks if a string is null, empty, or contains only whitespace.
/// </summary>
/// <param name="input">The string to check.</param>
/// <returns>True if the string is null, empty, or whitespace only.</returns>
public static bool IsNullOrWhiteSpace(string input)
{
return string.IsNullOrWhiteSpace(input);
}
/// <summary>
/// Safely gets a substring without throwing exceptions for invalid indices.
/// </summary>
/// <param name="input">The source string.</param>
/// <param name="startIndex">The starting index.</param>
/// <param name="length">The length of the substring.</param>
/// <returns>The substring, or empty string if indices are invalid.</returns>
public static string SafeSubstring(string input, int startIndex, int length)
{
if (string.IsNullOrEmpty(input) || startIndex < 0 || startIndex >= input.Length)
return string.Empty;
var actualLength = Math.Min(length, input.Length - startIndex);
return actualLength <= 0 ? string.Empty : input.Substring(startIndex, actualLength);
}
/// <summary>
/// Safely gets a substring from the start index to the end of the string.
/// </summary>
/// <param name="input">The source string.</param>
/// <param name="startIndex">The starting index.</param>
/// <returns>The substring from start index to end, or empty if invalid.</returns>
public static string SafeSubstring(string input, int startIndex)
{
if (string.IsNullOrEmpty(input) || startIndex < 0 || startIndex >= input.Length)
return string.Empty;
return input.Substring(startIndex);
}
/// <summary>
/// Pads a string to a specific length, truncating if too long.
/// </summary>
/// <param name="input">The string to pad or truncate.</param>
/// <param name="totalLength">The desired total length.</param>
/// <param name="paddingChar">The character to use for padding.</param>
/// <param name="padLeft">True to pad on the left, false to pad on the right.</param>
/// <returns>A string of exactly the specified length.</returns>
public static string PadOrTruncate(string input, int totalLength, char paddingChar = ' ', bool padLeft = false)
{
if (string.IsNullOrEmpty(input))
input = string.Empty;
if (input.Length == totalLength)
return input;
if (input.Length > totalLength)
return input.Substring(0, totalLength);
return padLeft
? input.PadLeft(totalLength, paddingChar)
: input.PadRight(totalLength, paddingChar);
}
/// <summary>
/// Converts a string to a safe identifier (letters, numbers, underscores only).
/// </summary>
/// <param name="input">The input string.</param>
/// <returns>A safe identifier string.</returns>
public static string ToSafeIdentifier(string input)
{
if (string.IsNullOrEmpty(input))
return "Identifier";
var sb = new StringBuilder();
foreach (char c in input)
{
if (char.IsLetterOrDigit(c))
{
sb.Append(c);
}
else if (c == ' ' || c == '-' || c == '.')
{
sb.Append('_');
}
}
var result = sb.ToString();
// Ensure it starts with a letter or underscore
if (result.Length > 0 && char.IsDigit(result[0]))
{
result = "_" + result;
}
return string.IsNullOrEmpty(result) ? "Identifier" : result;
}
#region Private Helper Methods
private static string GetSuffix(int lastDigit)
{
switch (lastDigit)
{
case 1: return "st";
case 2: return "nd";
case 3: return "rd";
default: return "th";
}
}
#endregion
}
}

View File

@@ -1,10 +1,12 @@
using SolidWorks.Interop.sldworks; using ExportDXF.Extensions;
using SolidWorks.Interop.sldworks;
using SolidWorks.Interop.swconst;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace ExportDXF namespace ExportDXF.Utilities
{ {
internal static class ViewHelper internal static class ViewHelper
{ {
@@ -269,5 +271,58 @@ namespace ExportDXF
{ {
return Math.Round(angleInRadians * 180.0 / Math.PI, 8); return Math.Round(angleInRadians * 180.0 / Math.PI, 8);
} }
public static bool HideModelSketches(IView view)
{
var model = view.ReferencedDocument;
var activeConfig = ((Configuration)model.GetActiveConfiguration()).Name;
var modelChanged = false;
var refConfig = view.ReferencedConfiguration;
model.ShowConfiguration(refConfig);
var sketches = model.GetAllFeaturesByTypeName("ProfileFeature");
foreach (var sketch in sketches)
{
var visible = (swVisibilityState_e)sketch.Visible;
if (visible == swVisibilityState_e.swVisibilityStateShown)
{
sketch.Select2(true, -1);
model.BlankSketch();
modelChanged = true;
}
}
model.ShowConfiguration(activeConfig);
return modelChanged;
}
public static bool HasSupressedBends(IView view)
{
var model = GetModelFromView(view);
var flatPattern = model.GetFeatureByTypeName("FlatPattern");
var bends = flatPattern.GetAllSubFeaturesByTypeName("UiBend");
foreach (var bend in bends)
{
var isSuppressed = bend.IsSuppressed();
if (isSuppressed)
return true;
}
return false;
}
public static ModelDoc2 GetModelFromView(IView view)
{
var model = view.ReferencedDocument;
var refConfig = view.ReferencedConfiguration;
model.ShowConfiguration(refConfig);
return model;
}
} }
} }

View File

@@ -0,0 +1,25 @@
using ExportDXF.Utilities;
using System.Windows.Forms;
namespace ExportDXF.ViewFlipDeciders
{
public class AskViewFlipDecider : IViewFlipDecider
{
public string Name => "Ask to flip";
public bool ShouldFlip(SolidWorks.Interop.sldworks.View view)
{
var bends = ViewHelper.GetBends(view);
if (bends.Count == 0)
return false;
return MessageBox.Show("Flip view?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes;
}
public override string ToString()
{
return Name;
}
}
}

View File

@@ -0,0 +1,30 @@
using ExportDXF.Utilities;
using System.Linq;
namespace ExportDXF.ViewFlipDeciders
{
public class AutoViewFlipDecider : IViewFlipDecider
{
public string Name => "Automatic";
public bool ShouldFlip(SolidWorks.Interop.sldworks.View view)
{
var orientation = ViewHelper.GetOrientation(view);
var bounds = ViewHelper.GetBounds(view);
var bends = ViewHelper.GetBends(view);
var up = bends.Where(b => b.Direction == BendDirection.Up).ToList();
var down = bends.Where(b => b.Direction == BendDirection.Down).ToList();
if (down.Count == 0)
return false;
if (up.Count == 0)
return true;
var bend = ViewHelper.GetBendClosestToBounds(bounds, bends);
return bend.Direction == BendDirection.Down;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace ExportDXF.ViewFlipDeciders
{
public interface IViewFlipDecider
{
bool ShouldFlip(SolidWorks.Interop.sldworks.View view);
string Name { get; }
}
}

View File

@@ -0,0 +1,27 @@
using ExportDXF.Utilities;
using System.Linq;
using System.Windows.Forms;
namespace ExportDXF.ViewFlipDeciders
{
public class PreferUpViewFlipDecider : IViewFlipDecider
{
public string Name => "Prefer up bends, ask if up/down";
public bool ShouldFlip(SolidWorks.Interop.sldworks.View view)
{
var bends = ViewHelper.GetBends(view);
var up = bends.Where(b => b.Direction == BendDirection.Up).ToList();
var down = bends.Where(b => b.Direction == BendDirection.Down).ToList();
if (up.Count > 0 && down.Count > 0)
{
return MessageBox.Show("Flip view?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes;
}
else
{
return down.Count > 0;
}
}
}
}

View File

@@ -0,0 +1,147 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace ExportDXF.ViewFlipDeciders
{
/// <summary>
/// Factory for discovering and creating IViewFlipDecider implementations.
/// </summary>
public static class ViewFlipDeciderFactory
{
private static readonly object _lock = new object();
private static List<IViewFlipDecider> _cachedDeciders;
/// <summary>
/// Gets all available IViewFlipDecider implementations from loaded assemblies.
/// Results are cached for performance.
/// </summary>
/// <returns>An enumerable collection of IViewFlipDecider instances.</returns>
public static IEnumerable<IViewFlipDecider> GetAvailableDeciders()
{
if (_cachedDeciders != null)
return _cachedDeciders;
lock (_lock)
{
if (_cachedDeciders != null)
return _cachedDeciders;
_cachedDeciders = DiscoverDeciders().ToList();
return _cachedDeciders;
}
}
/// <summary>
/// Gets a specific view flip decider by name.
/// </summary>
/// <param name="name">The name of the decider to retrieve.</param>
/// <returns>The matching decider, or null if not found.</returns>
public static IViewFlipDecider GetDeciderByName(string name)
{
if (string.IsNullOrWhiteSpace(name))
return null;
return GetAvailableDeciders()
.FirstOrDefault(d => string.Equals(d.Name, name, StringComparison.OrdinalIgnoreCase));
}
/// <summary>
/// Gets the default (Automatic) view flip decider, or the first available if Automatic doesn't exist.
/// </summary>
/// <returns>The default decider.</returns>
public static IViewFlipDecider GetDefaultDecider()
{
var automatic = GetDeciderByName("Automatic");
if (automatic != null)
return automatic;
return GetAvailableDeciders().FirstOrDefault();
}
/// <summary>
/// Clears the cached deciders, forcing rediscovery on next call.
/// Useful for testing or if assemblies are loaded dynamically.
/// </summary>
public static void ClearCache()
{
lock (_lock)
{
_cachedDeciders = null;
}
}
/// <summary>
/// Discovers all IViewFlipDecider implementations in loaded assemblies.
/// </summary>
private static IEnumerable<IViewFlipDecider> DiscoverDeciders()
{
var deciderType = typeof(IViewFlipDecider);
var discoveredTypes = new List<Type>();
try
{
// Search all loaded assemblies
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
try
{
var types = assembly.GetTypes()
.Where(t => deciderType.IsAssignableFrom(t) &&
t.IsClass &&
!t.IsAbstract &&
HasParameterlessConstructor(t));
discoveredTypes.AddRange(types);
}
catch (ReflectionTypeLoadException)
{
// Skip assemblies that can't be loaded
continue;
}
}
}
catch (Exception)
{
// If discovery fails entirely, return empty collection
yield break;
}
// Create instances of discovered types
foreach (var type in discoveredTypes)
{
IViewFlipDecider instance = null;
try
{
instance = (IViewFlipDecider)Activator.CreateInstance(type);
}
catch (Exception)
{
// Skip types that can't be instantiated
continue;
}
if (instance != null)
{
yield return instance;
}
}
}
/// <summary>
/// Checks if a type has a public parameterless constructor.
/// </summary>
private static bool HasParameterlessConstructor(Type type)
{
return type.GetConstructor(
BindingFlags.Public | BindingFlags.Instance,
null,
Type.EmptyTypes,
null) != null;
}
}
}

View File

@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8"/>
</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.