Compare commits
54 Commits
6b1a5f0ab6
...
feature/fa
| Author | SHA1 | Date | |
|---|---|---|---|
| bd3e7c2a36 | |||
| b9e84de7c0 | |||
| f6cd91f1b5 | |||
| 3554bb6110 | |||
| 4707e96359 | |||
| c5bd7fb4c8 | |||
| 13c61a82a4 | |||
| 444a077cbc | |||
| c4920f933d | |||
| b472729fda | |||
| 5d2948d563 | |||
| 71c65e0bf5 | |||
| 53aa23f762 | |||
| 036ab2a55a | |||
| f9e7ace35d | |||
| 622cbf1170 | |||
| 4a3f33db33 | |||
| 77d0157370 | |||
| 26e9233b30 | |||
| e59584a5c0 | |||
| dcc508d479 | |||
| 1266378b51 | |||
| 5de40ebafd | |||
| e072919a59 | |||
| 7db44640ca | |||
| 0d5742124e | |||
| 463916c75c | |||
| c06d834e05 | |||
| d3c154b875 | |||
| 2721c33a39 | |||
| 5ec66f9039 | |||
| cf76ca8bb1 | |||
| 696bf2f72c | |||
| 8de441e126 | |||
| 8b6950ef28 | |||
| dba68ecc71 | |||
| f75b83d483 | |||
| 2273a83e42 | |||
| e10a7ed0ed | |||
| 16dc74c35d | |||
| 9e5e44c1ed | |||
| ab76fa61c9 | |||
| 28c9f715be | |||
| 2bef75f548 | |||
| 78a8a2197d | |||
| 719dca1ca5 | |||
| a17d8cac49 | |||
| 32e8379e9b | |||
| 0ace378eff | |||
| 697463f61e | |||
| 4eb13a1aca | |||
| 49051b5e64 | |||
| 384fceb047 | |||
| c4926c6e9f |
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git add:*)",
|
||||
"Bash(git commit:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -245,3 +245,6 @@ ModelManifest.xml
|
||||
|
||||
# Test documents
|
||||
TestDocs/
|
||||
|
||||
# Claude Code local settings
|
||||
.claude/
|
||||
|
||||
Submodule EtchBendLines updated: 89d987f6c6...da4d3228b0
@@ -1,32 +1,88 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.29123.88
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.3.11512.155 d18.3
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExportDXF", "ExportDXF\ExportDXF.csproj", "{05F21D73-FD31-4E77-8D9B-41C86D4D8305}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EtchBendLines", "EtchBendLines\EtchBendLines\EtchBendLines.csproj", "{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "netDxf", "EtchBendLines\netDxf\netDxf\netDxf.csproj", "{785380E0-CEB9-4C34-82E5-60D0E33E848E}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FabWorks.Core", "FabWorks.Core\FabWorks.Core.csproj", "{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FabWorks.Tests", "FabWorks.Tests\FabWorks.Tests.csproj", "{6DD89774-D86B-47E9-B982-2794BD95616A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FabWorks.Api", "FabWorks.Api\FabWorks.Api.csproj", "{9BD571FA-52D8-430D-8843-FEB6EABD421C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x64.Build.0 = Release|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{05F21D73-FD31-4E77-8D9B-41C86D4D8305}.Release|x86.Build.0 = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{785380E0-CEB9-4C34-82E5-60D0E33E848E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x64.Build.0 = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{229C2FB9-6AD6-4A5D-B83A-D1146573D6F9}.Release|x86.Build.0 = Release|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Release|x64.Build.0 = Release|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{24547EE4-2EAA-4A6C-AD94-1117C038D8CD}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6DD89774-D86B-47E9-B982-2794BD95616A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9BD571FA-52D8-430D-8843-FEB6EABD421C}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
157
ExportDXF/ApiClient/FabWorksApiClient.cs
Normal file
157
ExportDXF/ApiClient/FabWorksApiClient.cs
Normal file
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExportDXF.ApiClient
|
||||
{
|
||||
public class FabWorksApiClient : IFabWorksApiClient
|
||||
{
|
||||
private readonly HttpClient _http;
|
||||
|
||||
public FabWorksApiClient(HttpClient httpClient)
|
||||
{
|
||||
_http = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
|
||||
}
|
||||
|
||||
public async Task<ApiExportDetail> CreateExportAsync(string drawingNumber, string equipmentNo, string drawingNo, string sourceFilePath, string outputFolder, string title = null)
|
||||
{
|
||||
var request = new ApiCreateExportRequest
|
||||
{
|
||||
DrawingNumber = drawingNumber,
|
||||
Title = title,
|
||||
EquipmentNo = equipmentNo,
|
||||
DrawingNo = drawingNo,
|
||||
SourceFilePath = sourceFilePath,
|
||||
OutputFolder = outputFolder
|
||||
};
|
||||
|
||||
var response = await _http.PostAsJsonAsync("api/exports", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<ApiExportDetail>();
|
||||
}
|
||||
|
||||
public async Task<ApiExportDetail> GetExportBySourceFileAsync(string filePath)
|
||||
{
|
||||
var response = await _http.GetAsync($"api/exports/by-source?path={Uri.EscapeDataString(filePath)}");
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<ApiExportDetail>();
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetDrawingNumbersAsync()
|
||||
{
|
||||
var response = await _http.GetAsync("api/exports/drawing-numbers");
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<List<string>>();
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetEquipmentNumbersAsync()
|
||||
{
|
||||
var response = await _http.GetAsync("api/exports/equipment-numbers");
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<List<string>>();
|
||||
}
|
||||
|
||||
public async Task<List<string>> GetDrawingNumbersByEquipmentAsync(string equipmentNo = null)
|
||||
{
|
||||
var url = "api/exports/drawing-numbers-by-equipment";
|
||||
if (!string.IsNullOrEmpty(equipmentNo))
|
||||
url += $"?equipmentNo={Uri.EscapeDataString(equipmentNo)}";
|
||||
|
||||
var response = await _http.GetAsync(url);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<List<string>>();
|
||||
}
|
||||
|
||||
public async Task<string> GetNextItemNumberAsync(string drawingNumber)
|
||||
{
|
||||
var response = await _http.GetAsync($"api/exports/next-item-number?drawingNumber={Uri.EscapeDataString(drawingNumber)}");
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
public async Task UpdatePdfHashAsync(int exportId, string pdfContentHash)
|
||||
{
|
||||
var request = new ApiUpdatePdfHashRequest { PdfContentHash = pdfContentHash };
|
||||
var response = await _http.PatchAsJsonAsync($"api/exports/{exportId}/pdf-hash", request);
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
public async Task<string> GetPreviousPdfHashAsync(string drawingNumber, int? excludeId = null)
|
||||
{
|
||||
var url = $"api/exports/previous-pdf-hash?drawingNumber={Uri.EscapeDataString(drawingNumber)}";
|
||||
if (excludeId.HasValue)
|
||||
url += $"&excludeId={excludeId.Value}";
|
||||
|
||||
var response = await _http.GetAsync(url);
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
public async Task<ApiBomItem> FindExistingBomItemAsync(int exportId, string partName, string configurationName)
|
||||
{
|
||||
var url = $"api/exports/{exportId}/bom-items/find?partName={Uri.EscapeDataString(partName ?? "")}&configurationName={Uri.EscapeDataString(configurationName ?? "")}";
|
||||
var response = await _http.GetAsync(url);
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<ApiBomItem>();
|
||||
}
|
||||
|
||||
public async Task<ApiBomItem> CreateBomItemAsync(int exportId, ApiBomItem bomItem)
|
||||
{
|
||||
var response = await _http.PostAsJsonAsync($"api/exports/{exportId}/bom-items", bomItem);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<ApiBomItem>();
|
||||
}
|
||||
|
||||
public async Task<ApiCutTemplate> GetPreviousCutTemplateAsync(string drawingNumber, string itemNo)
|
||||
{
|
||||
var response = await _http.GetAsync($"api/exports/previous-cut-template?drawingNumber={Uri.EscapeDataString(drawingNumber)}&itemNo={Uri.EscapeDataString(itemNo)}");
|
||||
if (response.StatusCode == HttpStatusCode.NotFound)
|
||||
return null;
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<ApiCutTemplate>();
|
||||
}
|
||||
|
||||
public async Task<ApiFileUploadResponse> UploadDxfAsync(string localFilePath, string equipment, string drawingNo, string itemNo, string contentHash)
|
||||
{
|
||||
using var content = new MultipartFormDataContent();
|
||||
using var fileStream = new FileStream(localFilePath, FileMode.Open, FileAccess.Read);
|
||||
var fileContent = new StreamContent(fileStream);
|
||||
content.Add(fileContent, "file", Path.GetFileName(localFilePath));
|
||||
content.Add(new StringContent(equipment ?? ""), "equipment");
|
||||
content.Add(new StringContent(drawingNo ?? ""), "drawingNo");
|
||||
content.Add(new StringContent(itemNo ?? ""), "itemNo");
|
||||
content.Add(new StringContent(contentHash ?? ""), "contentHash");
|
||||
|
||||
var response = await _http.PostAsync("api/files/dxf", content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<ApiFileUploadResponse>();
|
||||
}
|
||||
|
||||
public async Task<ApiFileUploadResponse> UploadPdfAsync(string localFilePath, string equipment, string drawingNo, string contentHash, int? exportRecordId = null)
|
||||
{
|
||||
using var content = new MultipartFormDataContent();
|
||||
using var fileStream = new FileStream(localFilePath, FileMode.Open, FileAccess.Read);
|
||||
var fileContent = new StreamContent(fileStream);
|
||||
content.Add(fileContent, "file", Path.GetFileName(localFilePath));
|
||||
content.Add(new StringContent(equipment ?? ""), "equipment");
|
||||
content.Add(new StringContent(drawingNo ?? ""), "drawingNo");
|
||||
content.Add(new StringContent(contentHash ?? ""), "contentHash");
|
||||
if (exportRecordId.HasValue)
|
||||
content.Add(new StringContent(exportRecordId.Value.ToString()), "exportRecordId");
|
||||
|
||||
var response = await _http.PostAsync("api/files/pdf", content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
return await response.Content.ReadFromJsonAsync<ApiFileUploadResponse>();
|
||||
}
|
||||
}
|
||||
}
|
||||
86
ExportDXF/ApiClient/FabWorksApiDtos.cs
Normal file
86
ExportDXF/ApiClient/FabWorksApiDtos.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ExportDXF.ApiClient
|
||||
{
|
||||
public class ApiExportDetail
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DrawingNumber { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string EquipmentNo { get; set; }
|
||||
public string DrawingNo { get; set; }
|
||||
public string SourceFilePath { get; set; }
|
||||
public string OutputFolder { get; set; }
|
||||
public DateTime ExportedAt { get; set; }
|
||||
public string ExportedBy { get; set; }
|
||||
public string PdfContentHash { get; set; }
|
||||
public List<ApiBomItem> BomItems { get; set; } = new();
|
||||
}
|
||||
|
||||
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 ApiCutTemplate CutTemplate { get; set; }
|
||||
public ApiFormProgram FormProgram { get; set; }
|
||||
}
|
||||
|
||||
public class ApiCutTemplate
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DxfFilePath { get; set; }
|
||||
public string ContentHash { get; set; }
|
||||
public int Revision { get; set; }
|
||||
public double? Thickness { get; set; }
|
||||
public double? KFactor { get; set; }
|
||||
public double? DefaultBendRadius { get; set; }
|
||||
}
|
||||
|
||||
public class ApiFormProgram
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string ProgramFilePath { get; set; }
|
||||
public string ContentHash { get; set; }
|
||||
public string ProgramName { get; set; }
|
||||
public double? Thickness { get; set; }
|
||||
public string MaterialType { get; set; }
|
||||
public double? KFactor { get; set; }
|
||||
public int BendCount { get; set; }
|
||||
public string UpperToolNames { get; set; }
|
||||
public string LowerToolNames { get; set; }
|
||||
public string SetupNotes { get; set; }
|
||||
}
|
||||
|
||||
public class ApiCreateExportRequest
|
||||
{
|
||||
public string DrawingNumber { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string EquipmentNo { get; set; }
|
||||
public string DrawingNo { get; set; }
|
||||
public string SourceFilePath { get; set; }
|
||||
public string OutputFolder { get; set; }
|
||||
}
|
||||
|
||||
public class ApiUpdatePdfHashRequest
|
||||
{
|
||||
public string PdfContentHash { get; set; }
|
||||
}
|
||||
|
||||
public class ApiFileUploadResponse
|
||||
{
|
||||
public string StoredFilePath { get; set; }
|
||||
public string ContentHash { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public bool WasUnchanged { get; set; }
|
||||
public bool IsNewFile { get; set; }
|
||||
}
|
||||
}
|
||||
22
ExportDXF/ApiClient/IFabWorksApiClient.cs
Normal file
22
ExportDXF/ApiClient/IFabWorksApiClient.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExportDXF.ApiClient
|
||||
{
|
||||
public interface IFabWorksApiClient
|
||||
{
|
||||
Task<ApiExportDetail> CreateExportAsync(string drawingNumber, string equipmentNo, string drawingNo, string sourceFilePath, string outputFolder, string title = null);
|
||||
Task<ApiExportDetail> GetExportBySourceFileAsync(string filePath);
|
||||
Task<List<string>> GetDrawingNumbersAsync();
|
||||
Task<List<string>> GetEquipmentNumbersAsync();
|
||||
Task<List<string>> GetDrawingNumbersByEquipmentAsync(string equipmentNo = null);
|
||||
Task<string> GetNextItemNumberAsync(string drawingNumber);
|
||||
Task UpdatePdfHashAsync(int exportId, string pdfContentHash);
|
||||
Task<string> GetPreviousPdfHashAsync(string drawingNumber, int? excludeId = null);
|
||||
Task<ApiBomItem> FindExistingBomItemAsync(int exportId, string partName, string configurationName);
|
||||
Task<ApiBomItem> CreateBomItemAsync(int exportId, ApiBomItem bomItem);
|
||||
Task<ApiCutTemplate> GetPreviousCutTemplateAsync(string drawingNumber, string itemNo);
|
||||
Task<ApiFileUploadResponse> UploadDxfAsync(string localFilePath, string equipment, string drawingNo, string itemNo, string contentHash);
|
||||
Task<ApiFileUploadResponse> UploadPdfAsync(string localFilePath, string equipment, string drawingNo, string contentHash, int? exportRecordId = null);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace ExportDXF
|
||||
{
|
||||
public class DrawingInfo
|
||||
{
|
||||
private static Regex drawingFormatRegex = new Regex(@"(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\s?(?<dwgNo>[ABEP]\d+(-?(\d+[A-Z]?))?)", RegexOptions.IgnoreCase);
|
||||
private static Regex equipmentOnlyRegex = new Regex(@"^(?<equipmentNo>[345]\d{3}(-\d+\w{1,2})?)\b", RegexOptions.IgnoreCase);
|
||||
|
||||
public string EquipmentNo { get; set; }
|
||||
|
||||
@@ -12,8 +14,39 @@ namespace ExportDXF
|
||||
|
||||
public string Source { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The descriptive text after the equipment/drawing number (e.g. "Prox switch bracket for drive").
|
||||
/// </summary>
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Source) || string.IsNullOrEmpty(EquipmentNo))
|
||||
return null;
|
||||
|
||||
// Strip equipment number (and optional drawing number) from the source to get the description
|
||||
var prefix = string.IsNullOrEmpty(DrawingNo)
|
||||
? EquipmentNo
|
||||
: EquipmentNo + " " + DrawingNo;
|
||||
|
||||
var desc = Source;
|
||||
if (desc.StartsWith(prefix, System.StringComparison.OrdinalIgnoreCase))
|
||||
desc = desc.Substring(prefix.Length);
|
||||
|
||||
// Remove file extension (e.g. ".SLDPRT")
|
||||
var ext = Path.GetExtension(desc);
|
||||
if (!string.IsNullOrEmpty(ext))
|
||||
desc = desc.Substring(0, desc.Length - ext.Length);
|
||||
|
||||
desc = desc.Trim();
|
||||
return string.IsNullOrEmpty(desc) ? null : desc;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (string.IsNullOrEmpty(DrawingNo))
|
||||
return EquipmentNo ?? string.Empty;
|
||||
return $"{EquipmentNo} {DrawingNo}";
|
||||
}
|
||||
|
||||
@@ -35,7 +68,21 @@ namespace ExportDXF
|
||||
var match = drawingFormatRegex.Match(input);
|
||||
|
||||
if (match.Success == false)
|
||||
{
|
||||
// Try matching just the equipment number (e.g. "5028 Prox switch bracket")
|
||||
var eqMatch = equipmentOnlyRegex.Match(input);
|
||||
if (eqMatch.Success)
|
||||
{
|
||||
return new DrawingInfo
|
||||
{
|
||||
EquipmentNo = eqMatch.Groups["equipmentNo"].Value,
|
||||
DrawingNo = null,
|
||||
Source = input
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var dwg = new DrawingInfo();
|
||||
|
||||
@@ -46,4 +93,4 @@ namespace ExportDXF
|
||||
return dwg;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,235 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{05F21D73-FD31-4E77-8D9B-41C86D4D8305}</ProjectGuid>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
<RootNamespace>ExportDXF</RootNamespace>
|
||||
<AssemblyName>ExportDXF</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<TargetFrameworkProfile />
|
||||
<PublishUrl>\\REMCOSRV0\Data\Software\ExportDXF\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Unc</InstallFrom>
|
||||
<UpdateEnabled>true</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<PublisherName>Rogers Engineering</PublisherName>
|
||||
<CreateWebPageOnPublish>true</CreateWebPageOnPublish>
|
||||
<WebPage>publish.htm</WebPage>
|
||||
<ApplicationRevision>8</ApplicationRevision>
|
||||
<ApplicationVersion>1.6.0.%2a</ApplicationVersion>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<PublishWizardCompleted>true</PublishWizardCompleted>
|
||||
<BootstrapperEnabled>true</BootstrapperEnabled>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ManifestCertificateThumbprint>34BB4CCEF0A2D6409091A3AC44083A6F09D1DF82</ManifestCertificateThumbprint>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ManifestKeyFile>ExportDXF_TemporaryKey.pfx</ManifestKeyFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<GenerateManifests>true</GenerateManifests>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<SignManifests>false</SignManifests>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<NoWin32Manifest>true</NoWin32Manifest>
|
||||
<ApplicationIcon />
|
||||
<StartupObject />
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<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">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\api\redist\SolidWorks.Interop.sldworks.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SolidWorks.Interop.swconst, Version=24.1.0.45, Culture=neutral, PublicKeyToken=19f43e188e4269d8, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
<HintPath>C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\api\redist\SolidWorks.Interop.swconst.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.configuration" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.Web.Extensions" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
<PackageReference Include="PDFtoImage" Version="4.1.1" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="SolidWorks.Interop.sldworks">
|
||||
<HintPath>C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\api\redist\SolidWorks.Interop.sldworks.dll</HintPath>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</Reference>
|
||||
<Reference Include="SolidWorks.Interop.swconst">
|
||||
<HintPath>C:\Program Files\SOLIDWORKS Corp\SOLIDWORKS\api\redist\SolidWorks.Interop.swconst.dll</HintPath>
|
||||
<EmbedInteropTypes>False</EmbedInteropTypes>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EtchBendLines\EtchBendLines\EtchBendLines.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="Bend.cs" />
|
||||
<Compile Include="BendDirection.cs" />
|
||||
<Compile Include="BendOrientation.cs" />
|
||||
<Compile Include="Bounds.cs" />
|
||||
<Compile Include="DrawingInfo.cs" />
|
||||
<Compile Include="Extensions\StringExtensions.cs" />
|
||||
<Compile Include="Extensions\SolidWorksExtensions.cs" />
|
||||
<Compile Include="Extensions\TimeSpanExtensions.cs" />
|
||||
<Compile Include="Extensions\UIExtensions.cs" />
|
||||
<Compile Include="Extensions\UnitConversionExtensions.cs" />
|
||||
<Compile Include="Forms\DrawingSelectionForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Forms\DrawingSelectionForm.Designer.cs">
|
||||
<DependentUpon>DrawingSelectionForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Forms\MainForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="Forms\MainForm.Designer.cs">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="ItemExtractors\AssemblyItemExtractor.cs" />
|
||||
<Compile Include="ItemExtractors\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="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="ViewFlipDeciders\PreferUpViewFlipDecider.cs" />
|
||||
<Compile Include="ViewFlipDeciders\ViewFlipDeciderFactory.cs" />
|
||||
<EmbeddedResource Include="Forms\DrawingSelectionForm.resx">
|
||||
<DependentUpon>DrawingSelectionForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Forms\MainForm.resx">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
</Compile>
|
||||
<None Include="app.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
<Content Include="Templates\Blank.drwdot">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources\edit_alt.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources\play.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources\stop_alt.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include=".NETFramework,Version=v4.0">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Microsoft .NET Framework 4 %28x86 and x64%29</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>.NET Framework 3.5 SP1</ProductName>
|
||||
<Install>false</Install>
|
||||
</BootstrapperPackage>
|
||||
<BootstrapperPackage Include="Microsoft.Windows.Installer.4.5">
|
||||
<Visible>False</Visible>
|
||||
<ProductName>Windows Installer 4.5</ProductName>
|
||||
<Install>true</Install>
|
||||
</BootstrapperPackage>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EtchBendLines\EtchBendLines\EtchBendLines.csproj">
|
||||
<Project>{229c2fb9-6ad6-4a5d-b83a-d1146573d6f9}</Project>
|
||||
<Name>EtchBendLines</Name>
|
||||
</ProjectReference>
|
||||
</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" />
|
||||
<!-- 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.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
283
ExportDXF/Forms/DrawingSelectionForm.Designer.cs
generated
283
ExportDXF/Forms/DrawingSelectionForm.Designer.cs
generated
@@ -1,283 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,260 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
<?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>
|
||||
334
ExportDXF/Forms/MainForm.Designer.cs
generated
334
ExportDXF/Forms/MainForm.Designer.cs
generated
@@ -1,4 +1,4 @@
|
||||
namespace ExportDXF.Forms
|
||||
namespace ExportDXF.Forms
|
||||
{
|
||||
partial class MainForm
|
||||
{
|
||||
@@ -28,192 +28,217 @@
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||
this.runButton = new System.Windows.Forms.Button();
|
||||
this.label3 = new System.Windows.Forms.Label();
|
||||
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();
|
||||
runButton = new System.Windows.Forms.Button();
|
||||
label3 = new System.Windows.Forms.Label();
|
||||
viewFlipDeciderBox = new System.Windows.Forms.ComboBox();
|
||||
mainTabControl = new System.Windows.Forms.TabControl();
|
||||
logEventsTab = new System.Windows.Forms.TabPage();
|
||||
logEventsDataGrid = new System.Windows.Forms.DataGridView();
|
||||
bomTab = new System.Windows.Forms.TabPage();
|
||||
bomDataGrid = new System.Windows.Forms.DataGridView();
|
||||
cutTemplatesTab = new System.Windows.Forms.TabPage();
|
||||
cutTemplatesDataGrid = new System.Windows.Forms.DataGridView();
|
||||
equipmentBox = new System.Windows.Forms.ComboBox();
|
||||
label1 = new System.Windows.Forms.Label();
|
||||
label2 = new System.Windows.Forms.Label();
|
||||
drawingNoBox = new System.Windows.Forms.ComboBox();
|
||||
titleLabel = new System.Windows.Forms.Label();
|
||||
titleBox = new System.Windows.Forms.TextBox();
|
||||
mainTabControl.SuspendLayout();
|
||||
logEventsTab.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).BeginInit();
|
||||
bomTab.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)bomDataGrid).BeginInit();
|
||||
cutTemplatesTab.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)cutTemplatesDataGrid).BeginInit();
|
||||
SuspendLayout();
|
||||
//
|
||||
// runButton
|
||||
//
|
||||
this.runButton.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
|
||||
this.runButton.Location = new System.Drawing.Point(790, 13);
|
||||
this.runButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
this.runButton.Name = "runButton";
|
||||
this.runButton.Size = new System.Drawing.Size(100, 30);
|
||||
this.runButton.TabIndex = 11;
|
||||
this.runButton.Text = "Start";
|
||||
this.runButton.UseVisualStyleBackColor = true;
|
||||
this.runButton.Click += new System.EventHandler(this.button1_Click);
|
||||
runButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
|
||||
runButton.Location = new System.Drawing.Point(508, 12);
|
||||
runButton.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
runButton.Name = "runButton";
|
||||
runButton.Size = new System.Drawing.Size(65, 87);
|
||||
runButton.TabIndex = 11;
|
||||
runButton.Text = "Start";
|
||||
runButton.UseVisualStyleBackColor = true;
|
||||
runButton.Click += button1_Click;
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.AutoSize = true;
|
||||
this.label3.Location = new System.Drawing.Point(12, 20);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(112, 17);
|
||||
this.label3.TabIndex = 2;
|
||||
this.label3.Text = "View flip decider :";
|
||||
label3.AutoSize = true;
|
||||
label3.Location = new System.Drawing.Point(26, 77);
|
||||
label3.Name = "label3";
|
||||
label3.Size = new System.Drawing.Size(105, 17);
|
||||
label3.TabIndex = 2;
|
||||
label3.Text = "View flip decider";
|
||||
//
|
||||
// viewFlipDeciderBox
|
||||
//
|
||||
this.viewFlipDeciderBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.viewFlipDeciderBox.FormattingEnabled = true;
|
||||
this.viewFlipDeciderBox.Location = new System.Drawing.Point(130, 17);
|
||||
this.viewFlipDeciderBox.Name = "viewFlipDeciderBox";
|
||||
this.viewFlipDeciderBox.Size = new System.Drawing.Size(375, 25);
|
||||
this.viewFlipDeciderBox.TabIndex = 3;
|
||||
viewFlipDeciderBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
viewFlipDeciderBox.FormattingEnabled = true;
|
||||
viewFlipDeciderBox.Location = new System.Drawing.Point(137, 74);
|
||||
viewFlipDeciderBox.Name = "viewFlipDeciderBox";
|
||||
viewFlipDeciderBox.Size = new System.Drawing.Size(365, 25);
|
||||
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;
|
||||
mainTabControl.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
mainTabControl.Controls.Add(logEventsTab);
|
||||
mainTabControl.Controls.Add(bomTab);
|
||||
mainTabControl.Controls.Add(cutTemplatesTab);
|
||||
mainTabControl.Location = new System.Drawing.Point(15, 105);
|
||||
mainTabControl.Name = "mainTabControl";
|
||||
mainTabControl.Padding = new System.Drawing.Point(20, 5);
|
||||
mainTabControl.SelectedIndex = 0;
|
||||
mainTabControl.Size = new System.Drawing.Size(910, 492);
|
||||
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;
|
||||
logEventsTab.Controls.Add(logEventsDataGrid);
|
||||
logEventsTab.Location = new System.Drawing.Point(4, 30);
|
||||
logEventsTab.Name = "logEventsTab";
|
||||
logEventsTab.Padding = new System.Windows.Forms.Padding(3);
|
||||
logEventsTab.Size = new System.Drawing.Size(902, 458);
|
||||
logEventsTab.TabIndex = 0;
|
||||
logEventsTab.Text = "Log Events";
|
||||
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;
|
||||
logEventsDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
logEventsDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
logEventsDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||
logEventsDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||
logEventsDataGrid.Name = "logEventsDataGrid";
|
||||
logEventsDataGrid.Size = new System.Drawing.Size(890, 444);
|
||||
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;
|
||||
bomTab.Controls.Add(bomDataGrid);
|
||||
bomTab.Location = new System.Drawing.Point(4, 30);
|
||||
bomTab.Name = "bomTab";
|
||||
bomTab.Padding = new System.Windows.Forms.Padding(3);
|
||||
bomTab.Size = new System.Drawing.Size(902, 458);
|
||||
bomTab.TabIndex = 1;
|
||||
bomTab.Text = "Bill Of Materials";
|
||||
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;
|
||||
bomDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
bomDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
bomDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||
bomDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||
bomDataGrid.Name = "bomDataGrid";
|
||||
bomDataGrid.Size = new System.Drawing.Size(890, 444);
|
||||
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;
|
||||
cutTemplatesTab.Controls.Add(cutTemplatesDataGrid);
|
||||
cutTemplatesTab.Location = new System.Drawing.Point(4, 30);
|
||||
cutTemplatesTab.Name = "cutTemplatesTab";
|
||||
cutTemplatesTab.Padding = new System.Windows.Forms.Padding(3);
|
||||
cutTemplatesTab.Size = new System.Drawing.Size(902, 458);
|
||||
cutTemplatesTab.TabIndex = 2;
|
||||
cutTemplatesTab.Text = "Cut Templates";
|
||||
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;
|
||||
cutTemplatesDataGrid.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
|
||||
cutTemplatesDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
cutTemplatesDataGrid.GridColor = System.Drawing.Color.WhiteSmoke;
|
||||
cutTemplatesDataGrid.Location = new System.Drawing.Point(6, 6);
|
||||
cutTemplatesDataGrid.Name = "cutTemplatesDataGrid";
|
||||
cutTemplatesDataGrid.Size = new System.Drawing.Size(890, 447);
|
||||
cutTemplatesDataGrid.TabIndex = 2;
|
||||
//
|
||||
// dwgDetailsTab
|
||||
// equipmentBox
|
||||
//
|
||||
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;
|
||||
equipmentBox.FormattingEnabled = true;
|
||||
equipmentBox.Location = new System.Drawing.Point(137, 12);
|
||||
equipmentBox.Name = "equipmentBox";
|
||||
equipmentBox.Size = new System.Drawing.Size(166, 25);
|
||||
equipmentBox.TabIndex = 13;
|
||||
//
|
||||
// drawingPdfViewer
|
||||
// label1
|
||||
//
|
||||
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;
|
||||
label1.AutoSize = true;
|
||||
label1.Location = new System.Drawing.Point(61, 15);
|
||||
label1.Name = "label1";
|
||||
label1.Size = new System.Drawing.Size(70, 17);
|
||||
label1.TabIndex = 2;
|
||||
label1.Text = "Equipment";
|
||||
//
|
||||
// label2
|
||||
//
|
||||
label2.AutoSize = true;
|
||||
label2.Location = new System.Drawing.Point(321, 15);
|
||||
label2.Name = "label2";
|
||||
label2.Size = new System.Drawing.Size(56, 17);
|
||||
label2.TabIndex = 2;
|
||||
label2.Text = "Drawing";
|
||||
//
|
||||
// drawingNoBox
|
||||
//
|
||||
drawingNoBox.FormattingEnabled = true;
|
||||
drawingNoBox.Location = new System.Drawing.Point(383, 12);
|
||||
drawingNoBox.Name = "drawingNoBox";
|
||||
drawingNoBox.Size = new System.Drawing.Size(119, 25);
|
||||
drawingNoBox.TabIndex = 13;
|
||||
//
|
||||
// titleLabel
|
||||
//
|
||||
titleLabel.AutoSize = true;
|
||||
titleLabel.Location = new System.Drawing.Point(99, 46);
|
||||
titleLabel.Name = "titleLabel";
|
||||
titleLabel.Size = new System.Drawing.Size(32, 17);
|
||||
titleLabel.TabIndex = 14;
|
||||
titleLabel.Text = "Title";
|
||||
//
|
||||
// titleBox
|
||||
//
|
||||
titleBox.Location = new System.Drawing.Point(137, 43);
|
||||
titleBox.Name = "titleBox";
|
||||
titleBox.Size = new System.Drawing.Size(365, 25);
|
||||
titleBox.TabIndex = 15;
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
this.ClientSize = new System.Drawing.Size(906, 656);
|
||||
this.Controls.Add(this.mainTabControl);
|
||||
this.Controls.Add(this.viewFlipDeciderBox);
|
||||
this.Controls.Add(this.label3);
|
||||
this.Controls.Add(this.runButton);
|
||||
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.MaximizeBox = false;
|
||||
this.MinimumSize = new System.Drawing.Size(643, 355);
|
||||
this.Name = "MainForm";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
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.PerformLayout();
|
||||
|
||||
AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
|
||||
ClientSize = new System.Drawing.Size(937, 609);
|
||||
Controls.Add(titleBox);
|
||||
Controls.Add(titleLabel);
|
||||
Controls.Add(drawingNoBox);
|
||||
Controls.Add(equipmentBox);
|
||||
Controls.Add(mainTabControl);
|
||||
Controls.Add(viewFlipDeciderBox);
|
||||
Controls.Add(label2);
|
||||
Controls.Add(label1);
|
||||
Controls.Add(label3);
|
||||
Controls.Add(runButton);
|
||||
Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, 0);
|
||||
Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
|
||||
MaximizeBox = false;
|
||||
MinimumSize = new System.Drawing.Size(642, 455);
|
||||
Name = "MainForm";
|
||||
StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
Text = "ExportDXF";
|
||||
mainTabControl.ResumeLayout(false);
|
||||
logEventsTab.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)logEventsDataGrid).EndInit();
|
||||
bomTab.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)bomDataGrid).EndInit();
|
||||
cutTemplatesTab.ResumeLayout(false);
|
||||
((System.ComponentModel.ISupportInitialize)cutTemplatesDataGrid).EndInit();
|
||||
ResumeLayout(false);
|
||||
PerformLayout();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -224,12 +249,15 @@
|
||||
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;
|
||||
private System.Windows.Forms.ComboBox equipmentBox;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.ComboBox drawingNoBox;
|
||||
private System.Windows.Forms.Label titleLabel;
|
||||
private System.Windows.Forms.TextBox titleBox;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,47 +1,51 @@
|
||||
using ExportDXF.ApiClient;
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.Models;
|
||||
using ExportDXF.Services;
|
||||
using ExportDXF.ViewFlipDeciders;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ExportDXF.Forms
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private readonly ISolidWorksService _solidWorksService;
|
||||
private readonly IDxfExportService _exportService;
|
||||
private readonly ICutFabApiClient _apiClient;
|
||||
private readonly IFabWorksApiClient _apiClient;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private readonly BindingList<LogEvent> _logEvents;
|
||||
private readonly BindingList<BomItem> _bomItems;
|
||||
private readonly BindingList<CutFabApiClient.ApiCutTemplate> _cutTemplates;
|
||||
private readonly int _selectedDrawingId;
|
||||
private readonly string _selectedDrawingNumber;
|
||||
private readonly BindingList<CutTemplate> _cutTemplates;
|
||||
private List<DrawingInfo> _allDrawings;
|
||||
|
||||
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, ICutFabApiClient apiClient, int selectedDrawingId, string selectedDrawingNumber)
|
||||
public MainForm(ISolidWorksService solidWorksService, IDxfExportService exportService, IFabWorksApiClient apiClient)
|
||||
{
|
||||
InitializeComponent();
|
||||
_solidWorksService = solidWorksService ??
|
||||
throw new ArgumentNullException(nameof(solidWorksService));
|
||||
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
||||
_exportService = exportService ??
|
||||
throw new ArgumentNullException(nameof(exportService));
|
||||
_apiClient = apiClient ??
|
||||
throw new ArgumentNullException(nameof(apiClient));
|
||||
_selectedDrawingId = selectedDrawingId;
|
||||
_selectedDrawingNumber = selectedDrawingNumber ?? throw new ArgumentNullException(nameof(selectedDrawingNumber));
|
||||
_logEvents = new BindingList<LogEvent>();
|
||||
_bomItems = new BindingList<BomItem>();
|
||||
_cutTemplates = new BindingList<CutFabApiClient.ApiCutTemplate>();
|
||||
_cutTemplates = new BindingList<CutTemplate>();
|
||||
_allDrawings = new List<DrawingInfo>();
|
||||
InitializeViewFlipDeciders();
|
||||
InitializeLogEventsGrid();
|
||||
InitializeBomGrid();
|
||||
InitializeCutTemplatesGrid();
|
||||
InitializeDrawingDropdowns();
|
||||
}
|
||||
|
||||
~MainForm()
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
@@ -49,12 +53,14 @@ namespace ExportDXF.Forms
|
||||
components?.Dispose();
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
protected override async void OnLoad(EventArgs e)
|
||||
{
|
||||
base.OnLoad(e);
|
||||
runButton.Enabled = false;
|
||||
await InitializeAsync();
|
||||
}
|
||||
|
||||
private async Task InitializeAsync()
|
||||
{
|
||||
try
|
||||
@@ -62,87 +68,19 @@ namespace ExportDXF.Forms
|
||||
LogMessage("Connecting to SolidWorks, this may take a minute...");
|
||||
await _solidWorksService.ConnectAsync();
|
||||
_solidWorksService.ActiveDocumentChanged += OnActiveDocumentChanged;
|
||||
LogMessage("Ready", Color.Green);
|
||||
UpdateActiveDocumentDisplay();
|
||||
LogMessage("Files will be uploaded to FabWorks API");
|
||||
await LoadDrawingDropdownsAsync();
|
||||
LogMessage("Ready");
|
||||
await UpdateActiveDocumentDisplayAsync();
|
||||
runButton.Enabled = true;
|
||||
|
||||
// Load the selected drawing's BOM items
|
||||
LogMessage($"Loading BOM items for drawing {_selectedDrawingNumber}...");
|
||||
await LoadBomItemsForDrawingAsync(_selectedDrawingId);
|
||||
|
||||
// Load cut templates
|
||||
await LoadCutTemplatesAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage($"Failed to connect to SolidWorks: {ex.Message}", Color.Red);
|
||||
LogMessage($"Failed to connect to SolidWorks: {ex.Message}", LogLevel.Error);
|
||||
MessageBox.Show("Failed to connect to SolidWorks.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
Application.Exit();
|
||||
}
|
||||
}
|
||||
private async Task LoadBomItemsForDrawingAsync(int drawingId)
|
||||
{
|
||||
try
|
||||
{
|
||||
_bomItems.Clear();
|
||||
|
||||
var apiBomItems = await _apiClient.GetBomItemsForDrawingAsync(drawingId);
|
||||
|
||||
foreach (var apiItem in apiBomItems)
|
||||
{
|
||||
var bomItem = new BomItem
|
||||
{
|
||||
ID = apiItem.ID,
|
||||
ItemNo = apiItem.ItemNo ?? "",
|
||||
PartNo = apiItem.PartNo ?? "",
|
||||
SortOrder = apiItem.SortOrder,
|
||||
Qty = apiItem.Qty,
|
||||
TotalQty = apiItem.TotalQty,
|
||||
Description = apiItem.Description ?? "",
|
||||
PartName = apiItem.PartName ?? "",
|
||||
ConfigurationName = apiItem.ConfigurationName ?? "",
|
||||
Material = apiItem.Material ?? "",
|
||||
DrawingID = apiItem.DrawingID,
|
||||
CutTemplateID = apiItem.CutTemplateID,
|
||||
CutTemplateName = apiItem.CutTemplateName ?? "",
|
||||
Thickness = apiItem.Thickness,
|
||||
KFactor = apiItem.KFactor,
|
||||
DefaultBendRadius = apiItem.DefaultBendRadius
|
||||
};
|
||||
|
||||
_bomItems.Add(bomItem);
|
||||
}
|
||||
|
||||
LogMessage($"Loaded {apiBomItems.Count} BOM item(s)", Color.Green);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage($"Failed to load BOM items: {ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadCutTemplatesAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_cutTemplates.Clear();
|
||||
|
||||
LogMessage("Loading cut templates...");
|
||||
|
||||
var apiCutTemplates = await _apiClient.GetCutTemplatesAsync();
|
||||
|
||||
foreach (var template in apiCutTemplates)
|
||||
{
|
||||
_cutTemplates.Add(template);
|
||||
}
|
||||
|
||||
LogMessage($"Loaded {apiCutTemplates.Count} cut template(s)", Color.Green);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage($"Failed to load cut templates: {ex.Message}", Color.Red);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeViewFlipDeciders()
|
||||
{
|
||||
@@ -163,6 +101,7 @@ namespace ExportDXF.Forms
|
||||
viewFlipDeciderBox.DataSource = items;
|
||||
viewFlipDeciderBox.DisplayMember = "Name";
|
||||
}
|
||||
|
||||
private void InitializeLogEventsGrid()
|
||||
{
|
||||
// Clear any existing columns first
|
||||
@@ -191,6 +130,13 @@ namespace ExportDXF.Forms
|
||||
Width = 70
|
||||
});
|
||||
|
||||
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(LogEvent.Part),
|
||||
HeaderText = "File",
|
||||
Width = 180
|
||||
});
|
||||
|
||||
logEventsDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(LogEvent.Message),
|
||||
@@ -198,6 +144,9 @@ namespace ExportDXF.Forms
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
|
||||
});
|
||||
|
||||
// Add row coloring based on log level
|
||||
logEventsDataGrid.CellFormatting += LogEventsDataGrid_CellFormatting;
|
||||
|
||||
// Set the data source AFTER adding columns
|
||||
logEventsDataGrid.DataSource = _logEvents;
|
||||
}
|
||||
@@ -264,86 +213,128 @@ namespace ExportDXF.Forms
|
||||
Width = 120
|
||||
});
|
||||
|
||||
bomDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(BomItem.CutTemplateName),
|
||||
HeaderText = "Cut Template",
|
||||
Width = 120
|
||||
});
|
||||
|
||||
// Set the data source AFTER adding columns
|
||||
bomDataGrid.DataSource = _bomItems;
|
||||
}
|
||||
|
||||
private void InitializeCutTemplatesGrid()
|
||||
{
|
||||
// Clear any existing columns first
|
||||
cutTemplatesDataGrid.Columns.Clear();
|
||||
|
||||
// Configure grid settings
|
||||
cutTemplatesDataGrid.AutoGenerateColumns = false;
|
||||
cutTemplatesDataGrid.AllowUserToAddRows = false;
|
||||
cutTemplatesDataGrid.AllowUserToDeleteRows = false;
|
||||
cutTemplatesDataGrid.ReadOnly = true;
|
||||
cutTemplatesDataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
|
||||
|
||||
// Add columns
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Name),
|
||||
HeaderText = "Name",
|
||||
Width = 200
|
||||
DataPropertyName = nameof(CutTemplate.CutTemplateName),
|
||||
HeaderText = "Template Name",
|
||||
Width = 150
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Material),
|
||||
HeaderText = "Material",
|
||||
Width = 120
|
||||
DataPropertyName = nameof(CutTemplate.DxfFilePath),
|
||||
HeaderText = "DXF File",
|
||||
Width = 250
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Thickness),
|
||||
DataPropertyName = nameof(CutTemplate.Thickness),
|
||||
HeaderText = "Thickness",
|
||||
Width = 80,
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Format = "0.##" }
|
||||
Width = 80
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.KFactor),
|
||||
DataPropertyName = nameof(CutTemplate.KFactor),
|
||||
HeaderText = "K-Factor",
|
||||
Width = 80,
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Format = "0.####" }
|
||||
Width = 80
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.DefaultBendRadius),
|
||||
DataPropertyName = nameof(CutTemplate.DefaultBendRadius),
|
||||
HeaderText = "Bend Radius",
|
||||
Width = 90,
|
||||
DefaultCellStyle = new DataGridViewCellStyle { Format = "0.##" }
|
||||
Width = 90
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Version),
|
||||
HeaderText = "Version",
|
||||
Width = 70
|
||||
DataPropertyName = nameof(CutTemplate.ContentHash),
|
||||
HeaderText = "Content Hash",
|
||||
Width = 150
|
||||
});
|
||||
|
||||
cutTemplatesDataGrid.Columns.Add(new DataGridViewTextBoxColumn
|
||||
{
|
||||
DataPropertyName = nameof(CutFabApiClient.ApiCutTemplate.Description),
|
||||
HeaderText = "Description",
|
||||
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill
|
||||
});
|
||||
|
||||
// Set the data source AFTER adding columns
|
||||
cutTemplatesDataGrid.DataSource = _cutTemplates;
|
||||
}
|
||||
|
||||
private void InitializeDrawingDropdowns()
|
||||
{
|
||||
// Wire up event handler; actual data loading happens in LoadDrawingDropdownsAsync
|
||||
equipmentBox.SelectedIndexChanged += EquipmentBox_SelectedIndexChanged;
|
||||
}
|
||||
|
||||
private async Task LoadDrawingDropdownsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var equipmentNumbers = await _apiClient.GetEquipmentNumbersAsync();
|
||||
|
||||
equipmentBox.Items.Clear();
|
||||
equipmentBox.Items.Add("");
|
||||
foreach (var eq in equipmentNumbers)
|
||||
{
|
||||
equipmentBox.Items.Add(eq);
|
||||
}
|
||||
|
||||
// Clear _allDrawings — drawing list is now loaded on equipment selection
|
||||
_allDrawings = new List<DrawingInfo>();
|
||||
await UpdateDrawingDropdownAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// API might not be available yet - that's OK
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to load equipment numbers from API: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async void EquipmentBox_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
await UpdateDrawingDropdownAsync();
|
||||
}
|
||||
|
||||
private async Task UpdateDrawingDropdownAsync()
|
||||
{
|
||||
var selectedEquipment = equipmentBox.SelectedItem?.ToString();
|
||||
|
||||
drawingNoBox.Items.Clear();
|
||||
drawingNoBox.Items.Add("");
|
||||
|
||||
try
|
||||
{
|
||||
var drawingNumbers = await _apiClient.GetDrawingNumbersByEquipmentAsync(
|
||||
string.IsNullOrEmpty(selectedEquipment) ? null : selectedEquipment);
|
||||
|
||||
foreach (var dn in drawingNumbers)
|
||||
{
|
||||
drawingNoBox.Items.Add(dn);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// API might not be available
|
||||
}
|
||||
|
||||
if (drawingNoBox.Items.Count > 0)
|
||||
{
|
||||
drawingNoBox.SelectedIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private async void button1_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (_cancellationTokenSource != null)
|
||||
@@ -355,6 +346,7 @@ namespace ExportDXF.Forms
|
||||
await StartExportAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartExportAsync()
|
||||
{
|
||||
try
|
||||
@@ -362,86 +354,179 @@ namespace ExportDXF.Forms
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
var token = _cancellationTokenSource.Token;
|
||||
UpdateUIForExportStart();
|
||||
|
||||
var activeDoc = _solidWorksService.GetActiveDocument();
|
||||
if (activeDoc == null)
|
||||
{
|
||||
LogMessage("No active document.", Color.Red);
|
||||
LogMessage("No active document.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use equipment/drawing values from the UI dropdowns
|
||||
var equipment = equipmentBox.Text?.Trim();
|
||||
var drawingNo = drawingNoBox.Text?.Trim();
|
||||
var filePrefix = !string.IsNullOrEmpty(equipment)
|
||||
? (!string.IsNullOrEmpty(drawingNo) ? $"{equipment} {drawingNo}" : equipment)
|
||||
: activeDoc.Title;
|
||||
|
||||
var viewFlipDecider = GetSelectedViewFlipDecider();
|
||||
var title = titleBox.Text?.Trim();
|
||||
|
||||
var exportContext = new ExportContext
|
||||
{
|
||||
ActiveDocument = activeDoc,
|
||||
ViewFlipDecider = viewFlipDecider,
|
||||
FilePrefix = _selectedDrawingNumber,
|
||||
EquipmentId = null, // Not needed for export
|
||||
FilePrefix = filePrefix,
|
||||
Equipment = equipment,
|
||||
DrawingNo = drawingNo,
|
||||
Title = string.IsNullOrEmpty(title) ? null : title,
|
||||
EquipmentId = null,
|
||||
CancellationToken = token,
|
||||
ProgressCallback = LogMessage
|
||||
ProgressCallback = (msg, level, file) => LogMessage(msg, level, file),
|
||||
BomItemCallback = AddBomItem
|
||||
};
|
||||
|
||||
// Clear previous BOM items and cut templates
|
||||
_bomItems.Clear();
|
||||
_cutTemplates.Clear();
|
||||
|
||||
LogMessage($"Started at {DateTime.Now:t}");
|
||||
await Task.Run(() => _exportService.Export(exportContext), token);
|
||||
LogMessage("Done.", Color.Green);
|
||||
LogMessage("Exporting (files will be uploaded to API)...");
|
||||
|
||||
_solidWorksService.SetCommandInProgress(true);
|
||||
|
||||
await Task.Run(async () => await _exportService.ExportAsync(exportContext), token);
|
||||
|
||||
LogMessage("Done.");
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
LogMessage("Export canceled.", Color.Red);
|
||||
LogMessage("Export canceled.", LogLevel.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogMessage($"Export failed: {ex.Message}", Color.Red);
|
||||
LogMessage($"Export failed: {ex.Message}", LogLevel.Error);
|
||||
MessageBox.Show($"Export failed: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_solidWorksService.SetCommandInProgress(false);
|
||||
UpdateUIForExportComplete();
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void CancelExport()
|
||||
{
|
||||
runButton.Enabled = false;
|
||||
_cancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
private IViewFlipDecider GetSelectedViewFlipDecider()
|
||||
{
|
||||
var item = viewFlipDeciderBox.SelectedItem as ViewFlipDeciderComboboxItem;
|
||||
return item?.ViewFlipDecider;
|
||||
}
|
||||
|
||||
private void UpdateUIForExportStart()
|
||||
{
|
||||
viewFlipDeciderBox.Enabled = false;
|
||||
runButton.Image = Properties.Resources.stop_alt;
|
||||
runButton.Text = "Stop";
|
||||
}
|
||||
|
||||
private void UpdateUIForExportComplete()
|
||||
{
|
||||
viewFlipDeciderBox.Enabled = true;
|
||||
runButton.Image = Properties.Resources.play;
|
||||
runButton.Text = "Start";
|
||||
runButton.Enabled = true;
|
||||
}
|
||||
private void OnActiveDocumentChanged(object sender, EventArgs e)
|
||||
|
||||
private async void OnActiveDocumentChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action(() => OnActiveDocumentChanged(sender, e)));
|
||||
Invoke(new Action(async () => await UpdateActiveDocumentDisplayAsync()));
|
||||
return;
|
||||
}
|
||||
UpdateActiveDocumentDisplay();
|
||||
await UpdateActiveDocumentDisplayAsync();
|
||||
}
|
||||
private void UpdateActiveDocumentDisplay()
|
||||
|
||||
private async Task UpdateActiveDocumentDisplayAsync()
|
||||
{
|
||||
var activeDoc = _solidWorksService.GetActiveDocument();
|
||||
var docTitle = activeDoc?.Title ?? "No Document Open";
|
||||
this.Text = $"ExportDXF - {docTitle}";
|
||||
}
|
||||
private void LogMessage(string message, Color? color = null)
|
||||
{
|
||||
var level = color.HasValue && color.Value == Color.Red ? LogLevel.Error :
|
||||
color.HasValue && color.Value == Color.Green ? LogLevel.Info :
|
||||
color.HasValue && color.Value == Color.DarkBlue ? LogLevel.Warning :
|
||||
LogLevel.Info;
|
||||
|
||||
AddLogEvent(level, LogAction.Start, message);
|
||||
if (activeDoc == null)
|
||||
return;
|
||||
|
||||
// Try API first: look up the most recent export for this file path
|
||||
DrawingInfo drawingInfo = null;
|
||||
|
||||
if (!string.IsNullOrEmpty(activeDoc.FilePath))
|
||||
{
|
||||
drawingInfo = await LookupDrawingInfoFromHistoryAsync(activeDoc.FilePath);
|
||||
}
|
||||
|
||||
// Fall back to parsing the document title
|
||||
if (drawingInfo == null)
|
||||
{
|
||||
drawingInfo = DrawingInfo.Parse(activeDoc.Title);
|
||||
}
|
||||
|
||||
if (drawingInfo != null)
|
||||
{
|
||||
// Detach event to prevent async race when setting equipment
|
||||
equipmentBox.SelectedIndexChanged -= EquipmentBox_SelectedIndexChanged;
|
||||
|
||||
if (!string.IsNullOrEmpty(drawingInfo.EquipmentNo))
|
||||
{
|
||||
if (!equipmentBox.Items.Contains(drawingInfo.EquipmentNo))
|
||||
equipmentBox.Items.Add(drawingInfo.EquipmentNo);
|
||||
equipmentBox.Text = drawingInfo.EquipmentNo;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(titleBox.Text) && !string.IsNullOrEmpty(drawingInfo.Description))
|
||||
titleBox.Text = drawingInfo.Description;
|
||||
|
||||
// Load drawings for the selected equipment, then set drawing number
|
||||
await UpdateDrawingDropdownAsync();
|
||||
|
||||
equipmentBox.SelectedIndexChanged += EquipmentBox_SelectedIndexChanged;
|
||||
|
||||
if (!string.IsNullOrEmpty(drawingInfo.DrawingNo))
|
||||
{
|
||||
if (!drawingNoBox.Items.Contains(drawingInfo.DrawingNo))
|
||||
drawingNoBox.Items.Add(drawingInfo.DrawingNo);
|
||||
drawingNoBox.Text = drawingInfo.DrawingNo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<DrawingInfo> LookupDrawingInfoFromHistoryAsync(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dto = await _apiClient.GetExportBySourceFileAsync(filePath);
|
||||
if (dto != null && !string.IsNullOrEmpty(dto.DrawingNumber))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(dto.Title))
|
||||
titleBox.Text = dto.Title;
|
||||
return DrawingInfo.Parse(dto.DrawingNumber);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Failed to look up drawing info from API: {ex.Message}");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void LogMessage(string message, LogLevel level = LogLevel.Info, string file = null)
|
||||
{
|
||||
AddLogEvent(level, LogAction.Start, message, part: file);
|
||||
}
|
||||
|
||||
private void AddLogEvent(LogLevel level, LogAction action, string message, string equipment = "", string drawing = "", string part = "", string target = "", string result = "OK", int durationMs = 0)
|
||||
@@ -474,5 +559,45 @@ namespace ExportDXF.Forms
|
||||
logEventsDataGrid.FirstDisplayedScrollingRowIndex = logEventsDataGrid.Rows.Count - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddBomItem(BomItem item)
|
||||
{
|
||||
if (InvokeRequired)
|
||||
{
|
||||
Invoke(new Action(() => AddBomItem(item)));
|
||||
return;
|
||||
}
|
||||
_bomItems.Add(item);
|
||||
|
||||
if (item.CutTemplate != null)
|
||||
{
|
||||
_cutTemplates.Add(item.CutTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogEventsDataGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
|
||||
{
|
||||
if (e.RowIndex < 0 || e.RowIndex >= _logEvents.Count)
|
||||
return;
|
||||
|
||||
var logEvent = _logEvents[e.RowIndex];
|
||||
var row = logEventsDataGrid.Rows[e.RowIndex];
|
||||
|
||||
switch (logEvent.Level)
|
||||
{
|
||||
case LogLevel.Warning:
|
||||
row.DefaultCellStyle.BackColor = Color.LightGoldenrodYellow;
|
||||
row.DefaultCellStyle.ForeColor = Color.DarkOrange;
|
||||
break;
|
||||
case LogLevel.Error:
|
||||
row.DefaultCellStyle.BackColor = Color.MistyRose;
|
||||
row.DefaultCellStyle.ForeColor = Color.DarkRed;
|
||||
break;
|
||||
default:
|
||||
row.DefaultCellStyle.BackColor = Color.White;
|
||||
row.DefaultCellStyle.ForeColor = Color.Black;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
<!--
|
||||
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
|
||||
|
||||
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>
|
||||
@@ -26,36 +26,36 @@
|
||||
<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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
@@ -117,12 +117,4 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="drawingPdfViewer.OcxState" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>
|
||||
AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
|
||||
LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACFTeXN0
|
||||
ZW0uV2luZG93cy5Gb3Jtcy5BeEhvc3QrU3RhdGUBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAIQAAAAIB
|
||||
AAAAAQAAAAAAAAAAAAAAAAwAAAAADgAASGsAANA3AAAL
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace ExportDXF.Models
|
||||
{
|
||||
public class BomItem
|
||||
@@ -14,14 +12,13 @@ namespace ExportDXF.Models
|
||||
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; }
|
||||
// EF Core relationship to ExportRecord
|
||||
public int ExportRecordId { get; set; }
|
||||
public virtual ExportRecord ExportRecord { get; set; }
|
||||
|
||||
// Optional 1:1 relationship to CutTemplate (only for sheet metal parts)
|
||||
public virtual CutTemplate CutTemplate { get; set; }
|
||||
}
|
||||
|
||||
public struct Size
|
||||
|
||||
33
ExportDXF/Models/CutTemplate.cs
Normal file
33
ExportDXF/Models/CutTemplate.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace ExportDXF.Models
|
||||
{
|
||||
public class CutTemplate
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DxfFilePath { get; set; } = "";
|
||||
public string ContentHash { get; set; }
|
||||
public string CutTemplateName { get; set; } = "";
|
||||
|
||||
// Sheet metal properties (moved from BomItem)
|
||||
private double? _thickness;
|
||||
public double? Thickness
|
||||
{
|
||||
get => _thickness;
|
||||
set => _thickness = value.HasValue ? Math.Round(value.Value, 8) : null;
|
||||
}
|
||||
|
||||
public double? KFactor { get; set; }
|
||||
|
||||
private double? _defaultBendRadius;
|
||||
public double? DefaultBendRadius
|
||||
{
|
||||
get => _defaultBendRadius;
|
||||
set => _defaultBendRadius = value.HasValue ? Math.Round(value.Value, 8) : null;
|
||||
}
|
||||
|
||||
// FK back to BomItem
|
||||
public int BomItemId { get; set; }
|
||||
public virtual BomItem BomItem { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using ExportDXF.ViewFlipDeciders;
|
||||
using ExportDXF.Models;
|
||||
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;
|
||||
@@ -32,6 +32,21 @@ namespace ExportDXF.Services
|
||||
/// </summary>
|
||||
public string FilePrefix { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Equipment number from the UI (e.g., "5028").
|
||||
/// </summary>
|
||||
public string Equipment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Drawing number from the UI (e.g., "A02", "Misc").
|
||||
/// </summary>
|
||||
public string DrawingNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional title/label for the export.
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Selected Equipment ID for API operations (optional).
|
||||
/// </summary>
|
||||
@@ -44,12 +59,18 @@ namespace ExportDXF.Services
|
||||
|
||||
/// <summary>
|
||||
/// Callback for reporting progress and status messages.
|
||||
/// Parameters: message, level, file (optional)
|
||||
/// </summary>
|
||||
public Action<string, Color?> ProgressCallback { get; set; }
|
||||
public Action<string, LogLevel, string> ProgressCallback { get; set; }
|
||||
|
||||
public void LogProgress(string message, Color? color = null)
|
||||
/// <summary>
|
||||
/// Callback for adding BOM items to the UI.
|
||||
/// </summary>
|
||||
public Action<BomItem> BomItemCallback { get; set; }
|
||||
|
||||
public void LogProgress(string message, LogLevel level = LogLevel.Info, string file = null)
|
||||
{
|
||||
ProgressCallback?.Invoke(message, color);
|
||||
ProgressCallback?.Invoke(message, level, file);
|
||||
}
|
||||
|
||||
public SldWorks SolidWorksApp { get; set; }
|
||||
@@ -90,7 +111,7 @@ namespace ExportDXF.Services
|
||||
|
||||
if (SolidWorksApp == null)
|
||||
{
|
||||
ProgressCallback?.Invoke("Warning: Cannot cleanup template drawing - SolidWorks app not available", Color.DarkBlue);
|
||||
ProgressCallback?.Invoke("Warning: Cannot cleanup template drawing - SolidWorks app not available", LogLevel.Warning, null);
|
||||
TemplateDrawing = null;
|
||||
return;
|
||||
}
|
||||
@@ -104,7 +125,7 @@ namespace ExportDXF.Services
|
||||
{
|
||||
// Close the document without saving
|
||||
SolidWorksApp.CloseDoc(title);
|
||||
ProgressCallback?.Invoke("Closed template drawing", null);
|
||||
ProgressCallback?.Invoke("Closed template drawing", LogLevel.Info, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +134,7 @@ namespace ExportDXF.Services
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ProgressCallback?.Invoke($"Failed to close template drawing: {ex.Message}", Color.Red);
|
||||
ProgressCallback?.Invoke($"Failed to close template drawing: {ex.Message}", LogLevel.Error, null);
|
||||
|
||||
// Still clear the reference to prevent further issues
|
||||
TemplateDrawing = null;
|
||||
|
||||
20
ExportDXF/Models/ExportRecord.cs
Normal file
20
ExportDXF/Models/ExportRecord.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ExportDXF.Models
|
||||
{
|
||||
public class ExportRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DrawingNumber { get; set; }
|
||||
public string EquipmentNo { get; set; }
|
||||
public string DrawingNo { get; set; }
|
||||
public string SourceFilePath { get; set; }
|
||||
public string OutputFolder { get; set; }
|
||||
public DateTime ExportedAt { get; set; }
|
||||
public string ExportedBy { get; set; }
|
||||
public string PdfContentHash { get; set; }
|
||||
|
||||
public virtual ICollection<BomItem> BomItems { get; set; } = new List<BomItem>();
|
||||
}
|
||||
}
|
||||
@@ -61,5 +61,16 @@ namespace ExportDXF.Services
|
||||
/// The SolidWorks component reference.
|
||||
/// </summary>
|
||||
public Component2 Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA256 content hash of the exported DXF (transient, not persisted).
|
||||
/// </summary>
|
||||
public string ContentHash { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Full path to the locally-exported DXF temp file (transient, not persisted).
|
||||
/// Set after successful export; used for upload to the API.
|
||||
/// </summary>
|
||||
public string LocalTempPath { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using ExportDXF.Forms;
|
||||
using ExportDXF.ApiClient;
|
||||
using ExportDXF.Forms;
|
||||
using ExportDXF.Services;
|
||||
using System;
|
||||
using System.Configuration;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace ExportDXF
|
||||
@@ -18,78 +22,95 @@ namespace ExportDXF
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
|
||||
// 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
|
||||
var mainForm = container.ResolveMainForm();
|
||||
Application.Run(mainForm);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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;
|
||||
private readonly string _apiBaseUrl;
|
||||
|
||||
public ServiceContainer()
|
||||
{
|
||||
_baseUrl = ConfigurationManager.AppSettings["CutFab.ApiBaseUrl"] ?? "http://localhost:7027";
|
||||
_apiClient = new CutFabApiClient(_baseUrl);
|
||||
_apiBaseUrl = ConfigurationManager.AppSettings["FabWorksApiUrl"] ?? "http://localhost:5206";
|
||||
}
|
||||
|
||||
public async Task<DrawingSelectionForm> ResolveDrawingSelectionAsync()
|
||||
public MainForm ResolveMainForm()
|
||||
{
|
||||
// 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();
|
||||
|
||||
EnsureApiRunning();
|
||||
|
||||
var httpClient = new HttpClient
|
||||
{
|
||||
BaseAddress = new Uri(_apiBaseUrl),
|
||||
Timeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
var apiClient = new FabWorksApiClient(httpClient);
|
||||
|
||||
var exportService = new DxfExportService(
|
||||
solidWorksService,
|
||||
bomExtractor,
|
||||
partExporter,
|
||||
drawingExporter,
|
||||
_apiClient);
|
||||
apiClient);
|
||||
|
||||
return new MainForm(solidWorksService, exportService, _apiClient, selectedDrawingId, selectedDrawingNumber);
|
||||
return new MainForm(solidWorksService, exportService, apiClient);
|
||||
}
|
||||
|
||||
private void EnsureApiRunning()
|
||||
{
|
||||
// Check if API is already responding
|
||||
using (var probe = new HttpClient { Timeout = TimeSpan.FromSeconds(2) })
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = probe.GetAsync(_apiBaseUrl + "/api/exports?take=1").Result;
|
||||
if (response.IsSuccessStatusCode)
|
||||
return; // already running
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// Find the API executable relative to this assembly
|
||||
var exeDir = AppContext.BaseDirectory;
|
||||
var apiExe = Path.GetFullPath(Path.Combine(exeDir, @"..\..\..\FabWorks.Api\bin\Debug\net8.0\FabWorks.Api.exe"));
|
||||
if (!File.Exists(apiExe))
|
||||
return; // can't find it, skip
|
||||
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = apiExe,
|
||||
WorkingDirectory = Path.GetDirectoryName(apiExe),
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
Process.Start(startInfo);
|
||||
|
||||
// Wait up to 10 seconds for API to become ready
|
||||
using (var probe = new HttpClient { Timeout = TimeSpan.FromSeconds(2) })
|
||||
{
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
try
|
||||
{
|
||||
var response = probe.GetAsync(_apiBaseUrl + "/api/exports?take=1").Result;
|
||||
if (response.IsSuccessStatusCode)
|
||||
return;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.ItemExtractors;
|
||||
using ExportDXF.Models;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
@@ -16,14 +16,14 @@ namespace ExportDXF.Services
|
||||
/// 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>
|
||||
/// <param name="progressCallback">Optional callback for progress updates (message, level, file).</param>
|
||||
/// <returns>A list of extracted items.</returns>
|
||||
List<Item> ExtractFromDrawing(DrawingDoc drawing, Action<string, Color?> progressCallback);
|
||||
List<Item> ExtractFromDrawing(DrawingDoc drawing, Action<string, LogLevel, string> progressCallback);
|
||||
}
|
||||
|
||||
public class BomExtractor : IBomExtractor
|
||||
{
|
||||
public List<Item> ExtractFromDrawing(DrawingDoc drawing, Action<string, Color?> progressCallback)
|
||||
public List<Item> ExtractFromDrawing(DrawingDoc drawing, Action<string, LogLevel, string> progressCallback)
|
||||
{
|
||||
if (drawing == null)
|
||||
throw new ArgumentNullException(nameof(drawing));
|
||||
@@ -32,11 +32,11 @@ namespace ExportDXF.Services
|
||||
|
||||
if (bomTables.Count == 0)
|
||||
{
|
||||
progressCallback?.Invoke("Error: Bill of materials not found.", Color.Red);
|
||||
progressCallback?.Invoke("Error: Bill of materials not found.", LogLevel.Error, null);
|
||||
return new List<Item>();
|
||||
}
|
||||
|
||||
progressCallback?.Invoke($"Found {bomTables.Count} BOM table(s)", null);
|
||||
progressCallback?.Invoke($"Found {bomTables.Count} BOM table(s)", LogLevel.Info, null);
|
||||
|
||||
var allItems = new List<Item>();
|
||||
|
||||
@@ -49,14 +49,14 @@ namespace ExportDXF.Services
|
||||
SkipHiddenRows = true
|
||||
};
|
||||
|
||||
progressCallback?.Invoke($"Fetching components from {bom.BomFeature.Name}", null);
|
||||
progressCallback?.Invoke($"Fetching components from {bom.BomFeature.Name}", LogLevel.Info, null);
|
||||
|
||||
var items = extractor.GetItems();
|
||||
allItems.AddRange(items);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
progressCallback?.Invoke($"Failed to extract from {bom.BomFeature.Name}: {ex.Message}", Color.Red);
|
||||
progressCallback?.Invoke($"Failed to extract: {ex.Message}", LogLevel.Error, bom.BomFeature.Name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,468 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using ExportDXF.Models;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
@@ -57,7 +57,7 @@ namespace ExportDXF.Services
|
||||
(int)swExportDataSheetsToExport_e.swExportData_ExportAllSheets,
|
||||
drawing);
|
||||
|
||||
context.ProgressCallback?.Invoke($"Exporting drawing to PDF: \"{pdfFileName}\"", null);
|
||||
context.ProgressCallback?.Invoke("Exporting drawing to PDF", LogLevel.Info, pdfFileName);
|
||||
|
||||
int errors = 0;
|
||||
int warnings = 0;
|
||||
@@ -73,26 +73,26 @@ namespace ExportDXF.Services
|
||||
|
||||
if (success && errors == 0)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"Saved drawing to PDF: \"{pdfFileName}\"", Color.Green);
|
||||
context.ProgressCallback?.Invoke("Saved drawing to PDF", LogLevel.Info, pdfFileName);
|
||||
}
|
||||
else if (success && warnings > 0)
|
||||
{
|
||||
context.ProgressCallback?.Invoke(
|
||||
$"PDF export completed with warnings: {warnings}",
|
||||
Color.DarkBlue);
|
||||
LogLevel.Warning, pdfFileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.ProgressCallback?.Invoke(
|
||||
$"PDF export failed. Errors: {errors}, Warnings: {warnings}",
|
||||
Color.Red);
|
||||
LogLevel.Error, pdfFileName);
|
||||
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);
|
||||
context.ProgressCallback?.Invoke(errorMessage, LogLevel.Error, null);
|
||||
throw new InvalidOperationException(errorMessage, ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
using ExportDXF.ApiClient;
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.ItemExtractors;
|
||||
using ExportDXF.Models;
|
||||
using ExportDXF.Utilities;
|
||||
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;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
@@ -21,11 +19,12 @@ namespace ExportDXF.Services
|
||||
/// Exports the document specified in the context to DXF format.
|
||||
/// </summary>
|
||||
/// <param name="context">The export context containing all necessary information.</param>
|
||||
void Export(ExportContext context);
|
||||
Task ExportAsync(ExportContext context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service responsible for orchestrating the export of SolidWorks documents to DXF format.
|
||||
/// Files are generated locally in a temp directory, then uploaded to the API for storage and versioning.
|
||||
/// </summary>
|
||||
public class DxfExportService : IDxfExportService
|
||||
{
|
||||
@@ -33,14 +32,14 @@ namespace ExportDXF.Services
|
||||
private readonly IBomExtractor _bomExtractor;
|
||||
private readonly IPartExporter _partExporter;
|
||||
private readonly IDrawingExporter _drawingExporter;
|
||||
private readonly ICutFabApiClient _apiClient;
|
||||
private readonly IFabWorksApiClient _apiClient;
|
||||
|
||||
public DxfExportService(
|
||||
ISolidWorksService solidWorksService,
|
||||
IBomExtractor bomExtractor,
|
||||
IPartExporter partExporter,
|
||||
IDrawingExporter drawingExporter,
|
||||
ICutFabApiClient apiClient)
|
||||
IFabWorksApiClient apiClient)
|
||||
{
|
||||
_solidWorksService = solidWorksService ?? throw new ArgumentNullException(nameof(solidWorksService));
|
||||
_bomExtractor = bomExtractor ?? throw new ArgumentNullException(nameof(bomExtractor));
|
||||
@@ -52,7 +51,7 @@ namespace ExportDXF.Services
|
||||
/// <summary>
|
||||
/// Exports the document specified in the context to DXF format.
|
||||
/// </summary>
|
||||
public void Export(ExportContext context)
|
||||
public async Task ExportAsync(ExportContext context)
|
||||
{
|
||||
if (context == null)
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
@@ -61,27 +60,30 @@ namespace ExportDXF.Services
|
||||
SetupExportContext(context);
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
var tempDir = CreateTempWorkDir();
|
||||
|
||||
try
|
||||
{
|
||||
_solidWorksService.EnableUserControl(false);
|
||||
|
||||
var drawingNumber = ParseDrawingNumber(context);
|
||||
|
||||
switch (context.ActiveDocument.DocumentType)
|
||||
{
|
||||
case DocumentType.Part:
|
||||
ExportPart(context);
|
||||
await ExportPartAsync(context, tempDir, drawingNumber);
|
||||
break;
|
||||
|
||||
case DocumentType.Assembly:
|
||||
ExportAssembly(context);
|
||||
await ExportAssemblyAsync(context, tempDir, drawingNumber);
|
||||
break;
|
||||
|
||||
case DocumentType.Drawing:
|
||||
ExportDrawing(context);
|
||||
await ExportDrawingAsync(context, drawingNumber, tempDir);
|
||||
break;
|
||||
|
||||
default:
|
||||
LogProgress(context, "Unknown document type.", Color.Red);
|
||||
LogProgress(context, "Unknown document type.", LogLevel.Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -89,38 +91,82 @@ namespace ExportDXF.Services
|
||||
{
|
||||
CleanupExportContext(context);
|
||||
_solidWorksService.EnableUserControl(true);
|
||||
CleanupTempDir(tempDir);
|
||||
|
||||
var duration = DateTime.Now - startTime;
|
||||
LogProgress(context, $"Run time: {duration.ToReadableFormat()}", null);
|
||||
LogProgress(context, $"Run time: {duration.ToReadableFormat()}");
|
||||
}
|
||||
}
|
||||
|
||||
#region Export Methods by Document Type
|
||||
|
||||
private void ExportPart(ExportContext context)
|
||||
private async Task ExportPartAsync(ExportContext context, string tempDir, string drawingNumber)
|
||||
{
|
||||
LogProgress(context, "Active document is a Part", null);
|
||||
LogProgress(context, "Active document is a Part");
|
||||
|
||||
var part = context.ActiveDocument.NativeDocument as PartDoc;
|
||||
if (part == null)
|
||||
{
|
||||
LogProgress(context, "Failed to get part document.", Color.Red);
|
||||
LogProgress(context, "Failed to get part document.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var tempDir = CreateTempWorkDir();
|
||||
_partExporter.ExportSinglePart(part, tempDir, context);
|
||||
var exportRecord = await CreateExportRecordAsync(context, drawingNumber);
|
||||
var item = _partExporter.ExportSinglePart(part, tempDir, context);
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
// Check if this part+config already has a BOM item for this drawing
|
||||
var existingItemNo = await FindExistingItemNoAsync(exportRecord?.Id, item.PartName, item.Configuration);
|
||||
item.ItemNo = existingItemNo ?? await GetNextItemNumberAsync(drawingNumber);
|
||||
|
||||
var bomItem = new BomItem
|
||||
{
|
||||
ExportRecordId = exportRecord?.Id ?? 0,
|
||||
ItemNo = item.ItemNo,
|
||||
PartNo = item.FileName ?? item.PartName ?? "",
|
||||
SortOrder = 0,
|
||||
Qty = item.Quantity,
|
||||
TotalQty = item.Quantity,
|
||||
Description = item.Description ?? "",
|
||||
PartName = item.PartName ?? "",
|
||||
ConfigurationName = item.Configuration ?? "",
|
||||
Material = item.Material ?? ""
|
||||
};
|
||||
|
||||
// Upload DXF to API and get stored path
|
||||
if (!string.IsNullOrEmpty(item.LocalTempPath))
|
||||
{
|
||||
var uploadResult = await UploadDxfAsync(item, context);
|
||||
if (uploadResult != null)
|
||||
{
|
||||
bomItem.CutTemplate = new CutTemplate
|
||||
{
|
||||
DxfFilePath = uploadResult.StoredFilePath,
|
||||
ContentHash = item.ContentHash,
|
||||
Thickness = item.Thickness > 0 ? item.Thickness : null,
|
||||
KFactor = item.KFactor > 0 ? item.KFactor : null,
|
||||
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
context.BomItemCallback?.Invoke(bomItem);
|
||||
|
||||
if (exportRecord != null)
|
||||
await SaveBomItemAsync(exportRecord.Id, bomItem, context);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportAssembly(ExportContext context)
|
||||
private async Task ExportAssemblyAsync(ExportContext context, string tempDir, string drawingNumber)
|
||||
{
|
||||
LogProgress(context, "Active document is an Assembly", null);
|
||||
LogProgress(context, "Fetching components...", null);
|
||||
LogProgress(context, "Active document is an Assembly");
|
||||
LogProgress(context, "Fetching components...");
|
||||
|
||||
var assembly = context.ActiveDocument.NativeDocument as AssemblyDoc;
|
||||
if (assembly == null)
|
||||
{
|
||||
LogProgress(context, "Failed to get assembly document.", Color.Red);
|
||||
LogProgress(context, "Failed to get assembly document.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,151 +174,155 @@ namespace ExportDXF.Services
|
||||
|
||||
if (items == null || items.Count == 0)
|
||||
{
|
||||
LogProgress(context, "No items found in assembly.", Color.DarkBlue);
|
||||
LogProgress(context, "No items found in assembly.", LogLevel.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
LogProgress(context, $"Found {items.Count} item(s).", null);
|
||||
LogProgress(context, $"Found {items.Count} item(s).");
|
||||
|
||||
var tempDir = CreateTempWorkDir();
|
||||
ExportItems(items, tempDir, context, drawingId: null);
|
||||
var exportRecord = await CreateExportRecordAsync(context, drawingNumber);
|
||||
|
||||
// Check existing BOM items and reuse item numbers, or assign new ones
|
||||
var nextNum = int.Parse(await GetNextItemNumberAsync(drawingNumber));
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||
{
|
||||
var existingItemNo = await FindExistingItemNoAsync(exportRecord?.Id, item.PartName, item.Configuration);
|
||||
if (existingItemNo != null)
|
||||
{
|
||||
item.ItemNo = existingItemNo;
|
||||
}
|
||||
else
|
||||
{
|
||||
item.ItemNo = nextNum.ToString();
|
||||
nextNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await ExportItemsAsync(items, tempDir, context, exportRecord?.Id);
|
||||
}
|
||||
|
||||
private void ExportDrawing(ExportContext context)
|
||||
private async Task ExportDrawingAsync(ExportContext context, string drawingNumber, string tempDir)
|
||||
{
|
||||
LogProgress(context, "Active document is a Drawing", null);
|
||||
LogProgress(context, "Finding BOM tables...", null);
|
||||
LogProgress(context, "Active document is a Drawing");
|
||||
|
||||
var drawing = context.ActiveDocument.NativeDocument as DrawingDoc;
|
||||
if (drawing == null)
|
||||
{
|
||||
LogProgress(context, "Failed to get drawing document.", Color.Red);
|
||||
LogProgress(context, "Failed to get drawing document.", LogLevel.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Export drawing to PDF in temp dir
|
||||
_drawingExporter.ExportToPdf(drawing, tempDir, context);
|
||||
|
||||
// Create export record via API
|
||||
var exportRecord = await CreateExportRecordAsync(context, drawingNumber);
|
||||
|
||||
// Upload PDF to API with versioning
|
||||
try
|
||||
{
|
||||
var pdfs = Directory.GetFiles(tempDir, "*.pdf");
|
||||
if (pdfs.Length > 0)
|
||||
{
|
||||
var pdfTempPath = pdfs[0];
|
||||
var pdfHash = ContentHasher.ComputePdfContentHash(pdfTempPath);
|
||||
|
||||
var uploadResult = await _apiClient.UploadPdfAsync(
|
||||
pdfTempPath,
|
||||
context.Equipment,
|
||||
context.DrawingNo,
|
||||
pdfHash,
|
||||
exportRecord?.Id);
|
||||
|
||||
if (exportRecord != null)
|
||||
await _apiClient.UpdatePdfHashAsync(exportRecord.Id, pdfHash);
|
||||
|
||||
if (uploadResult != null)
|
||||
{
|
||||
if (uploadResult.WasUnchanged)
|
||||
LogProgress(context, $"PDF unchanged: {uploadResult.FileName}", LogLevel.Info);
|
||||
else if (uploadResult.IsNewFile)
|
||||
LogProgress(context, $"Saved PDF: {uploadResult.FileName}", LogLevel.Info);
|
||||
else
|
||||
LogProgress(context, $"PDF updated: {uploadResult.FileName}", LogLevel.Info);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"PDF upload error: {ex.Message}", LogLevel.Error);
|
||||
}
|
||||
|
||||
// Extract BOM items from drawing tables
|
||||
LogProgress(context, "Finding BOM tables...");
|
||||
var items = _bomExtractor.ExtractFromDrawing(drawing, context.ProgressCallback);
|
||||
|
||||
if (items == null || items.Count == 0)
|
||||
if (items != null && items.Count > 0)
|
||||
{
|
||||
LogProgress(context, "Error: Bill of materials not found.", Color.Red);
|
||||
return;
|
||||
LogProgress(context, $"Found {items.Count} component(s)");
|
||||
await ExportItemsAsync(items, tempDir, context, exportRecord?.Id);
|
||||
}
|
||||
|
||||
LogProgress(context, $"Found {items.Count} component(s)", null);
|
||||
|
||||
var tempDir = CreateTempWorkDir();
|
||||
|
||||
// Determine drawing number
|
||||
var drawingNumber = ParseDrawingNumber(context);
|
||||
if (string.IsNullOrWhiteSpace(drawingNumber))
|
||||
else
|
||||
{
|
||||
LogProgress(context, "Warning: Could not determine drawing number for API upload.", Color.DarkBlue);
|
||||
}
|
||||
// No BOM table — fall back to exporting the part referenced by the drawing views
|
||||
LogProgress(context, "No BOM table found. Checking drawing views for referenced part...");
|
||||
|
||||
// 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)
|
||||
var (part, configuration) = GetReferencedPartFromViews(drawing);
|
||||
if (part == null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
LogProgress(context, "No referenced part found in drawing views.", LogLevel.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// Export drawing to PDF first
|
||||
_drawingExporter.ExportToPdf(drawing, tempDir, context);
|
||||
LogProgress(context, $"Found referenced part, exporting as single part...");
|
||||
|
||||
// Upload PDF if we have a drawing number
|
||||
try
|
||||
var item = _partExporter.ExportSinglePart(part, tempDir, context);
|
||||
if (item != null)
|
||||
{
|
||||
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 (!string.IsNullOrEmpty(configuration))
|
||||
item.Configuration = configuration;
|
||||
|
||||
// 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)
|
||||
var existingItemNo = await FindExistingItemNoAsync(exportRecord?.Id, item.PartName, item.Configuration);
|
||||
item.ItemNo = existingItemNo ?? await GetNextItemNumberAsync(drawingNumber);
|
||||
|
||||
var bomItem = new BomItem
|
||||
{
|
||||
try
|
||||
ExportRecordId = exportRecord?.Id ?? 0,
|
||||
ItemNo = item.ItemNo,
|
||||
PartNo = item.FileName ?? item.PartName ?? "",
|
||||
SortOrder = 0,
|
||||
Qty = item.Quantity,
|
||||
TotalQty = item.Quantity,
|
||||
Description = item.Description ?? "",
|
||||
PartName = item.PartName ?? "",
|
||||
ConfigurationName = item.Configuration ?? "",
|
||||
Material = item.Material ?? ""
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(item.LocalTempPath))
|
||||
{
|
||||
var uploadResult = await UploadDxfAsync(item, context);
|
||||
if (uploadResult != null)
|
||||
{
|
||||
var drawings = _apiClient.GetDrawingsForEquipmentAsync(context.EquipmentId.Value).GetAwaiter().GetResult();
|
||||
if (drawings != null)
|
||||
bomItem.CutTemplate = new CutTemplate
|
||||
{
|
||||
var match = drawings.FirstOrDefault(d => string.Equals(d.DrawingNumber?.Trim(), drawingNumber.Trim(), StringComparison.OrdinalIgnoreCase));
|
||||
if (match != null) resolved = match.ID;
|
||||
}
|
||||
DxfFilePath = uploadResult.StoredFilePath,
|
||||
ContentHash = item.ContentHash,
|
||||
Thickness = item.Thickness > 0 ? item.Thickness : null,
|
||||
KFactor = item.KFactor > 0 ? item.KFactor : null,
|
||||
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null
|
||||
};
|
||||
}
|
||||
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);
|
||||
context.BomItemCallback?.Invoke(bomItem);
|
||||
|
||||
// Attempt to auto-link templates at the end
|
||||
try
|
||||
{
|
||||
if (drawingId.HasValue)
|
||||
{
|
||||
_apiClient.AutoLinkTemplatesAsync(drawingId.Value).GetAwaiter().GetResult();
|
||||
}
|
||||
if (exportRecord != null)
|
||||
await SaveBomItemAsync(exportRecord.Id, bomItem, context);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +343,7 @@ namespace ExportDXF.Services
|
||||
// Set up drawing template path
|
||||
context.TemplateDrawing = null;
|
||||
|
||||
LogProgress(context, "Export context initialized", null);
|
||||
LogProgress(context, "Export context initialized");
|
||||
}
|
||||
|
||||
private void CleanupExportContext(ExportContext context)
|
||||
@@ -305,7 +355,7 @@ namespace ExportDXF.Services
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"Warning: Failed to cleanup template drawing: {ex.Message}", Color.DarkBlue);
|
||||
LogProgress(context, $"Warning: Failed to cleanup template drawing: {ex.Message}", LogLevel.Warning);
|
||||
// Don't throw - this is cleanup code
|
||||
}
|
||||
}
|
||||
@@ -327,102 +377,236 @@ namespace ExportDXF.Services
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"Failed to extract items from assembly: {ex.Message}", Color.Red);
|
||||
LogProgress(context, $"Failed to extract items from assembly: {ex.Message}", LogLevel.Error);
|
||||
return new List<Item>();
|
||||
}
|
||||
}
|
||||
|
||||
private void ExportItems(List<Item> items, string saveDirectory, ExportContext context, int? drawingId)
|
||||
private async Task ExportItemsAsync(List<Item> items, string tempDir, ExportContext context, int? exportRecordId = null)
|
||||
{
|
||||
LogProgress(context, "", null);
|
||||
|
||||
int successCount = 0;
|
||||
int skippedCount = 0;
|
||||
int failureCount = 0;
|
||||
int sortOrder = 0;
|
||||
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (context.CancellationToken.IsCancellationRequested)
|
||||
{
|
||||
LogProgress(context, "Export canceled by user.", Color.DarkBlue);
|
||||
LogProgress(context, "Export canceled by user.", LogLevel.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// PartExporter will handle template drawing creation through context
|
||||
_partExporter.ExportItem(item, saveDirectory, context);
|
||||
_partExporter.ExportItem(item, tempDir, context);
|
||||
|
||||
// Always create BOM item first if we have a drawing
|
||||
if (drawingId.HasValue)
|
||||
// Always create BomItem for every item (sheet metal or not)
|
||||
var bomItem = new BomItem
|
||||
{
|
||||
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);
|
||||
}
|
||||
ExportRecordId = exportRecordId ?? 0,
|
||||
ItemNo = item.ItemNo ?? "",
|
||||
PartNo = item.FileName ?? item.PartName ?? "",
|
||||
SortOrder = sortOrder++,
|
||||
Qty = item.Quantity,
|
||||
TotalQty = item.Quantity,
|
||||
Description = item.Description ?? "",
|
||||
PartName = item.PartName ?? "",
|
||||
ConfigurationName = item.Configuration ?? "",
|
||||
Material = item.Material ?? ""
|
||||
};
|
||||
|
||||
if (!string.IsNullOrEmpty(item.FileName))
|
||||
// Only upload and create CutTemplate if DXF was exported successfully
|
||||
if (!string.IsNullOrEmpty(item.LocalTempPath))
|
||||
{
|
||||
successCount++;
|
||||
|
||||
// If we know the drawing, upload DXF
|
||||
if (drawingId.HasValue)
|
||||
var uploadResult = await UploadDxfAsync(item, context);
|
||||
if (uploadResult != null)
|
||||
{
|
||||
var dxfPath = Path.Combine(saveDirectory, item.FileName + ".dxf");
|
||||
if (File.Exists(dxfPath))
|
||||
bomItem.CutTemplate = new CutTemplate
|
||||
{
|
||||
// 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 { }
|
||||
}
|
||||
}
|
||||
DxfFilePath = uploadResult.StoredFilePath,
|
||||
ContentHash = item.ContentHash,
|
||||
Thickness = item.Thickness > 0 ? item.Thickness : null,
|
||||
KFactor = item.KFactor > 0 ? item.KFactor : null,
|
||||
DefaultBendRadius = item.BendRadius > 0 ? item.BendRadius : null
|
||||
};
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
failureCount++;
|
||||
skippedCount++;
|
||||
}
|
||||
|
||||
// Add to UI
|
||||
context.BomItemCallback?.Invoke(bomItem);
|
||||
|
||||
// Save BOM item via API if we have an export record
|
||||
if (exportRecordId.HasValue)
|
||||
{
|
||||
await SaveBomItemAsync(exportRecordId.Value, bomItem, context);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", Color.Red);
|
||||
LogProgress(context, $"Error exporting item {item.ItemNo}: {ex.Message}", LogLevel.Error);
|
||||
failureCount++;
|
||||
}
|
||||
|
||||
LogProgress(context, "", null);
|
||||
}
|
||||
|
||||
LogProgress(context, $"Export complete: {successCount} succeeded, {failureCount} failed",
|
||||
failureCount > 0 ? Color.DarkBlue : Color.Green);
|
||||
var summary = $"Export complete: {successCount} exported, {skippedCount} skipped";
|
||||
if (failureCount > 0)
|
||||
summary += $", {failureCount} failed";
|
||||
LogProgress(context, summary, failureCount > 0 ? LogLevel.Warning : LogLevel.Info);
|
||||
|
||||
if (exportRecordId.HasValue)
|
||||
{
|
||||
LogProgress(context, $"BOM items saved (ExportRecord ID: {exportRecordId.Value})", LogLevel.Info);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Temp + Upload Helpers
|
||||
#region File Upload
|
||||
|
||||
private async Task<ApiFileUploadResponse> UploadDxfAsync(Item item, ExportContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _apiClient.UploadDxfAsync(
|
||||
item.LocalTempPath,
|
||||
context.Equipment,
|
||||
context.DrawingNo,
|
||||
item.ItemNo,
|
||||
item.ContentHash);
|
||||
|
||||
if (result.WasUnchanged)
|
||||
LogProgress(context, $"DXF unchanged: {result.FileName}", LogLevel.Info);
|
||||
else if (result.IsNewFile)
|
||||
LogProgress(context, $"Exported: {result.FileName}", LogLevel.Info);
|
||||
else
|
||||
LogProgress(context, $"DXF updated: {result.FileName}", LogLevel.Info);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"DXF upload failed for {item.FileName}: {ex.Message}", LogLevel.Warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region API Helpers
|
||||
|
||||
private async Task<ExportRecord> CreateExportRecordAsync(ExportContext context, string drawingNumber)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dto = await _apiClient.CreateExportAsync(
|
||||
drawingNumber ?? context.ActiveDocument.Title,
|
||||
context.Equipment ?? "",
|
||||
context.DrawingNo ?? "",
|
||||
context.ActiveDocument.FilePath,
|
||||
"", // Output folder is now managed by the API
|
||||
context.Title);
|
||||
|
||||
var record = new ExportRecord
|
||||
{
|
||||
Id = dto.Id,
|
||||
DrawingNumber = dto.DrawingNumber,
|
||||
EquipmentNo = dto.EquipmentNo,
|
||||
DrawingNo = dto.DrawingNo,
|
||||
SourceFilePath = dto.SourceFilePath,
|
||||
OutputFolder = dto.OutputFolder,
|
||||
ExportedAt = dto.ExportedAt,
|
||||
ExportedBy = dto.ExportedBy
|
||||
};
|
||||
|
||||
LogProgress(context, $"Created export record (ID: {record.Id})", LogLevel.Info);
|
||||
return record;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"API error creating export record: {ex.Message}", LogLevel.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> FindExistingItemNoAsync(int? exportRecordId, string partName, string configurationName)
|
||||
{
|
||||
if (!exportRecordId.HasValue)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
var existing = await _apiClient.FindExistingBomItemAsync(exportRecordId.Value, partName, configurationName);
|
||||
return existing?.ItemNo;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetNextItemNumberAsync(string drawingNumber)
|
||||
{
|
||||
if (string.IsNullOrEmpty(drawingNumber))
|
||||
return "1";
|
||||
|
||||
try
|
||||
{
|
||||
return await _apiClient.GetNextItemNumberAsync(drawingNumber);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "1";
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveBomItemAsync(int exportRecordId, BomItem bomItem, ExportContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var apiBomItem = new ApiBomItem
|
||||
{
|
||||
ItemNo = bomItem.ItemNo,
|
||||
PartNo = bomItem.PartNo,
|
||||
SortOrder = bomItem.SortOrder,
|
||||
Qty = bomItem.Qty,
|
||||
TotalQty = bomItem.TotalQty,
|
||||
Description = bomItem.Description,
|
||||
PartName = bomItem.PartName,
|
||||
ConfigurationName = bomItem.ConfigurationName,
|
||||
Material = bomItem.Material
|
||||
};
|
||||
|
||||
if (bomItem.CutTemplate != null)
|
||||
{
|
||||
apiBomItem.CutTemplate = new ApiCutTemplate
|
||||
{
|
||||
DxfFilePath = bomItem.CutTemplate.DxfFilePath,
|
||||
ContentHash = bomItem.CutTemplate.ContentHash,
|
||||
Thickness = bomItem.CutTemplate.Thickness,
|
||||
KFactor = bomItem.CutTemplate.KFactor,
|
||||
DefaultBendRadius = bomItem.CutTemplate.DefaultBendRadius
|
||||
};
|
||||
}
|
||||
|
||||
await _apiClient.CreateBomItemAsync(exportRecordId, apiBomItem);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogProgress(context, $"API error saving BOM item: {ex.Message}", LogLevel.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
private string CreateTempWorkDir()
|
||||
{
|
||||
@@ -431,9 +615,30 @@ namespace ExportDXF.Services
|
||||
return path;
|
||||
}
|
||||
|
||||
private void CleanupTempDir(string tempDir)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(tempDir))
|
||||
Directory.Delete(tempDir, recursive: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Best-effort cleanup
|
||||
}
|
||||
}
|
||||
|
||||
private string ParseDrawingNumber(ExportContext context)
|
||||
{
|
||||
// Prefer prefix (e.g., "5007 A02 PT"), fallback to active document title
|
||||
// Use explicit Equipment/DrawingNo from the UI when available
|
||||
if (!string.IsNullOrWhiteSpace(context?.Equipment))
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(context?.DrawingNo)
|
||||
? $"{context.Equipment} {context.DrawingNo}"
|
||||
: context.Equipment;
|
||||
}
|
||||
|
||||
// Fallback: parse from prefix or document title
|
||||
var candidate = context?.FilePrefix;
|
||||
var info = string.IsNullOrWhiteSpace(candidate) ? null : DrawingInfo.Parse(candidate);
|
||||
if (info == null)
|
||||
@@ -441,24 +646,9 @@ namespace ExportDXF.Services
|
||||
var title = context?.ActiveDocument?.Title;
|
||||
info = string.IsNullOrWhiteSpace(title) ? null : DrawingInfo.Parse(title);
|
||||
}
|
||||
return info != null ? ($"{info.EquipmentNo} {info.DrawingNo}") : null;
|
||||
return info?.ToString();
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -468,15 +658,29 @@ namespace ExportDXF.Services
|
||||
throw new ArgumentException("ProgressCallback cannot be null.", nameof(context));
|
||||
}
|
||||
|
||||
private void LogProgress(ExportContext context, string message, Color? color)
|
||||
private (PartDoc part, string configuration) GetReferencedPartFromViews(DrawingDoc drawing)
|
||||
{
|
||||
context.ProgressCallback?.Invoke(message, color);
|
||||
var view = (IView)drawing.GetFirstView();
|
||||
// First view is the sheet itself — skip it
|
||||
view = (IView)view.GetNextView();
|
||||
|
||||
while (view != null)
|
||||
{
|
||||
var doc = view.ReferencedDocument;
|
||||
if (doc is PartDoc part)
|
||||
return (part, view.ReferencedConfiguration);
|
||||
|
||||
view = (IView)view.GetNextView();
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
|
||||
private void LogProgress(ExportContext context, string message, LogLevel level = LogLevel.Info, string file = null)
|
||||
{
|
||||
context.ProgressCallback?.Invoke(message, level, file);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.Extensions;
|
||||
using ExportDXF.Models;
|
||||
using ExportDXF.Utilities;
|
||||
using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
@@ -15,24 +15,29 @@ namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Exports a single part document to DXF.
|
||||
/// Returns an Item with export metadata (filename, hash, sheet metal properties), or null if export failed.
|
||||
/// </summary>
|
||||
/// <param name="part">The part document to export.</param>
|
||||
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param>
|
||||
/// <param name="saveDirectory">The temp directory where the DXF file will be saved.</param>
|
||||
/// <param name="context">The export context.</param>
|
||||
void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
||||
Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context);
|
||||
|
||||
/// <summary>
|
||||
/// Exports an item (component from BOM or assembly) to DXF.
|
||||
/// </summary>
|
||||
/// <param name="item">The item to export.</param>
|
||||
/// <param name="saveDirectory">The directory where the DXF file will be saved.</param>
|
||||
/// <param name="saveDirectory">The temp directory where the DXF file will be saved.</param>
|
||||
/// <param name="context">The export context.</param>
|
||||
void ExportItem(Item item, string saveDirectory, ExportContext context);
|
||||
}
|
||||
|
||||
public class PartExporter : IPartExporter
|
||||
{
|
||||
public void ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
|
||||
public PartExporter()
|
||||
{
|
||||
}
|
||||
|
||||
public Item ExportSinglePart(PartDoc part, string saveDirectory, ExportContext context)
|
||||
{
|
||||
if (part == null)
|
||||
throw new ArgumentNullException(nameof(part));
|
||||
@@ -49,12 +54,52 @@ namespace ExportDXF.Services
|
||||
|
||||
try
|
||||
{
|
||||
var fileName = GetSinglePartFileName(model, context.FilePrefix);
|
||||
var fileName = GetSinglePartFileName(model, context.Equipment);
|
||||
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
||||
|
||||
// Build result item with metadata
|
||||
var item = new Item
|
||||
{
|
||||
PartName = model.GetTitle()?.Replace(".SLDPRT", "") ?? "",
|
||||
Configuration = originalConfigName ?? "",
|
||||
Quantity = 1
|
||||
};
|
||||
|
||||
// Enrich with sheet metal properties and description
|
||||
var sheetMetalProps = SolidWorksHelper.GetSheetMetalProperties(model);
|
||||
if (sheetMetalProps != null)
|
||||
{
|
||||
item.Thickness = sheetMetalProps.Thickness;
|
||||
item.KFactor = sheetMetalProps.KFactor;
|
||||
item.BendRadius = sheetMetalProps.BendRadius;
|
||||
}
|
||||
|
||||
// Get description from custom properties
|
||||
var configPropMgr = model.Extension.CustomPropertyManager[originalConfigName];
|
||||
item.Description = configPropMgr?.Get("Description");
|
||||
if (string.IsNullOrEmpty(item.Description))
|
||||
{
|
||||
var docPropMgr = model.Extension.CustomPropertyManager[""];
|
||||
item.Description = docPropMgr?.Get("Description");
|
||||
}
|
||||
item.Description = TextHelper.RemoveXmlTags(item.Description);
|
||||
|
||||
// Get material
|
||||
item.Material = part.GetMaterialPropertyName2(originalConfigName, out _);
|
||||
|
||||
context.GetOrCreateTemplateDrawing();
|
||||
|
||||
ExportPartToDxf(part, originalConfigName, savePath, context);
|
||||
if (ExportPartToDxf(part, originalConfigName, savePath, context))
|
||||
{
|
||||
item.FileName = Path.GetFileNameWithoutExtension(savePath);
|
||||
item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath);
|
||||
item.LocalTempPath = savePath;
|
||||
return item;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -75,7 +120,7 @@ namespace ExportDXF.Services
|
||||
|
||||
if (item?.Component == null)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"Item {item?.ItemNo} - skipped, no component", Color.Yellow);
|
||||
context.ProgressCallback?.Invoke("Skipped, no component", LogLevel.Warning, $"Item {item?.ItemNo}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,13 +133,13 @@ namespace ExportDXF.Services
|
||||
|
||||
if (part == null)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"{item.ItemNo} - skipped, not a part document", null);
|
||||
context.ProgressCallback?.Invoke("Skipped, not a part document", LogLevel.Info, item.PartName);
|
||||
return;
|
||||
}
|
||||
|
||||
EnrichItemWithMetadata(item, model, part);
|
||||
|
||||
var fileName = GetItemFileName(item, context.FilePrefix);
|
||||
var fileName = GetItemFileName(item, context);
|
||||
var savePath = Path.Combine(saveDirectory, fileName + ".dxf");
|
||||
|
||||
var templateDrawing = context.GetOrCreateTemplateDrawing();
|
||||
@@ -102,6 +147,8 @@ namespace ExportDXF.Services
|
||||
if (ExportPartToDxf(part, item.Component.ReferencedConfiguration, savePath, context))
|
||||
{
|
||||
item.FileName = Path.GetFileNameWithoutExtension(savePath);
|
||||
item.ContentHash = Utilities.ContentHasher.ComputeDxfContentHash(savePath);
|
||||
item.LocalTempPath = savePath;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -150,9 +197,11 @@ namespace ExportDXF.Services
|
||||
{
|
||||
var model = part as ModelDoc2;
|
||||
|
||||
var partTitle = model.GetTitle();
|
||||
|
||||
if (!model.IsSheetMetal())
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - skipped, not sheet metal", null);
|
||||
context.ProgressCallback?.Invoke("Skipped, not sheet metal", LogLevel.Info, partTitle);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -164,13 +213,13 @@ namespace ExportDXF.Services
|
||||
var modelName = Path.GetFileNameWithoutExtension(model.GetPathName());
|
||||
sheet.SetName(modelName);
|
||||
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Creating flat pattern", null);
|
||||
context.ProgressCallback?.Invoke("Creating flat pattern", LogLevel.Info, partTitle);
|
||||
|
||||
var view = CreateFlatPatternView(templateDrawing, model, configName);
|
||||
|
||||
if (view == null)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Failed to create flat pattern", Color.Red);
|
||||
context.ProgressCallback?.Invoke("Failed to create flat pattern", LogLevel.Error, partTitle);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -178,16 +227,16 @@ namespace ExportDXF.Services
|
||||
|
||||
if (context.ViewFlipDecider?.ShouldFlip(view) == true)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Flipped view", Color.Blue);
|
||||
context.ProgressCallback?.Invoke("Flipped view", LogLevel.Info, partTitle);
|
||||
view.FlipView = true;
|
||||
}
|
||||
|
||||
var drawingModel = templateDrawing as ModelDoc2;
|
||||
drawingModel.SaveAs(savePath);
|
||||
|
||||
AddEtchLines(savePath);
|
||||
AddEtchLines(savePath, context);
|
||||
|
||||
context.ProgressCallback?.Invoke($"{model.GetTitle()} - Saved to \"{savePath}\"", Color.Green);
|
||||
context.ProgressCallback?.Invoke($"Saved to \"{savePath}\"", LogLevel.Info, partTitle);
|
||||
|
||||
DeleteView(drawingModel, view);
|
||||
|
||||
@@ -195,7 +244,7 @@ namespace ExportDXF.Services
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
context.ProgressCallback?.Invoke($"Export failed: {ex.Message}", Color.Red);
|
||||
context.ProgressCallback?.Invoke($"Export failed: {ex.Message}", LogLevel.Error, null);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -232,7 +281,8 @@ namespace ExportDXF.Services
|
||||
|
||||
if (ViewHelper.HasSupressedBends(view))
|
||||
{
|
||||
context.ProgressCallback?.Invoke("A bend is suppressed, please check flat pattern", Color.Red);
|
||||
var title = partModel.GetTitle();
|
||||
context.ProgressCallback?.Invoke("A bend is suppressed, please check flat pattern", LogLevel.Error, title);
|
||||
}
|
||||
|
||||
if (ViewHelper.HideModelSketches(view))
|
||||
@@ -251,40 +301,64 @@ namespace ExportDXF.Services
|
||||
drawing.DeleteSelection(false);
|
||||
}
|
||||
|
||||
private void AddEtchLines(string dxfPath)
|
||||
private void AddEtchLines(string dxfPath, ExportContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var etcher = new EtchBendLines.Etcher();
|
||||
etcher.AddEtchLines(dxfPath);
|
||||
FixDegreeSymbol(dxfPath);
|
||||
}
|
||||
catch (Exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Silently fail if etch lines can't be added
|
||||
context.ProgressCallback?.Invoke($"Etch lines failed: {ex.Message}", LogLevel.Warning, Path.GetFileName(dxfPath));
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSinglePartFileName(ModelDoc2 model, string prefix)
|
||||
/// <summary>
|
||||
/// Workaround for ACadSharp encoding bug (no upstream fix as of v3.4.9).
|
||||
/// ACadSharp's DxfReader uses $DWGCODEPAGE (ANSI_1252) to decode text, but
|
||||
/// AC1018+ DXF files use UTF-8. The degree symbol ° (UTF-8: C2 B0) gets
|
||||
/// misread as two ANSI_1252 characters: Â (C2) and ° (B0).
|
||||
/// See: https://github.com/DomCR/ACadSharp/issues?q=encoding
|
||||
/// </summary>
|
||||
private static void FixDegreeSymbol(string path)
|
||||
{
|
||||
var text = System.IO.File.ReadAllText(path);
|
||||
if (text.Contains("\u00C2\u00B0"))
|
||||
{
|
||||
text = text.Replace("\u00C2\u00B0", "\u00B0");
|
||||
System.IO.File.WriteAllText(path, text);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetSinglePartFileName(ModelDoc2 model, string equipment)
|
||||
{
|
||||
var title = model.GetTitle().Replace(".SLDPRT", "");
|
||||
var config = model.ConfigurationManager.ActiveConfiguration.Name;
|
||||
var isDefaultConfig = string.Equals(config, "default", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
var name = isDefaultConfig ? title : $"{title} [{config}]";
|
||||
return prefix + name;
|
||||
return string.IsNullOrWhiteSpace(equipment) ? name : $"{equipment} {name}";
|
||||
}
|
||||
|
||||
private string GetItemFileName(Item item, string prefix)
|
||||
private string GetItemFileName(Item item, ExportContext context)
|
||||
{
|
||||
prefix = prefix?.Replace("\"", "''") ?? string.Empty;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||
if (string.IsNullOrWhiteSpace(context.DrawingNo))
|
||||
{
|
||||
return prefix + item.PartName;
|
||||
// No drawing number: preserve part name, prefix with EquipmentNo
|
||||
var equipment = context.Equipment;
|
||||
return string.IsNullOrWhiteSpace(equipment)
|
||||
? item.PartName
|
||||
: $"{equipment} {item.PartName}";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(item.ItemNo))
|
||||
return item.PartName;
|
||||
|
||||
var prefix = context.FilePrefix?.Replace("\"", "''") ?? string.Empty;
|
||||
var num = item.ItemNo.PadLeft(2, '0');
|
||||
// Expected format: {DrawingNo} PT{ItemNo}
|
||||
// Expected format: {EquipNo} {DrawingNo} PT{ItemNo}
|
||||
return string.IsNullOrWhiteSpace(prefix)
|
||||
? $"PT{num}"
|
||||
: $"{prefix} PT{num}";
|
||||
@@ -297,14 +371,16 @@ namespace ExportDXF.Services
|
||||
if (desc.Contains("laser"))
|
||||
{
|
||||
context.ProgressCallback?.Invoke(
|
||||
$"Failed to export item #{item.ItemNo} but description says it is laser cut.",
|
||||
Color.Red);
|
||||
"Export failed but description says it is laser cut",
|
||||
LogLevel.Error,
|
||||
item.PartName);
|
||||
}
|
||||
else if (desc.Contains("plasma"))
|
||||
{
|
||||
context.ProgressCallback?.Invoke(
|
||||
$"Failed to export item #{item.ItemNo} but description says it is plasma cut.",
|
||||
Color.Red);
|
||||
"Export failed but description says it is plasma cut",
|
||||
LogLevel.Error,
|
||||
item.PartName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,69 @@ using SolidWorks.Interop.sldworks;
|
||||
using SolidWorks.Interop.swconst;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace ExportDXF.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper class to get COM objects from Running Object Table (ROT) in .NET Core/5+
|
||||
/// </summary>
|
||||
internal static class ComHelper
|
||||
{
|
||||
[DllImport("ole32.dll")]
|
||||
private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable rot);
|
||||
|
||||
[DllImport("ole32.dll")]
|
||||
private static extern int CreateBindCtx(int reserved, out IBindCtx bindCtx);
|
||||
|
||||
public static object GetActiveObject(string progId)
|
||||
{
|
||||
IRunningObjectTable rot = null;
|
||||
IEnumMoniker enumMoniker = null;
|
||||
IBindCtx bindCtx = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (GetRunningObjectTable(0, out rot) != 0 || rot == null)
|
||||
return null;
|
||||
|
||||
rot.EnumRunning(out enumMoniker);
|
||||
if (enumMoniker == null)
|
||||
return null;
|
||||
|
||||
if (CreateBindCtx(0, out bindCtx) != 0 || bindCtx == null)
|
||||
return null;
|
||||
|
||||
IMoniker[] monikers = new IMoniker[1];
|
||||
IntPtr fetched = IntPtr.Zero;
|
||||
|
||||
while (enumMoniker.Next(1, monikers, fetched) == 0)
|
||||
{
|
||||
monikers[0].GetDisplayName(bindCtx, null, out string displayName);
|
||||
|
||||
if (displayName != null && displayName.IndexOf(progId, StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
{
|
||||
rot.GetObject(monikers[0], out object obj);
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (enumMoniker != null) Marshal.ReleaseComObject(enumMoniker);
|
||||
if (rot != null) Marshal.ReleaseComObject(rot);
|
||||
if (bindCtx != null) Marshal.ReleaseComObject(bindCtx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing SolidWorks application connection and document lifecycle.
|
||||
/// </summary>
|
||||
@@ -29,6 +88,13 @@ namespace ExportDXF.Services
|
||||
/// <param name="enable">True to enable user control, false to disable.</param>
|
||||
void EnableUserControl(bool enable);
|
||||
|
||||
/// <summary>
|
||||
/// Sets whether a command is in progress. When true, user input to
|
||||
/// SolidWorks is disabled and interactive dialogs are suppressed.
|
||||
/// </summary>
|
||||
/// <param name="inProgress">True to block user input, false to re-enable.</param>
|
||||
void SetCommandInProgress(bool inProgress);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SolidWorks application instance.
|
||||
/// </summary>
|
||||
@@ -129,6 +195,15 @@ namespace ExportDXF.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetCommandInProgress(bool inProgress)
|
||||
{
|
||||
if (_sldWorks != null)
|
||||
{
|
||||
_sldWorks.CommandInProgress = inProgress;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the native SolidWorks application instance.
|
||||
/// Use this when you need direct access to the SolidWorks API.
|
||||
@@ -245,7 +320,7 @@ namespace ExportDXF.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
return Marshal.GetActiveObject("SldWorks.Application") as SldWorks;
|
||||
return ComHelper.GetActiveObject("SldWorks.Application") as SldWorks;
|
||||
}
|
||||
catch (COMException)
|
||||
{
|
||||
|
||||
202
ExportDXF/Utilities/ContentHasher.cs
Normal file
202
ExportDXF/Utilities/ContentHasher.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using ACadSharp.Entities;
|
||||
using ACadSharp.IO;
|
||||
using PDFtoImage;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
|
||||
namespace ExportDXF.Utilities
|
||||
{
|
||||
public static class ContentHasher
|
||||
{
|
||||
/// <summary>
|
||||
/// Computes a SHA256 hash of DXF geometry, ignoring entity ordering,
|
||||
/// handle assignments, style names, and floating-point epsilon differences
|
||||
/// that SolidWorks changes between re-exports of identical geometry.
|
||||
/// Falls back to a raw file hash if ACadSharp parsing fails.
|
||||
/// </summary>
|
||||
public static string ComputeDxfContentHash(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
return ComputeGeometricHash(filePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ComputeFileHash(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a perceptual hash of a PDF by rendering page 1 to an image,
|
||||
/// so only visual changes affect the hash (metadata/timestamp changes are ignored).
|
||||
/// Falls back to a raw file hash if rendering fails.
|
||||
/// </summary>
|
||||
public static string ComputePdfContentHash(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var pdfStream = File.OpenRead(filePath))
|
||||
using (var pngStream = new MemoryStream())
|
||||
{
|
||||
Conversion.SavePng(pngStream, pdfStream, page: 0,
|
||||
options: new RenderOptions(Dpi: 150));
|
||||
pngStream.Position = 0;
|
||||
|
||||
using (var image = Image.Load<Rgba32>(pngStream))
|
||||
{
|
||||
var hash = ComputeDifferenceHash(image);
|
||||
return hash.ToString("x16");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return ComputeFileHash(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Computes a SHA256 hash of the entire file contents (for PDFs and other binary files).
|
||||
/// </summary>
|
||||
public static string ComputeFileHash(string filePath)
|
||||
{
|
||||
using (var sha = SHA256.Create())
|
||||
using (var stream = File.OpenRead(filePath))
|
||||
{
|
||||
var bytes = sha.ComputeHash(stream);
|
||||
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DifferenceHash: resize to 9x8 grayscale, compare adjacent pixels.
|
||||
/// Produces a 64-bit hash. Implemented directly against ImageSharp 3.x API
|
||||
/// (CoenM.ImageHash uses the removed GetPixelRowSpan from ImageSharp 2.x).
|
||||
/// </summary>
|
||||
private static ulong ComputeDifferenceHash(Image<Rgba32> image)
|
||||
{
|
||||
// Resize to 9 wide x 8 tall for 8x8 = 64 bit comparisons
|
||||
image.Mutate(ctx => ctx.Resize(9, 8));
|
||||
|
||||
ulong hash = 0;
|
||||
int bit = 0;
|
||||
|
||||
for (int y = 0; y < 8; y++)
|
||||
{
|
||||
for (int x = 0; x < 8; x++)
|
||||
{
|
||||
var left = image[x, y];
|
||||
var right = image[x + 1, y];
|
||||
var leftGray = 0.299 * left.R + 0.587 * left.G + 0.114 * left.B;
|
||||
var rightGray = 0.299 * right.R + 0.587 * right.G + 0.114 * right.B;
|
||||
|
||||
if (leftGray > rightGray)
|
||||
hash |= (1UL << bit);
|
||||
|
||||
bit++;
|
||||
}
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
private static string ComputeGeometricHash(string filePath)
|
||||
{
|
||||
using (var reader = new DxfReader(filePath))
|
||||
{
|
||||
var doc = reader.Read();
|
||||
var signatures = new List<string>();
|
||||
|
||||
foreach (var entity in doc.Entities)
|
||||
{
|
||||
signatures.Add(GetEntitySignature(entity));
|
||||
}
|
||||
|
||||
signatures.Sort(StringComparer.Ordinal);
|
||||
var combined = string.Join("\n", signatures);
|
||||
|
||||
using (var sha = SHA256.Create())
|
||||
{
|
||||
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(combined));
|
||||
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetEntitySignature(Entity entity)
|
||||
{
|
||||
var layer = entity.Layer?.Name ?? "";
|
||||
|
||||
switch (entity)
|
||||
{
|
||||
case Line line:
|
||||
return GetLineSignature(line, layer);
|
||||
case Arc arc:
|
||||
return GetArcSignature(arc, layer);
|
||||
case Circle circle:
|
||||
return GetCircleSignature(circle, layer);
|
||||
case MText mtext:
|
||||
return GetMTextSignature(mtext, layer);
|
||||
default:
|
||||
return $"{entity.GetType().Name}|{layer}";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLineSignature(Line line, string layer)
|
||||
{
|
||||
var p1 = FormatPoint(line.StartPoint.X, line.StartPoint.Y);
|
||||
var p2 = FormatPoint(line.EndPoint.X, line.EndPoint.Y);
|
||||
|
||||
// Normalize endpoint order so direction doesn't affect the hash
|
||||
if (string.Compare(p1, p2, StringComparison.Ordinal) > 0)
|
||||
{
|
||||
var tmp = p1;
|
||||
p1 = p2;
|
||||
p2 = tmp;
|
||||
}
|
||||
|
||||
return $"LINE|{layer}|{p1}|{p2}";
|
||||
}
|
||||
|
||||
private static string GetArcSignature(Arc arc, string layer)
|
||||
{
|
||||
var center = FormatPoint(arc.Center.X, arc.Center.Y);
|
||||
var r = R(arc.Radius);
|
||||
var sa = R(arc.StartAngle);
|
||||
var ea = R(arc.EndAngle);
|
||||
return $"ARC|{layer}|{center}|{r}|{sa}|{ea}";
|
||||
}
|
||||
|
||||
private static string GetCircleSignature(Circle circle, string layer)
|
||||
{
|
||||
var center = FormatPoint(circle.Center.X, circle.Center.Y);
|
||||
var r = R(circle.Radius);
|
||||
return $"CIRCLE|{layer}|{center}|{r}";
|
||||
}
|
||||
|
||||
private static string GetMTextSignature(MText mtext, string layer)
|
||||
{
|
||||
var point = FormatPoint(mtext.InsertPoint.X, mtext.InsertPoint.Y);
|
||||
var text = mtext.Value ?? "";
|
||||
return $"MTEXT|{layer}|{point}|{text}";
|
||||
}
|
||||
|
||||
private static string R(double value)
|
||||
{
|
||||
return Math.Round(value, 4).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static string FormatPoint(double x, double y)
|
||||
{
|
||||
return $"{R(x)},{R(y)}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
</startup>
|
||||
<appSettings>
|
||||
<add key="MaxBendRadius" value="2.0"/>
|
||||
<!-- Deployed API base URL (default port from Deploy-CutFabApi.ps1) -->
|
||||
<add key="CutFab.ApiBaseUrl" value="http://localhost:7027"/>
|
||||
<add key="FabWorksApiUrl" value="http://localhost:5206"/>
|
||||
</appSettings>
|
||||
</configuration>
|
||||
|
||||
9
FabWorks.Api/Configuration/FileStorageOptions.cs
Normal file
9
FabWorks.Api/Configuration/FileStorageOptions.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FabWorks.Api.Configuration
|
||||
{
|
||||
public class FileStorageOptions
|
||||
{
|
||||
public const string SectionName = "FileStorage";
|
||||
|
||||
public string OutputFolder { get; set; } = @"C:\ExportDXF\Output";
|
||||
}
|
||||
}
|
||||
259
FabWorks.Api/Controllers/BomItemsController.cs
Normal file
259
FabWorks.Api/Controllers/BomItemsController.cs
Normal file
@@ -0,0 +1,259 @@
|
||||
using FabWorks.Api.DTOs;
|
||||
using FabWorks.Core.Data;
|
||||
using FabWorks.Core.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FabWorks.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/exports/{exportId}/bom-items")]
|
||||
public class BomItemsController : ControllerBase
|
||||
{
|
||||
private readonly FabWorksDbContext _db;
|
||||
|
||||
public BomItemsController(FabWorksDbContext db) => _db = db;
|
||||
|
||||
[HttpGet("find")]
|
||||
public async Task<ActionResult<BomItemDto>> FindExisting(int exportId, [FromQuery] string partName, [FromQuery] string configurationName)
|
||||
{
|
||||
var export = await _db.ExportRecords.FindAsync(exportId);
|
||||
if (export == null) return NotFound();
|
||||
|
||||
var existing = await _db.BomItems
|
||||
.Include(b => b.CutTemplate)
|
||||
.Include(b => b.FormProgram)
|
||||
.Include(b => b.ExportRecord)
|
||||
.Where(b => b.ExportRecord.DrawingNumber == export.DrawingNumber
|
||||
&& b.PartName == (partName ?? "")
|
||||
&& b.ConfigurationName == (configurationName ?? ""))
|
||||
.OrderByDescending(b => b.ID)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (existing == null) return NotFound();
|
||||
return MapToDto(existing);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<List<BomItemDto>>> GetByExport(int exportId)
|
||||
{
|
||||
var items = await _db.BomItems
|
||||
.Include(b => b.CutTemplate)
|
||||
.Include(b => b.FormProgram)
|
||||
.Where(b => b.ExportRecordId == exportId)
|
||||
.OrderBy(b => b.SortOrder)
|
||||
.ToListAsync();
|
||||
|
||||
return items.Select(MapToDto).ToList();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<BomItemDto>> Create(int exportId, BomItemDto dto)
|
||||
{
|
||||
var export = await _db.ExportRecords.FindAsync(exportId);
|
||||
if (export == null) return NotFound("Export record not found");
|
||||
|
||||
// Look up the latest CutTemplate for this drawing+item across all previous exports
|
||||
// to determine the revision number
|
||||
var newContentHash = dto.CutTemplate?.ContentHash;
|
||||
int revision = await ResolveRevisionAsync(export.DrawingNumber, dto.ItemNo, newContentHash);
|
||||
|
||||
// Look for existing BomItem with same PartName + ConfigurationName within this export record
|
||||
var existing = await _db.BomItems
|
||||
.Include(b => b.CutTemplate)
|
||||
.Include(b => b.FormProgram)
|
||||
.Where(b => b.ExportRecordId == exportId
|
||||
&& b.PartName == (dto.PartName ?? "")
|
||||
&& b.ConfigurationName == (dto.ConfigurationName ?? ""))
|
||||
.OrderByDescending(b => b.ID)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
// Update existing fields
|
||||
existing.PartNo = dto.PartNo ?? "";
|
||||
existing.SortOrder = dto.SortOrder;
|
||||
existing.Qty = dto.Qty;
|
||||
existing.TotalQty = dto.TotalQty;
|
||||
existing.Description = dto.Description ?? "";
|
||||
existing.Material = dto.Material ?? "";
|
||||
|
||||
if (dto.CutTemplate != null)
|
||||
{
|
||||
if (existing.CutTemplate != null)
|
||||
{
|
||||
existing.CutTemplate.DxfFilePath = dto.CutTemplate.DxfFilePath ?? "";
|
||||
existing.CutTemplate.ContentHash = dto.CutTemplate.ContentHash;
|
||||
existing.CutTemplate.Revision = revision;
|
||||
existing.CutTemplate.Thickness = dto.CutTemplate.Thickness;
|
||||
existing.CutTemplate.KFactor = dto.CutTemplate.KFactor;
|
||||
existing.CutTemplate.DefaultBendRadius = dto.CutTemplate.DefaultBendRadius;
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.CutTemplate = new CutTemplate
|
||||
{
|
||||
DxfFilePath = dto.CutTemplate.DxfFilePath ?? "",
|
||||
ContentHash = dto.CutTemplate.ContentHash,
|
||||
Revision = revision,
|
||||
Thickness = dto.CutTemplate.Thickness,
|
||||
KFactor = dto.CutTemplate.KFactor,
|
||||
DefaultBendRadius = dto.CutTemplate.DefaultBendRadius
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (dto.FormProgram != null)
|
||||
{
|
||||
if (existing.FormProgram != null)
|
||||
{
|
||||
existing.FormProgram.ProgramFilePath = dto.FormProgram.ProgramFilePath ?? "";
|
||||
existing.FormProgram.ContentHash = dto.FormProgram.ContentHash;
|
||||
existing.FormProgram.ProgramName = dto.FormProgram.ProgramName ?? "";
|
||||
existing.FormProgram.Thickness = dto.FormProgram.Thickness;
|
||||
existing.FormProgram.MaterialType = dto.FormProgram.MaterialType ?? "";
|
||||
existing.FormProgram.KFactor = dto.FormProgram.KFactor;
|
||||
existing.FormProgram.BendCount = dto.FormProgram.BendCount;
|
||||
existing.FormProgram.UpperToolNames = dto.FormProgram.UpperToolNames ?? "";
|
||||
existing.FormProgram.LowerToolNames = dto.FormProgram.LowerToolNames ?? "";
|
||||
existing.FormProgram.SetupNotes = dto.FormProgram.SetupNotes ?? "";
|
||||
}
|
||||
else
|
||||
{
|
||||
existing.FormProgram = new FormProgram
|
||||
{
|
||||
ProgramFilePath = dto.FormProgram.ProgramFilePath ?? "",
|
||||
ContentHash = dto.FormProgram.ContentHash,
|
||||
ProgramName = dto.FormProgram.ProgramName ?? "",
|
||||
Thickness = dto.FormProgram.Thickness,
|
||||
MaterialType = dto.FormProgram.MaterialType ?? "",
|
||||
KFactor = dto.FormProgram.KFactor,
|
||||
BendCount = dto.FormProgram.BendCount,
|
||||
UpperToolNames = dto.FormProgram.UpperToolNames ?? "",
|
||||
LowerToolNames = dto.FormProgram.LowerToolNames ?? "",
|
||||
SetupNotes = dto.FormProgram.SetupNotes ?? ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
return Ok(MapToDto(existing));
|
||||
}
|
||||
|
||||
// No existing match — create new
|
||||
var item = new BomItem
|
||||
{
|
||||
ExportRecordId = exportId,
|
||||
ItemNo = dto.ItemNo ?? "",
|
||||
PartNo = dto.PartNo ?? "",
|
||||
SortOrder = dto.SortOrder,
|
||||
Qty = dto.Qty,
|
||||
TotalQty = dto.TotalQty,
|
||||
Description = dto.Description ?? "",
|
||||
PartName = dto.PartName ?? "",
|
||||
ConfigurationName = dto.ConfigurationName ?? "",
|
||||
Material = dto.Material ?? ""
|
||||
};
|
||||
|
||||
if (dto.CutTemplate != null)
|
||||
{
|
||||
item.CutTemplate = new CutTemplate
|
||||
{
|
||||
DxfFilePath = dto.CutTemplate.DxfFilePath ?? "",
|
||||
ContentHash = dto.CutTemplate.ContentHash,
|
||||
Revision = revision,
|
||||
Thickness = dto.CutTemplate.Thickness,
|
||||
KFactor = dto.CutTemplate.KFactor,
|
||||
DefaultBendRadius = dto.CutTemplate.DefaultBendRadius
|
||||
};
|
||||
}
|
||||
|
||||
if (dto.FormProgram != null)
|
||||
{
|
||||
item.FormProgram = new FormProgram
|
||||
{
|
||||
ProgramFilePath = dto.FormProgram.ProgramFilePath ?? "",
|
||||
ContentHash = dto.FormProgram.ContentHash,
|
||||
ProgramName = dto.FormProgram.ProgramName ?? "",
|
||||
Thickness = dto.FormProgram.Thickness,
|
||||
MaterialType = dto.FormProgram.MaterialType ?? "",
|
||||
KFactor = dto.FormProgram.KFactor,
|
||||
BendCount = dto.FormProgram.BendCount,
|
||||
UpperToolNames = dto.FormProgram.UpperToolNames ?? "",
|
||||
LowerToolNames = dto.FormProgram.LowerToolNames ?? "",
|
||||
SetupNotes = dto.FormProgram.SetupNotes ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
_db.BomItems.Add(item);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction(nameof(GetByExport), new { exportId }, MapToDto(item));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the revision number for a CutTemplate by looking at the most recent
|
||||
/// CutTemplate for the same drawing number and item number across all exports.
|
||||
/// Returns 1 if no previous version exists, the same revision if the hash matches,
|
||||
/// or previous revision + 1 if the hash changed.
|
||||
/// </summary>
|
||||
private async Task<int> ResolveRevisionAsync(string drawingNumber, string itemNo, string contentHash)
|
||||
{
|
||||
if (string.IsNullOrEmpty(drawingNumber) || string.IsNullOrEmpty(itemNo) || string.IsNullOrEmpty(contentHash))
|
||||
return 1;
|
||||
|
||||
var previous = await _db.CutTemplates
|
||||
.Where(c => c.BomItem.ExportRecord.DrawingNumber == drawingNumber
|
||||
&& c.BomItem.ItemNo == itemNo
|
||||
&& c.ContentHash != null)
|
||||
.OrderByDescending(c => c.Id)
|
||||
.Select(c => new { c.ContentHash, c.Revision })
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (previous == null)
|
||||
return 1;
|
||||
|
||||
return previous.ContentHash == contentHash
|
||||
? previous.Revision
|
||||
: previous.Revision + 1;
|
||||
}
|
||||
|
||||
private static BomItemDto MapToDto(BomItem b) => new()
|
||||
{
|
||||
ID = b.ID,
|
||||
ItemNo = b.ItemNo,
|
||||
PartNo = b.PartNo,
|
||||
SortOrder = b.SortOrder,
|
||||
Qty = b.Qty,
|
||||
TotalQty = b.TotalQty,
|
||||
Description = b.Description,
|
||||
PartName = b.PartName,
|
||||
ConfigurationName = b.ConfigurationName,
|
||||
Material = b.Material,
|
||||
CutTemplate = b.CutTemplate == null ? null : new CutTemplateDto
|
||||
{
|
||||
Id = b.CutTemplate.Id,
|
||||
DxfFilePath = b.CutTemplate.DxfFilePath,
|
||||
ContentHash = b.CutTemplate.ContentHash,
|
||||
Revision = b.CutTemplate.Revision,
|
||||
Thickness = b.CutTemplate.Thickness,
|
||||
KFactor = b.CutTemplate.KFactor,
|
||||
DefaultBendRadius = b.CutTemplate.DefaultBendRadius
|
||||
},
|
||||
FormProgram = b.FormProgram == null ? null : new FormProgramDto
|
||||
{
|
||||
Id = b.FormProgram.Id,
|
||||
ProgramFilePath = b.FormProgram.ProgramFilePath,
|
||||
ContentHash = b.FormProgram.ContentHash,
|
||||
ProgramName = b.FormProgram.ProgramName,
|
||||
Thickness = b.FormProgram.Thickness,
|
||||
MaterialType = b.FormProgram.MaterialType,
|
||||
KFactor = b.FormProgram.KFactor,
|
||||
BendCount = b.FormProgram.BendCount,
|
||||
UpperToolNames = b.FormProgram.UpperToolNames,
|
||||
LowerToolNames = b.FormProgram.LowerToolNames,
|
||||
SetupNotes = b.FormProgram.SetupNotes
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
470
FabWorks.Api/Controllers/ExportsController.cs
Normal file
470
FabWorks.Api/Controllers/ExportsController.cs
Normal file
@@ -0,0 +1,470 @@
|
||||
using System.Globalization;
|
||||
using System.IO.Compression;
|
||||
using System.Numerics;
|
||||
using FabWorks.Api.DTOs;
|
||||
using FabWorks.Api.Services;
|
||||
using FabWorks.Core.Data;
|
||||
using FabWorks.Core.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FabWorks.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class ExportsController : ControllerBase
|
||||
{
|
||||
private readonly FabWorksDbContext _db;
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
|
||||
public ExportsController(FabWorksDbContext db, IFileStorageService fileStorage)
|
||||
{
|
||||
_db = db;
|
||||
_fileStorage = fileStorage;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<object>> List(
|
||||
[FromQuery] string search = null,
|
||||
[FromQuery] int skip = 0,
|
||||
[FromQuery] int take = 50)
|
||||
{
|
||||
var query = _db.ExportRecords
|
||||
.Include(r => r.BomItems)
|
||||
.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var term = search.Trim().ToLower();
|
||||
query = query.Where(r =>
|
||||
r.DrawingNumber.ToLower().Contains(term) ||
|
||||
(r.Title != null && r.Title.ToLower().Contains(term)) ||
|
||||
r.ExportedBy.ToLower().Contains(term) ||
|
||||
r.BomItems.Any(b => b.PartName.ToLower().Contains(term) ||
|
||||
b.Description.ToLower().Contains(term)));
|
||||
}
|
||||
|
||||
var total = await query.CountAsync();
|
||||
|
||||
var records = await query
|
||||
.OrderByDescending(r => r.ExportedAt)
|
||||
.Skip(skip)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
|
||||
return new
|
||||
{
|
||||
total,
|
||||
items = records.Select(r => new
|
||||
{
|
||||
r.Id,
|
||||
r.DrawingNumber,
|
||||
r.Title,
|
||||
r.SourceFilePath,
|
||||
r.ExportedAt,
|
||||
r.ExportedBy,
|
||||
BomItemCount = r.BomItems?.Count ?? 0
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<ExportDetailDto>> Create(CreateExportRequest request)
|
||||
{
|
||||
var record = new ExportRecord
|
||||
{
|
||||
DrawingNumber = request.DrawingNumber,
|
||||
Title = request.Title,
|
||||
EquipmentNo = request.EquipmentNo,
|
||||
DrawingNo = request.DrawingNo,
|
||||
SourceFilePath = request.SourceFilePath,
|
||||
OutputFolder = request.OutputFolder,
|
||||
ExportedAt = DateTime.Now,
|
||||
ExportedBy = Environment.UserName
|
||||
};
|
||||
|
||||
_db.ExportRecords.Add(record);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return CreatedAtAction(nameof(GetById), new { id = record.Id }, MapToDto(record));
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<ActionResult<ExportDetailDto>> GetById(int id)
|
||||
{
|
||||
var record = await _db.ExportRecords
|
||||
.Include(r => r.BomItems).ThenInclude(b => b.CutTemplate)
|
||||
.Include(r => r.BomItems).ThenInclude(b => b.FormProgram)
|
||||
.FirstOrDefaultAsync(r => r.Id == id);
|
||||
|
||||
if (record == null) return NotFound();
|
||||
return MapToDto(record);
|
||||
}
|
||||
|
||||
[HttpGet("by-source")]
|
||||
public async Task<ActionResult<ExportDetailDto>> GetBySourceFile([FromQuery] string path)
|
||||
{
|
||||
var record = await _db.ExportRecords
|
||||
.Where(r => r.SourceFilePath.ToLower() == path.ToLower()
|
||||
&& !string.IsNullOrEmpty(r.DrawingNumber))
|
||||
.OrderByDescending(r => r.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (record == null) return NotFound();
|
||||
return MapToDto(record);
|
||||
}
|
||||
|
||||
[HttpGet("by-drawing")]
|
||||
public async Task<ActionResult<List<ExportDetailDto>>> GetByDrawing([FromQuery] string drawingNumber)
|
||||
{
|
||||
var records = await _db.ExportRecords
|
||||
.Include(r => r.BomItems).ThenInclude(b => b.CutTemplate)
|
||||
.Include(r => r.BomItems).ThenInclude(b => b.FormProgram)
|
||||
.Where(r => r.DrawingNumber == drawingNumber)
|
||||
.OrderByDescending(r => r.ExportedAt)
|
||||
.ToListAsync();
|
||||
|
||||
return records.Select(MapToDto).ToList();
|
||||
}
|
||||
|
||||
[HttpGet("next-item-number")]
|
||||
public async Task<ActionResult<string>> GetNextItemNumber([FromQuery] string drawingNumber)
|
||||
{
|
||||
if (string.IsNullOrEmpty(drawingNumber)) return "1";
|
||||
|
||||
var existingItems = await _db.ExportRecords
|
||||
.Where(r => r.DrawingNumber == drawingNumber)
|
||||
.SelectMany(r => r.BomItems)
|
||||
.Select(b => b.ItemNo)
|
||||
.ToListAsync();
|
||||
|
||||
int maxNum = 0;
|
||||
foreach (var itemNo in existingItems)
|
||||
{
|
||||
if (int.TryParse(itemNo, out var num) && num > maxNum)
|
||||
maxNum = num;
|
||||
}
|
||||
return (maxNum + 1).ToString();
|
||||
}
|
||||
|
||||
[HttpGet("drawing-numbers")]
|
||||
public async Task<ActionResult<List<string>>> GetDrawingNumbers()
|
||||
{
|
||||
var numbers = await _db.ExportRecords
|
||||
.Select(r => r.DrawingNumber)
|
||||
.Where(d => !string.IsNullOrEmpty(d))
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
|
||||
return numbers;
|
||||
}
|
||||
|
||||
[HttpGet("equipment-numbers")]
|
||||
public async Task<ActionResult<List<string>>> GetEquipmentNumbers()
|
||||
{
|
||||
var numbers = await _db.ExportRecords
|
||||
.Select(r => r.EquipmentNo)
|
||||
.Where(e => !string.IsNullOrEmpty(e))
|
||||
.Distinct()
|
||||
.OrderBy(e => e)
|
||||
.ToListAsync();
|
||||
|
||||
return numbers;
|
||||
}
|
||||
|
||||
[HttpGet("drawing-numbers-by-equipment")]
|
||||
public async Task<ActionResult<List<string>>> GetDrawingNumbersByEquipment([FromQuery] string equipmentNo)
|
||||
{
|
||||
var query = _db.ExportRecords
|
||||
.Where(r => !string.IsNullOrEmpty(r.DrawingNo));
|
||||
|
||||
if (!string.IsNullOrEmpty(equipmentNo))
|
||||
query = query.Where(r => r.EquipmentNo == equipmentNo);
|
||||
|
||||
var numbers = await query
|
||||
.Select(r => r.DrawingNo)
|
||||
.Distinct()
|
||||
.OrderBy(d => d)
|
||||
.ToListAsync();
|
||||
|
||||
return numbers;
|
||||
}
|
||||
|
||||
[HttpGet("previous-pdf-hash")]
|
||||
public async Task<ActionResult<string>> GetPreviousPdfHash(
|
||||
[FromQuery] string drawingNumber,
|
||||
[FromQuery] int? excludeId = null)
|
||||
{
|
||||
var hash = await _db.ExportRecords
|
||||
.Where(r => r.DrawingNumber == drawingNumber
|
||||
&& r.PdfContentHash != null
|
||||
&& (excludeId == null || r.Id != excludeId))
|
||||
.OrderByDescending(r => r.Id)
|
||||
.Select(r => r.PdfContentHash)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (hash == null) return NotFound();
|
||||
return hash;
|
||||
}
|
||||
|
||||
[HttpPatch("{id}/pdf-hash")]
|
||||
public async Task<IActionResult> UpdatePdfHash(int id, [FromBody] UpdatePdfHashRequest request)
|
||||
{
|
||||
var record = await _db.ExportRecords.FindAsync(id);
|
||||
if (record == null) return NotFound();
|
||||
|
||||
record.PdfContentHash = request.PdfContentHash;
|
||||
|
||||
if (!string.IsNullOrEmpty(record.DrawingNumber) && !string.IsNullOrEmpty(request.PdfContentHash))
|
||||
{
|
||||
var (drawing, revision) = await ResolveDrawingAsync(record.DrawingNumber, record.Title, request.PdfContentHash);
|
||||
record.Drawing = drawing;
|
||||
record.DrawingRevision = revision;
|
||||
}
|
||||
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<(Drawing drawing, int revision)> ResolveDrawingAsync(string drawingNumber, string title, string pdfContentHash)
|
||||
{
|
||||
var drawing = await _db.Drawings
|
||||
.FirstOrDefaultAsync(d => d.DrawingNumber == drawingNumber);
|
||||
|
||||
// Get the highest revision recorded for this drawing across all exports
|
||||
var lastRevision = await _db.ExportRecords
|
||||
.Where(r => r.DrawingNumber == drawingNumber && r.DrawingRevision != null)
|
||||
.OrderByDescending(r => r.DrawingRevision)
|
||||
.Select(r => r.DrawingRevision)
|
||||
.FirstOrDefaultAsync() ?? 0;
|
||||
|
||||
if (drawing == null)
|
||||
{
|
||||
drawing = new Drawing
|
||||
{
|
||||
DrawingNumber = drawingNumber,
|
||||
Title = title,
|
||||
PdfContentHash = pdfContentHash
|
||||
};
|
||||
_db.Drawings.Add(drawing);
|
||||
return (drawing, 1);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(title))
|
||||
drawing.Title = title;
|
||||
|
||||
if (ArePerceptualHashesSimilar(drawing.PdfContentHash, pdfContentHash))
|
||||
{
|
||||
// Hash unchanged — keep same revision
|
||||
return (drawing, lastRevision == 0 ? 1 : lastRevision);
|
||||
}
|
||||
|
||||
// Hash changed — bump revision and update stored hash
|
||||
drawing.PdfContentHash = pdfContentHash;
|
||||
return (drawing, lastRevision + 1);
|
||||
}
|
||||
|
||||
[HttpGet("previous-cut-template")]
|
||||
public async Task<ActionResult<CutTemplateDto>> GetPreviousCutTemplate(
|
||||
[FromQuery] string drawingNumber,
|
||||
[FromQuery] string itemNo)
|
||||
{
|
||||
if (string.IsNullOrEmpty(drawingNumber) || string.IsNullOrEmpty(itemNo))
|
||||
return BadRequest("drawingNumber and itemNo are required.");
|
||||
|
||||
var ct = await _db.CutTemplates
|
||||
.Where(c => c.BomItem.ExportRecord.DrawingNumber == drawingNumber
|
||||
&& c.BomItem.ItemNo == itemNo
|
||||
&& c.ContentHash != null)
|
||||
.OrderByDescending(c => c.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (ct == null) return NotFound();
|
||||
|
||||
return new CutTemplateDto
|
||||
{
|
||||
Id = ct.Id,
|
||||
DxfFilePath = ct.DxfFilePath,
|
||||
ContentHash = ct.ContentHash,
|
||||
Revision = ct.Revision,
|
||||
Thickness = ct.Thickness,
|
||||
KFactor = ct.KFactor,
|
||||
DefaultBendRadius = ct.DefaultBendRadius
|
||||
};
|
||||
}
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
var record = await _db.ExportRecords
|
||||
.Include(r => r.BomItems).ThenInclude(b => b.CutTemplate)
|
||||
.Include(r => r.BomItems).ThenInclude(b => b.FormProgram)
|
||||
.FirstOrDefaultAsync(r => r.Id == id);
|
||||
|
||||
if (record == null) return NotFound();
|
||||
|
||||
_db.ExportRecords.Remove(record);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
[HttpGet("{id}/download-dxfs")]
|
||||
public async Task<IActionResult> DownloadAllDxfs(int id)
|
||||
{
|
||||
var record = await _db.ExportRecords
|
||||
.Include(r => r.BomItems).ThenInclude(b => b.CutTemplate)
|
||||
.FirstOrDefaultAsync(r => r.Id == id);
|
||||
|
||||
if (record == null) return NotFound();
|
||||
|
||||
var dxfItems = record.BomItems
|
||||
.Where(b => b.CutTemplate?.ContentHash != null)
|
||||
.ToList();
|
||||
|
||||
if (dxfItems.Count == 0) return NotFound("No DXF files for this export.");
|
||||
|
||||
var zipName = $"{record.DrawingNumber ?? $"Export-{id}"} DXFs.zip";
|
||||
return BuildDxfZip(dxfItems, zipName);
|
||||
}
|
||||
|
||||
[HttpGet("download-dxfs")]
|
||||
public async Task<IActionResult> DownloadDxfsByDrawing([FromQuery] string drawingNumber)
|
||||
{
|
||||
if (string.IsNullOrEmpty(drawingNumber))
|
||||
return BadRequest("drawingNumber is required.");
|
||||
|
||||
var dxfItems = await _db.BomItems
|
||||
.Include(b => b.CutTemplate)
|
||||
.Where(b => b.ExportRecord.DrawingNumber == drawingNumber
|
||||
&& b.CutTemplate != null
|
||||
&& b.CutTemplate.ContentHash != null)
|
||||
.ToListAsync();
|
||||
|
||||
if (dxfItems.Count == 0) return NotFound("No DXF files for this drawing.");
|
||||
|
||||
// Deduplicate by content hash (keep latest)
|
||||
dxfItems = dxfItems
|
||||
.GroupBy(b => b.CutTemplate.ContentHash)
|
||||
.Select(g => g.Last())
|
||||
.ToList();
|
||||
|
||||
var zipName = $"{drawingNumber} DXFs.zip";
|
||||
return BuildDxfZip(dxfItems, zipName);
|
||||
}
|
||||
|
||||
private FileResult BuildDxfZip(List<BomItem> dxfItems, string zipName)
|
||||
{
|
||||
var ms = new MemoryStream();
|
||||
using (var zip = new ZipArchive(ms, ZipArchiveMode.Create, leaveOpen: true))
|
||||
{
|
||||
var usedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var b in dxfItems)
|
||||
{
|
||||
var ct = b.CutTemplate;
|
||||
var fileName = ct.DxfFilePath?.Split(new[] { '/', '\\' }).LastOrDefault()
|
||||
?? $"PT{(b.ItemNo ?? "").PadLeft(2, '0')}.dxf";
|
||||
|
||||
// Ensure unique names in zip
|
||||
if (!usedNames.Add(fileName))
|
||||
{
|
||||
var baseName = Path.GetFileNameWithoutExtension(fileName);
|
||||
var ext = Path.GetExtension(fileName);
|
||||
var counter = 2;
|
||||
do { fileName = $"{baseName}_{counter++}{ext}"; }
|
||||
while (!usedNames.Add(fileName));
|
||||
}
|
||||
|
||||
var blobStream = _fileStorage.OpenBlob(ct.ContentHash, "dxf");
|
||||
if (blobStream == null) continue;
|
||||
|
||||
var entry = zip.CreateEntry(fileName, CompressionLevel.Fastest);
|
||||
using var entryStream = entry.Open();
|
||||
blobStream.CopyTo(entryStream);
|
||||
blobStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
ms.Position = 0;
|
||||
return File(ms, "application/zip", zipName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compares two perceptual hashes using Hamming distance.
|
||||
/// Perceptual hashes (16 hex chars / 64 bits) are compared with a tolerance
|
||||
/// of up to 10 differing bits (~84% similarity). SHA256 fallback hashes
|
||||
/// (64 hex chars) use exact comparison.
|
||||
/// </summary>
|
||||
private static bool ArePerceptualHashesSimilar(string hash1, string hash2)
|
||||
{
|
||||
if (hash1 == hash2) return true;
|
||||
if (string.IsNullOrEmpty(hash1) || string.IsNullOrEmpty(hash2)) return false;
|
||||
|
||||
// Perceptual hashes are 16 hex chars (64-bit DifferenceHash)
|
||||
// SHA256 fallback hashes are 64 hex chars — require exact match
|
||||
if (hash1.Length != 16 || hash2.Length != 16)
|
||||
return false;
|
||||
|
||||
if (ulong.TryParse(hash1, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var h1) &&
|
||||
ulong.TryParse(hash2, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var h2))
|
||||
{
|
||||
var hammingDistance = BitOperations.PopCount(h1 ^ h2);
|
||||
return hammingDistance <= 10;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ExportDetailDto MapToDto(ExportRecord r) => new()
|
||||
{
|
||||
Id = r.Id,
|
||||
DrawingNumber = r.DrawingNumber,
|
||||
Title = r.Title,
|
||||
EquipmentNo = r.EquipmentNo,
|
||||
DrawingNo = r.DrawingNo,
|
||||
SourceFilePath = r.SourceFilePath,
|
||||
OutputFolder = r.OutputFolder,
|
||||
ExportedAt = r.ExportedAt,
|
||||
ExportedBy = r.ExportedBy,
|
||||
PdfContentHash = r.PdfContentHash,
|
||||
BomItems = r.BomItems?.Select(b => new BomItemDto
|
||||
{
|
||||
ID = b.ID,
|
||||
ItemNo = b.ItemNo,
|
||||
PartNo = b.PartNo,
|
||||
SortOrder = b.SortOrder,
|
||||
Qty = b.Qty,
|
||||
TotalQty = b.TotalQty,
|
||||
Description = b.Description,
|
||||
PartName = b.PartName,
|
||||
ConfigurationName = b.ConfigurationName,
|
||||
Material = b.Material,
|
||||
CutTemplate = b.CutTemplate == null ? null : new CutTemplateDto
|
||||
{
|
||||
Id = b.CutTemplate.Id,
|
||||
DxfFilePath = b.CutTemplate.DxfFilePath,
|
||||
ContentHash = b.CutTemplate.ContentHash,
|
||||
Revision = b.CutTemplate.Revision,
|
||||
Thickness = b.CutTemplate.Thickness,
|
||||
KFactor = b.CutTemplate.KFactor,
|
||||
DefaultBendRadius = b.CutTemplate.DefaultBendRadius
|
||||
},
|
||||
FormProgram = b.FormProgram == null ? null : new FormProgramDto
|
||||
{
|
||||
Id = b.FormProgram.Id,
|
||||
ProgramFilePath = b.FormProgram.ProgramFilePath,
|
||||
ContentHash = b.FormProgram.ContentHash,
|
||||
ProgramName = b.FormProgram.ProgramName,
|
||||
Thickness = b.FormProgram.Thickness,
|
||||
MaterialType = b.FormProgram.MaterialType,
|
||||
KFactor = b.FormProgram.KFactor,
|
||||
BendCount = b.FormProgram.BendCount,
|
||||
UpperToolNames = b.FormProgram.UpperToolNames,
|
||||
LowerToolNames = b.FormProgram.LowerToolNames,
|
||||
SetupNotes = b.FormProgram.SetupNotes
|
||||
}
|
||||
}).ToList() ?? new()
|
||||
};
|
||||
}
|
||||
}
|
||||
189
FabWorks.Api/Controllers/FileBrowserController.cs
Normal file
189
FabWorks.Api/Controllers/FileBrowserController.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using FabWorks.Api.Services;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FabWorks.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class FileBrowserController : ControllerBase
|
||||
{
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
private readonly FabWorksDbContext _db;
|
||||
private readonly FileExtensionContentTypeProvider _contentTypeProvider = new();
|
||||
|
||||
public FileBrowserController(IFileStorageService fileStorage, FabWorksDbContext db)
|
||||
{
|
||||
_fileStorage = fileStorage;
|
||||
_db = db;
|
||||
}
|
||||
|
||||
[HttpGet("files")]
|
||||
public async Task<ActionResult<FileListResult>> ListFiles(
|
||||
[FromQuery] string search = null,
|
||||
[FromQuery] string type = null)
|
||||
{
|
||||
var files = new List<StoredFileEntry>();
|
||||
|
||||
// Query DXF files from CutTemplates
|
||||
if (type == null || type.Equals("dxf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var dxfQuery = _db.CutTemplates
|
||||
.Where(c => c.ContentHash != null)
|
||||
.Select(c => new
|
||||
{
|
||||
c.Id,
|
||||
c.DxfFilePath,
|
||||
c.ContentHash,
|
||||
c.Thickness,
|
||||
c.Revision,
|
||||
DrawingNumber = c.BomItem.ExportRecord.DrawingNumber,
|
||||
CreatedAt = c.BomItem.ExportRecord.ExportedAt
|
||||
});
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var term = search.Trim().ToLower();
|
||||
dxfQuery = dxfQuery.Where(c =>
|
||||
c.DxfFilePath.ToLower().Contains(term) ||
|
||||
c.DrawingNumber.ToLower().Contains(term));
|
||||
}
|
||||
|
||||
var dxfResults = await dxfQuery
|
||||
.OrderByDescending(c => c.CreatedAt)
|
||||
.Take(500)
|
||||
.ToListAsync();
|
||||
|
||||
// Deduplicate by content hash (keep latest)
|
||||
var seenDxf = new HashSet<string>();
|
||||
foreach (var c in dxfResults)
|
||||
{
|
||||
if (seenDxf.Contains(c.ContentHash)) continue;
|
||||
seenDxf.Add(c.ContentHash);
|
||||
|
||||
var fileName = c.DxfFilePath?.Split(new[] { '/', '\\' }).LastOrDefault() ?? c.DxfFilePath;
|
||||
files.Add(new StoredFileEntry
|
||||
{
|
||||
FileName = fileName,
|
||||
ContentHash = c.ContentHash,
|
||||
FileType = "dxf",
|
||||
DrawingNumber = c.DrawingNumber,
|
||||
Thickness = c.Thickness,
|
||||
Revision = c.Revision,
|
||||
CreatedAt = c.CreatedAt
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Query PDF files from ExportRecords
|
||||
if (type == null || type.Equals("pdf", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var pdfQuery = _db.ExportRecords
|
||||
.Where(r => r.PdfContentHash != null)
|
||||
.Select(r => new
|
||||
{
|
||||
r.Id,
|
||||
r.DrawingNumber,
|
||||
r.PdfContentHash,
|
||||
r.ExportedAt,
|
||||
r.DrawingRevision
|
||||
});
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
var term = search.Trim().ToLower();
|
||||
pdfQuery = pdfQuery.Where(r =>
|
||||
r.DrawingNumber.ToLower().Contains(term));
|
||||
}
|
||||
|
||||
var pdfResults = await pdfQuery
|
||||
.OrderByDescending(r => r.ExportedAt)
|
||||
.Take(500)
|
||||
.ToListAsync();
|
||||
|
||||
// Deduplicate by content hash
|
||||
var seenPdf = new HashSet<string>();
|
||||
foreach (var r in pdfResults)
|
||||
{
|
||||
if (seenPdf.Contains(r.PdfContentHash)) continue;
|
||||
seenPdf.Add(r.PdfContentHash);
|
||||
|
||||
files.Add(new StoredFileEntry
|
||||
{
|
||||
FileName = $"{r.DrawingNumber}.pdf",
|
||||
ContentHash = r.PdfContentHash,
|
||||
FileType = "pdf",
|
||||
DrawingNumber = r.DrawingNumber,
|
||||
Revision = r.DrawingRevision,
|
||||
CreatedAt = r.ExportedAt
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new FileListResult
|
||||
{
|
||||
Total = files.Count,
|
||||
Files = files.OrderByDescending(f => f.CreatedAt).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("preview")]
|
||||
public IActionResult PreviewFile([FromQuery] string hash, [FromQuery] string ext = "dxf")
|
||||
{
|
||||
if (string.IsNullOrEmpty(hash) || hash.Length < 4)
|
||||
return BadRequest("Invalid hash.");
|
||||
|
||||
if (!_fileStorage.BlobExists(hash, ext))
|
||||
return NotFound("File not found.");
|
||||
|
||||
var stream = _fileStorage.OpenBlob(hash, ext);
|
||||
if (stream == null)
|
||||
return NotFound("File not found.");
|
||||
|
||||
var virtualName = $"file.{ext}";
|
||||
if (!_contentTypeProvider.TryGetContentType(virtualName, out var contentType))
|
||||
contentType = "application/octet-stream";
|
||||
|
||||
return File(stream, contentType);
|
||||
}
|
||||
|
||||
[HttpGet("download")]
|
||||
public IActionResult DownloadFile([FromQuery] string hash, [FromQuery] string ext = "dxf", [FromQuery] string name = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hash) || hash.Length < 4)
|
||||
return BadRequest("Invalid hash.");
|
||||
|
||||
if (!_fileStorage.BlobExists(hash, ext))
|
||||
return NotFound("File not found.");
|
||||
|
||||
var stream = _fileStorage.OpenBlob(hash, ext);
|
||||
if (stream == null)
|
||||
return NotFound("File not found.");
|
||||
|
||||
var fileName = name ?? $"{hash[..8]}.{ext}";
|
||||
if (!_contentTypeProvider.TryGetContentType(fileName, out var contentType))
|
||||
contentType = "application/octet-stream";
|
||||
|
||||
return File(stream, contentType, fileName);
|
||||
}
|
||||
}
|
||||
|
||||
public class FileListResult
|
||||
{
|
||||
public int Total { get; set; }
|
||||
public List<StoredFileEntry> Files { get; set; }
|
||||
}
|
||||
|
||||
public class StoredFileEntry
|
||||
{
|
||||
public string FileName { get; set; }
|
||||
public string ContentHash { get; set; }
|
||||
public string FileType { get; set; }
|
||||
public string DrawingNumber { get; set; }
|
||||
public double? Thickness { get; set; }
|
||||
public int? Revision { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
93
FabWorks.Api/Controllers/FilesController.cs
Normal file
93
FabWorks.Api/Controllers/FilesController.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using FabWorks.Api.DTOs;
|
||||
using FabWorks.Api.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
|
||||
namespace FabWorks.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class FilesController : ControllerBase
|
||||
{
|
||||
private readonly IFileStorageService _fileStorage;
|
||||
private readonly FileExtensionContentTypeProvider _contentTypeProvider = new();
|
||||
|
||||
public FilesController(IFileStorageService fileStorage)
|
||||
{
|
||||
_fileStorage = fileStorage;
|
||||
}
|
||||
|
||||
[HttpPost("dxf")]
|
||||
[RequestSizeLimit(50_000_000)] // 50 MB
|
||||
public async Task<ActionResult<FileUploadResponse>> UploadDxf(
|
||||
IFormFile file,
|
||||
[FromForm] string equipment,
|
||||
[FromForm] string drawingNo,
|
||||
[FromForm] string itemNo,
|
||||
[FromForm] string contentHash)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
return BadRequest("No file uploaded.");
|
||||
|
||||
using var stream = file.OpenReadStream();
|
||||
var result = await _fileStorage.StoreDxfAsync(stream, equipment, drawingNo, itemNo, contentHash, file.FileName);
|
||||
|
||||
return Ok(new FileUploadResponse
|
||||
{
|
||||
StoredFilePath = result.FileName,
|
||||
ContentHash = result.ContentHash,
|
||||
FileName = result.FileName,
|
||||
WasUnchanged = result.WasUnchanged,
|
||||
IsNewFile = result.IsNewFile
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("pdf")]
|
||||
[RequestSizeLimit(100_000_000)] // 100 MB
|
||||
public async Task<ActionResult<FileUploadResponse>> UploadPdf(
|
||||
IFormFile file,
|
||||
[FromForm] string equipment,
|
||||
[FromForm] string drawingNo,
|
||||
[FromForm] string contentHash,
|
||||
[FromForm] int? exportRecordId = null)
|
||||
{
|
||||
if (file == null || file.Length == 0)
|
||||
return BadRequest("No file uploaded.");
|
||||
|
||||
using var stream = file.OpenReadStream();
|
||||
var result = await _fileStorage.StorePdfAsync(stream, equipment, drawingNo, contentHash, exportRecordId);
|
||||
|
||||
return Ok(new FileUploadResponse
|
||||
{
|
||||
StoredFilePath = result.FileName,
|
||||
ContentHash = result.ContentHash,
|
||||
FileName = result.FileName,
|
||||
WasUnchanged = result.WasUnchanged,
|
||||
IsNewFile = result.IsNewFile
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet("blob/{hash}")]
|
||||
public IActionResult GetBlob(string hash, [FromQuery] string ext = "dxf", [FromQuery] bool download = false, [FromQuery] string name = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hash) || hash.Length < 4)
|
||||
return BadRequest("Invalid hash.");
|
||||
|
||||
if (!_fileStorage.BlobExists(hash, ext))
|
||||
return NotFound("Blob not found.");
|
||||
|
||||
var stream = _fileStorage.OpenBlob(hash, ext);
|
||||
if (stream == null)
|
||||
return NotFound("Blob not found.");
|
||||
|
||||
var fileName = !string.IsNullOrEmpty(name) ? name : $"{hash[..8]}.{ext}";
|
||||
if (!_contentTypeProvider.TryGetContentType(fileName, out var contentType))
|
||||
contentType = "application/octet-stream";
|
||||
|
||||
if (download)
|
||||
return File(stream, contentType, fileName);
|
||||
|
||||
return File(stream, contentType);
|
||||
}
|
||||
}
|
||||
}
|
||||
106
FabWorks.Api/Controllers/FormProgramsController.cs
Normal file
106
FabWorks.Api/Controllers/FormProgramsController.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using FabWorks.Api.DTOs;
|
||||
using FabWorks.Api.Services;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FabWorks.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/form-programs")]
|
||||
public class FormProgramsController : ControllerBase
|
||||
{
|
||||
private readonly FabWorksDbContext _db;
|
||||
private readonly FormProgramService _formService;
|
||||
|
||||
public FormProgramsController(FabWorksDbContext db, FormProgramService formService)
|
||||
{
|
||||
_db = db;
|
||||
_formService = formService;
|
||||
}
|
||||
|
||||
[HttpGet("by-drawing")]
|
||||
public async Task<ActionResult<List<FormProgramDto>>> GetByDrawing([FromQuery] string drawingNumber)
|
||||
{
|
||||
var programs = await _db.FormPrograms
|
||||
.Include(fp => fp.BomItem)
|
||||
.ThenInclude(b => b.ExportRecord)
|
||||
.Where(fp => fp.BomItem.ExportRecord.DrawingNumber == drawingNumber)
|
||||
.ToListAsync();
|
||||
|
||||
return programs.Select(fp => new FormProgramDto
|
||||
{
|
||||
Id = fp.Id,
|
||||
ProgramFilePath = fp.ProgramFilePath,
|
||||
ContentHash = fp.ContentHash,
|
||||
ProgramName = fp.ProgramName,
|
||||
Thickness = fp.Thickness,
|
||||
MaterialType = fp.MaterialType,
|
||||
KFactor = fp.KFactor,
|
||||
BendCount = fp.BendCount,
|
||||
UpperToolNames = fp.UpperToolNames,
|
||||
LowerToolNames = fp.LowerToolNames,
|
||||
SetupNotes = fp.SetupNotes
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
[HttpPost("parse")]
|
||||
public ActionResult<FormProgramDto> Parse([FromQuery] string filePath)
|
||||
{
|
||||
if (!System.IO.File.Exists(filePath))
|
||||
return NotFound($"File not found: {filePath}");
|
||||
|
||||
var fp = _formService.ParseFromFile(filePath);
|
||||
return new FormProgramDto
|
||||
{
|
||||
ProgramFilePath = fp.ProgramFilePath,
|
||||
ContentHash = fp.ContentHash,
|
||||
ProgramName = fp.ProgramName,
|
||||
Thickness = fp.Thickness,
|
||||
MaterialType = fp.MaterialType,
|
||||
KFactor = fp.KFactor,
|
||||
BendCount = fp.BendCount,
|
||||
UpperToolNames = fp.UpperToolNames,
|
||||
LowerToolNames = fp.LowerToolNames,
|
||||
SetupNotes = fp.SetupNotes
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost("{bomItemId}")]
|
||||
public async Task<ActionResult<FormProgramDto>> AttachToItem(int bomItemId, [FromQuery] string filePath)
|
||||
{
|
||||
var bomItem = await _db.BomItems
|
||||
.Include(b => b.FormProgram)
|
||||
.FirstOrDefaultAsync(b => b.ID == bomItemId);
|
||||
|
||||
if (bomItem == null) return NotFound("BOM item not found");
|
||||
|
||||
if (!System.IO.File.Exists(filePath))
|
||||
return NotFound($"File not found: {filePath}");
|
||||
|
||||
var fp = _formService.ParseFromFile(filePath);
|
||||
fp.BomItemId = bomItemId;
|
||||
|
||||
if (bomItem.FormProgram != null)
|
||||
_db.FormPrograms.Remove(bomItem.FormProgram);
|
||||
|
||||
bomItem.FormProgram = fp;
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
return new FormProgramDto
|
||||
{
|
||||
Id = fp.Id,
|
||||
ProgramFilePath = fp.ProgramFilePath,
|
||||
ContentHash = fp.ContentHash,
|
||||
ProgramName = fp.ProgramName,
|
||||
Thickness = fp.Thickness,
|
||||
MaterialType = fp.MaterialType,
|
||||
KFactor = fp.KFactor,
|
||||
BendCount = fp.BendCount,
|
||||
UpperToolNames = fp.UpperToolNames,
|
||||
LowerToolNames = fp.LowerToolNames,
|
||||
SetupNotes = fp.SetupNotes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
17
FabWorks.Api/DTOs/CreateExportRequest.cs
Normal file
17
FabWorks.Api/DTOs/CreateExportRequest.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace FabWorks.Api.DTOs
|
||||
{
|
||||
public class CreateExportRequest
|
||||
{
|
||||
public string DrawingNumber { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string EquipmentNo { get; set; }
|
||||
public string DrawingNo { get; set; }
|
||||
public string SourceFilePath { get; set; }
|
||||
public string OutputFolder { get; set; }
|
||||
}
|
||||
|
||||
public class UpdatePdfHashRequest
|
||||
{
|
||||
public string PdfContentHash { get; set; }
|
||||
}
|
||||
}
|
||||
62
FabWorks.Api/DTOs/ExportDetailDto.cs
Normal file
62
FabWorks.Api/DTOs/ExportDetailDto.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FabWorks.Api.DTOs
|
||||
{
|
||||
public class ExportDetailDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DrawingNumber { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string EquipmentNo { get; set; }
|
||||
public string DrawingNo { get; set; }
|
||||
public string SourceFilePath { get; set; }
|
||||
public string OutputFolder { get; set; }
|
||||
public DateTime ExportedAt { get; set; }
|
||||
public string ExportedBy { get; set; }
|
||||
public string PdfContentHash { get; set; }
|
||||
public List<BomItemDto> BomItems { get; set; } = new();
|
||||
}
|
||||
|
||||
public class BomItemDto
|
||||
{
|
||||
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 CutTemplateDto CutTemplate { get; set; }
|
||||
public FormProgramDto FormProgram { get; set; }
|
||||
}
|
||||
|
||||
public class CutTemplateDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DxfFilePath { get; set; }
|
||||
public string ContentHash { get; set; }
|
||||
public int Revision { get; set; }
|
||||
public double? Thickness { get; set; }
|
||||
public double? KFactor { get; set; }
|
||||
public double? DefaultBendRadius { get; set; }
|
||||
}
|
||||
|
||||
public class FormProgramDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string ProgramFilePath { get; set; }
|
||||
public string ContentHash { get; set; }
|
||||
public string ProgramName { get; set; }
|
||||
public double? Thickness { get; set; }
|
||||
public string MaterialType { get; set; }
|
||||
public double? KFactor { get; set; }
|
||||
public int BendCount { get; set; }
|
||||
public string UpperToolNames { get; set; }
|
||||
public string LowerToolNames { get; set; }
|
||||
public string SetupNotes { get; set; }
|
||||
}
|
||||
}
|
||||
11
FabWorks.Api/DTOs/FileUploadResponse.cs
Normal file
11
FabWorks.Api/DTOs/FileUploadResponse.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace FabWorks.Api.DTOs
|
||||
{
|
||||
public class FileUploadResponse
|
||||
{
|
||||
public string StoredFilePath { get; set; } // kept for client compat, contains logical filename
|
||||
public string ContentHash { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public bool WasUnchanged { get; set; }
|
||||
public bool IsNewFile { get; set; }
|
||||
}
|
||||
}
|
||||
20
FabWorks.Api/FabWorks.Api.csproj
Normal file
20
FabWorks.Api/FabWorks.Api.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FabWorks.Core\FabWorks.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
26
FabWorks.Api/Program.cs
Normal file
26
FabWorks.Api/Program.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using FabWorks.Api.Configuration;
|
||||
using FabWorks.Api.Services;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddDbContext<FabWorksDbContext>(options =>
|
||||
options.UseSqlServer(builder.Configuration.GetConnectionString("FabWorksDb")));
|
||||
builder.Services.AddSingleton<FormProgramService>();
|
||||
|
||||
builder.Services.Configure<FileStorageOptions>(
|
||||
builder.Configuration.GetSection(FileStorageOptions.SectionName));
|
||||
builder.Services.AddScoped<IFileStorageService, FileStorageService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
OnPrepareResponse = ctx =>
|
||||
ctx.Context.Response.Headers.Append("Cache-Control", "no-cache, no-store")
|
||||
});
|
||||
app.MapControllers();
|
||||
app.Run();
|
||||
41
FabWorks.Api/Properties/launchSettings.json
Normal file
41
FabWorks.Api/Properties/launchSettings.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:45483",
|
||||
"sslPort": 44397
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "",
|
||||
"applicationUrl": "http://localhost:5206",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "",
|
||||
"applicationUrl": "https://localhost:7182;http://localhost:5206",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
167
FabWorks.Api/Services/FileStorageService.cs
Normal file
167
FabWorks.Api/Services/FileStorageService.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using FabWorks.Api.Configuration;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace FabWorks.Api.Services
|
||||
{
|
||||
public class FileUploadResult
|
||||
{
|
||||
public string ContentHash { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public bool WasUnchanged { get; set; }
|
||||
public bool IsNewFile { get; set; }
|
||||
}
|
||||
|
||||
public interface IFileStorageService
|
||||
{
|
||||
string OutputFolder { get; }
|
||||
Task<FileUploadResult> StoreDxfAsync(Stream stream, string equipment, string drawingNo, string itemNo, string contentHash, string originalFileName = null);
|
||||
Task<FileUploadResult> StorePdfAsync(Stream stream, string equipment, string drawingNo, string contentHash, int? exportRecordId = null);
|
||||
Stream OpenBlob(string contentHash, string extension);
|
||||
bool BlobExists(string contentHash, string extension);
|
||||
}
|
||||
|
||||
public class FileStorageService : IFileStorageService
|
||||
{
|
||||
private readonly FileStorageOptions _options;
|
||||
private readonly FabWorksDbContext _db;
|
||||
|
||||
public string OutputFolder => _options.OutputFolder;
|
||||
|
||||
public FileStorageService(IOptions<FileStorageOptions> options, FabWorksDbContext db)
|
||||
{
|
||||
_options = options.Value;
|
||||
_db = db;
|
||||
|
||||
var blobRoot = Path.Combine(_options.OutputFolder, "blobs");
|
||||
if (!Directory.Exists(blobRoot))
|
||||
Directory.CreateDirectory(blobRoot);
|
||||
}
|
||||
|
||||
public async Task<FileUploadResult> StoreDxfAsync(Stream stream, string equipment, string drawingNo, string itemNo, string contentHash, string originalFileName = null)
|
||||
{
|
||||
var fileName = BuildDxfFileName(drawingNo, equipment, itemNo, originalFileName);
|
||||
|
||||
// Look up previous hash by drawing number + item number
|
||||
var drawingNumber = BuildDrawingNumber(equipment, drawingNo);
|
||||
var previousHash = await _db.CutTemplates
|
||||
.Where(c => c.BomItem.ExportRecord.DrawingNumber == drawingNumber
|
||||
&& c.BomItem.ItemNo == itemNo
|
||||
&& c.ContentHash != null)
|
||||
.OrderByDescending(c => c.Id)
|
||||
.Select(c => c.ContentHash)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
var wasUnchanged = previousHash != null && previousHash == contentHash;
|
||||
var isNewFile = await StoreBlobAsync(stream, contentHash, "dxf");
|
||||
|
||||
return new FileUploadResult
|
||||
{
|
||||
ContentHash = contentHash,
|
||||
FileName = fileName,
|
||||
WasUnchanged = wasUnchanged,
|
||||
IsNewFile = isNewFile
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FileUploadResult> StorePdfAsync(Stream stream, string equipment, string drawingNo, string contentHash, int? exportRecordId = null)
|
||||
{
|
||||
var drawingNumber = BuildDrawingNumber(equipment, drawingNo);
|
||||
var fileName = $"{drawingNumber}.pdf";
|
||||
|
||||
// Look up previous PDF hash
|
||||
var previousHash = await _db.ExportRecords
|
||||
.Where(r => r.DrawingNumber == drawingNumber
|
||||
&& r.PdfContentHash != null
|
||||
&& (exportRecordId == null || r.Id != exportRecordId))
|
||||
.OrderByDescending(r => r.Id)
|
||||
.Select(r => r.PdfContentHash)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
var wasUnchanged = previousHash != null && previousHash == contentHash;
|
||||
var isNewFile = await StoreBlobAsync(stream, contentHash, "pdf");
|
||||
|
||||
// Update the export record with the PDF content hash
|
||||
if (exportRecordId.HasValue)
|
||||
{
|
||||
var record = await _db.ExportRecords.FindAsync(exportRecordId.Value);
|
||||
if (record != null)
|
||||
{
|
||||
record.PdfContentHash = contentHash;
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
return new FileUploadResult
|
||||
{
|
||||
ContentHash = contentHash,
|
||||
FileName = fileName,
|
||||
WasUnchanged = wasUnchanged,
|
||||
IsNewFile = isNewFile
|
||||
};
|
||||
}
|
||||
|
||||
public Stream OpenBlob(string contentHash, string extension)
|
||||
{
|
||||
var path = GetBlobPath(contentHash, extension);
|
||||
if (!File.Exists(path))
|
||||
return null;
|
||||
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
}
|
||||
|
||||
public bool BlobExists(string contentHash, string extension)
|
||||
{
|
||||
return File.Exists(GetBlobPath(contentHash, extension));
|
||||
}
|
||||
|
||||
private async Task<bool> StoreBlobAsync(Stream stream, string contentHash, string extension)
|
||||
{
|
||||
var blobPath = GetBlobPath(contentHash, extension);
|
||||
|
||||
if (File.Exists(blobPath))
|
||||
return false; // blob already exists (dedup)
|
||||
|
||||
var dir = Path.GetDirectoryName(blobPath);
|
||||
if (!Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
using var fileStream = new FileStream(blobPath, FileMode.Create, FileAccess.Write);
|
||||
await stream.CopyToAsync(fileStream);
|
||||
return true; // new blob written
|
||||
}
|
||||
|
||||
private string GetBlobPath(string contentHash, string extension)
|
||||
{
|
||||
var prefix1 = contentHash[..2];
|
||||
var prefix2 = contentHash[2..4];
|
||||
return Path.Combine(_options.OutputFolder, "blobs", prefix1, prefix2, $"{contentHash}.{extension}");
|
||||
}
|
||||
|
||||
private static string BuildDrawingNumber(string equipment, string drawingNo)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(equipment) && !string.IsNullOrEmpty(drawingNo))
|
||||
return $"{equipment} {drawingNo}";
|
||||
if (!string.IsNullOrEmpty(equipment))
|
||||
return equipment;
|
||||
return drawingNo ?? "";
|
||||
}
|
||||
|
||||
private static string BuildDxfFileName(string drawingNo, string equipment, string itemNo, string originalFileName = null)
|
||||
{
|
||||
// No drawing number: use the original filename from the client
|
||||
if (string.IsNullOrEmpty(drawingNo) && !string.IsNullOrEmpty(originalFileName))
|
||||
{
|
||||
return originalFileName.EndsWith(".dxf", StringComparison.OrdinalIgnoreCase)
|
||||
? originalFileName
|
||||
: originalFileName + ".dxf";
|
||||
}
|
||||
|
||||
var drawingNumber = BuildDrawingNumber(equipment, drawingNo);
|
||||
var paddedItem = (itemNo ?? "").PadLeft(2, '0');
|
||||
if (!string.IsNullOrEmpty(drawingNumber) && !string.IsNullOrEmpty(itemNo))
|
||||
return $"{drawingNumber} PT{paddedItem}.dxf";
|
||||
return $"PT{paddedItem}.dxf";
|
||||
}
|
||||
}
|
||||
}
|
||||
39
FabWorks.Api/Services/FormProgramService.cs
Normal file
39
FabWorks.Api/Services/FormProgramService.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using FabWorks.Core.Models;
|
||||
using FabWorks.Core.PressBrake;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace FabWorks.Api.Services
|
||||
{
|
||||
public class FormProgramService
|
||||
{
|
||||
public FormProgram ParseFromFile(string filePath)
|
||||
{
|
||||
var pgm = FabWorks.Core.PressBrake.Program.Load(filePath);
|
||||
var hash = ComputeFileHash(filePath);
|
||||
|
||||
return new FormProgram
|
||||
{
|
||||
ProgramFilePath = filePath,
|
||||
ContentHash = hash,
|
||||
ProgramName = pgm.ProgName ?? "",
|
||||
Thickness = pgm.MatThick > 0 ? pgm.MatThick : null,
|
||||
MaterialType = pgm.MatType.ToString(),
|
||||
KFactor = pgm.KFactor > 0 ? pgm.KFactor : null,
|
||||
BendCount = pgm.Steps.Count,
|
||||
UpperToolNames = string.Join(", ", pgm.UpperToolSets
|
||||
.Select(t => t.Name).Where(n => !string.IsNullOrEmpty(n)).Distinct()),
|
||||
LowerToolNames = string.Join(", ", pgm.LowerToolSets
|
||||
.Select(t => t.Name).Where(n => !string.IsNullOrEmpty(n)).Distinct()),
|
||||
SetupNotes = pgm.SetupNotes ?? ""
|
||||
};
|
||||
}
|
||||
|
||||
private static string ComputeFileHash(string filePath)
|
||||
{
|
||||
using var sha = SHA256.Create();
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var bytes = sha.ComputeHash(stream);
|
||||
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
8
FabWorks.Api/appsettings.Development.json
Normal file
8
FabWorks.Api/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
FabWorks.Api/appsettings.json
Normal file
15
FabWorks.Api/appsettings.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ConnectionStrings": {
|
||||
"FabWorksDb": "Server=localhost;Database=ExportDxfDb;Trusted_Connection=True;TrustServerCertificate=True;"
|
||||
},
|
||||
"FileStorage": {
|
||||
"OutputFolder": "C:\\ExportDXF\\Output"
|
||||
}
|
||||
}
|
||||
824
FabWorks.Api/wwwroot/css/styles.css
Normal file
824
FabWorks.Api/wwwroot/css/styles.css
Normal file
@@ -0,0 +1,824 @@
|
||||
:root {
|
||||
--bg-deep: #f0f1f3;
|
||||
--bg: #f8f9fa;
|
||||
--surface: #ffffff;
|
||||
--surface-raised: #ffffff;
|
||||
--border: #d0d5dd;
|
||||
--border-subtle: #e4e7ec;
|
||||
--text: #1a1a1a;
|
||||
--text-secondary: #475467;
|
||||
--text-dim: #667085;
|
||||
--cyan: #0975b0;
|
||||
--cyan-dim: rgba(9, 117, 176, 0.1);
|
||||
--cyan-glow: rgba(9, 117, 176, 0.2);
|
||||
--amber: #b54708;
|
||||
--amber-dim: rgba(181, 71, 8, 0.08);
|
||||
--green: #067647;
|
||||
--green-dim: rgba(6, 118, 71, 0.08);
|
||||
--red: #d92d20;
|
||||
--sidebar-w: 64px;
|
||||
--font-display: 'Outfit', sans-serif;
|
||||
--font-body: 'IBM Plex Sans', sans-serif;
|
||||
--font-mono: 'IBM Plex Mono', monospace;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* ─── Sidebar ─── */
|
||||
.sidebar {
|
||||
width: var(--sidebar-w);
|
||||
background: var(--bg-deep);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
top: 0; left: 0; bottom: 0;
|
||||
z-index: 50;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.sidebar-brand {
|
||||
width: 40px; height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 24px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sidebar-brand::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
left: 8px; right: 8px;
|
||||
height: 1px;
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.sidebar-brand svg {
|
||||
width: 26px; height: 26px;
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding-top: 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px; height: 44px;
|
||||
margin: 0 auto;
|
||||
color: var(--text-dim);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
color: var(--text-secondary);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--cyan);
|
||||
background: var(--cyan-dim);
|
||||
}
|
||||
|
||||
.nav-item.active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 20px;
|
||||
background: var(--cyan);
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
.nav-item svg { width: 20px; height: 20px; }
|
||||
|
||||
.nav-tooltip {
|
||||
position: absolute;
|
||||
left: calc(100% + 12px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
color: #fff;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-family: var(--font-body);
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.15s;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-item:hover .nav-tooltip { opacity: 1; }
|
||||
|
||||
/* ─── Main ─── */
|
||||
.main {
|
||||
margin-left: var(--sidebar-w);
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0 32px;
|
||||
height: 56px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.topbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.topbar h2 {
|
||||
font-family: var(--font-display);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.topbar-tag {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--cyan);
|
||||
background: var(--cyan-dim);
|
||||
padding: 2px 8px;
|
||||
border-radius: 3px;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding: 28px 32px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ─── Animations ─── */
|
||||
@keyframes fadeSlideIn {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.animate-in {
|
||||
animation: fadeSlideIn 0.3s ease forwards;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.animate-in:nth-child(1) { animation-delay: 0.04s; }
|
||||
.animate-in:nth-child(2) { animation-delay: 0.08s; }
|
||||
.animate-in:nth-child(3) { animation-delay: 0.12s; }
|
||||
.animate-in:nth-child(4) { animation-delay: 0.16s; }
|
||||
|
||||
/* ─── Cards ─── */
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.02em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.card-body { padding: 18px; }
|
||||
|
||||
/* ─── Stats ─── */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px;
|
||||
padding: 18px 20px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 3px;
|
||||
background: linear-gradient(90deg, var(--cyan), transparent);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-family: var(--font-display);
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin-top: 4px;
|
||||
color: var(--text);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.stat-value.stat-sm {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* ─── Tables ─── */
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
padding: 10px 16px;
|
||||
background: var(--bg);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
color: var(--text-dim);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
tbody tr { transition: background 0.1s; }
|
||||
tbody tr:hover td { background: var(--cyan-dim); }
|
||||
tbody tr:last-child td { border-bottom: none; }
|
||||
|
||||
/* ─── Badges ─── */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 3px 10px;
|
||||
border-radius: 3px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.badge svg { width: 14px; height: 14px; flex-shrink: 0; }
|
||||
.badge-cyan { background: var(--cyan-dim); color: var(--cyan); }
|
||||
.badge-amber { background: var(--amber-dim); color: var(--amber); }
|
||||
.badge-green { background: var(--green-dim); color: var(--green); }
|
||||
.badge-count {
|
||||
background: var(--bg);
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
/* ─── Buttons ─── */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 7px 14px;
|
||||
border-radius: 4px;
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
border-color: var(--text-dim);
|
||||
}
|
||||
|
||||
.btn svg { width: 14px; height: 14px; }
|
||||
|
||||
.btn-cyan {
|
||||
background: var(--cyan-dim);
|
||||
color: var(--cyan);
|
||||
border-color: rgba(9, 117, 176, 0.25);
|
||||
}
|
||||
|
||||
.btn-cyan:hover {
|
||||
background: rgba(9, 117, 176, 0.15);
|
||||
border-color: rgba(9, 117, 176, 0.4);
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.btn-amber {
|
||||
background: var(--amber-dim);
|
||||
color: var(--amber);
|
||||
border-color: rgba(181, 71, 8, 0.25);
|
||||
}
|
||||
|
||||
.btn-amber:hover {
|
||||
background: rgba(181, 71, 8, 0.15);
|
||||
border-color: rgba(181, 71, 8, 0.4);
|
||||
color: var(--amber);
|
||||
}
|
||||
|
||||
.btn-red {
|
||||
background: rgba(217, 45, 32, 0.08);
|
||||
color: var(--red);
|
||||
border-color: rgba(217, 45, 32, 0.25);
|
||||
}
|
||||
|
||||
.btn-red:hover {
|
||||
background: rgba(217, 45, 32, 0.15);
|
||||
border-color: rgba(217, 45, 32, 0.4);
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.btn-sm { padding: 4px 10px; font-size: 12px; }
|
||||
|
||||
/* ─── Search ─── */
|
||||
.search-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
padding: 0 12px;
|
||||
height: 36px;
|
||||
width: 300px;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.search-box:focus-within {
|
||||
border-color: var(--cyan);
|
||||
box-shadow: 0 0 0 2px var(--cyan-dim);
|
||||
}
|
||||
|
||||
.search-box svg {
|
||||
width: 16px; height: 16px;
|
||||
color: var(--text-dim);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.search-box input {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-family: var(--font-body);
|
||||
font-size: 14px;
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.search-box input::placeholder { color: var(--text-dim); }
|
||||
|
||||
/* ─── Clickable ─── */
|
||||
.clickable { cursor: pointer; }
|
||||
|
||||
/* ─── Detail sections ─── */
|
||||
.detail-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.detail-field label {
|
||||
display: block;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.2px;
|
||||
color: var(--text-dim);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.detail-field .value {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.detail-field .value.mono {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* ─── Back link ─── */
|
||||
.back-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--text-dim);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 20px;
|
||||
font-family: var(--font-mono);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.back-link:hover { color: var(--cyan); }
|
||||
.back-link svg { width: 14px; height: 14px; }
|
||||
|
||||
/* ─── BOM Expansion ─── */
|
||||
.bom-expand-row td {
|
||||
padding: 0 !important;
|
||||
background: var(--bg) !important;
|
||||
}
|
||||
|
||||
.bom-expand-content {
|
||||
padding: 16px 16px 16px 48px;
|
||||
border-left: 3px solid var(--cyan-dim);
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.bom-expand-content .info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 6px 24px;
|
||||
}
|
||||
|
||||
.bom-expand-content .info-item {
|
||||
font-size: 13px;
|
||||
padding: 2px 0;
|
||||
}
|
||||
|
||||
.bom-expand-content .info-item .lbl {
|
||||
color: var(--text-dim);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.bom-expand-content .info-item .val {
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.bom-section-title {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--cyan);
|
||||
margin: 14px 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bom-section-title svg { width: 14px; height: 14px; flex-shrink: 0; }
|
||||
.bom-section-title:first-child { margin-top: 0; }
|
||||
|
||||
.bom-section-title::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--border-subtle);
|
||||
}
|
||||
|
||||
/* ─── File Browser ─── */
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
margin-bottom: 16px;
|
||||
flex-wrap: wrap;
|
||||
padding: 8px 14px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.breadcrumb a {
|
||||
color: var(--cyan);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.breadcrumb a:hover { opacity: 0.7; }
|
||||
.breadcrumb .sep { color: var(--text-dim); font-size: 11px; }
|
||||
.breadcrumb .current { color: var(--text); font-weight: 500; }
|
||||
|
||||
.file-name-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.file-name-cell svg { width: 18px; height: 18px; flex-shrink: 0; }
|
||||
|
||||
.file-name-cell a {
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.file-name-cell a:hover { color: var(--cyan); }
|
||||
|
||||
/* ─── Loading / Empty ─── */
|
||||
.loading, .empty {
|
||||
text-align: center;
|
||||
padding: 60px 24px;
|
||||
color: var(--text-dim);
|
||||
font-size: 14px;
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.loading::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border: 2px solid var(--border);
|
||||
border-top-color: var(--cyan);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
margin: 0 auto 12px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ─── Chevron toggle ─── */
|
||||
.chevron-toggle {
|
||||
display: inline-flex;
|
||||
width: 18px; height: 18px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: transform 0.2s;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.chevron-toggle.open {
|
||||
transform: rotate(90deg);
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
/* ─── Drawing cards grid ─── */
|
||||
.drawings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.drawing-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px;
|
||||
padding: 18px 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
position: relative;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.drawing-card:hover {
|
||||
border-color: var(--cyan);
|
||||
background: var(--cyan-dim);
|
||||
box-shadow: 0 2px 8px rgba(9, 117, 176, 0.1);
|
||||
}
|
||||
|
||||
.drawing-card-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.drawing-card-sub {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--text-dim);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* ─── Equipment Groups ─── */
|
||||
.equip-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.equip-group:last-child { margin-bottom: 0; }
|
||||
|
||||
.equip-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 12px 16px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px 6px 0 0;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
user-select: none;
|
||||
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
||||
}
|
||||
|
||||
.equip-header:hover { background: var(--cyan-dim); }
|
||||
|
||||
.equip-header .chevron-toggle { flex-shrink: 0; }
|
||||
|
||||
.equip-header-title {
|
||||
font-family: var(--font-display);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.equip-header-number {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 15px;
|
||||
color: var(--cyan);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.equip-header-meta {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.equip-header-stat {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
color: var(--text-dim);
|
||||
}
|
||||
|
||||
.equip-header-stat strong {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.equip-body {
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-top: none;
|
||||
border-radius: 0 0 6px 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.equip-body table { margin: 0; }
|
||||
|
||||
.equip-group.collapsed .equip-body { display: none; }
|
||||
.equip-group.collapsed .equip-header { border-radius: 6px; }
|
||||
|
||||
/* ─── Modal ─── */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 200;
|
||||
animation: fadeIn 0.15s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
|
||||
.modal-panel {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15);
|
||||
width: 90%;
|
||||
max-width: 640px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: fadeSlideIn 0.2s ease forwards;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
font-family: var(--font-display);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.02em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.modal-header svg { width: 16px; height: 16px; }
|
||||
|
||||
.modal-body {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn-green {
|
||||
background: var(--green-dim);
|
||||
color: var(--green);
|
||||
border-color: rgba(6, 118, 71, 0.25);
|
||||
}
|
||||
|
||||
.btn-green:hover {
|
||||
background: rgba(6, 118, 71, 0.15);
|
||||
border-color: rgba(6, 118, 71, 0.4);
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
/* ─── Toast ─── */
|
||||
.toast {
|
||||
position: fixed;
|
||||
bottom: 24px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--text);
|
||||
color: #fff;
|
||||
padding: 8px 20px;
|
||||
border-radius: 4px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
z-index: 300;
|
||||
animation: fadeSlideIn 0.2s ease, fadeOut 0.3s ease 2s forwards;
|
||||
}
|
||||
|
||||
@keyframes fadeOut { to { opacity: 0; } }
|
||||
|
||||
/* ─── Responsive ─── */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar { display: none; }
|
||||
.main { margin-left: 0; }
|
||||
.search-box { width: 100%; }
|
||||
.topbar { padding: 0 16px; }
|
||||
.page-content { padding: 16px; }
|
||||
}
|
||||
56
FabWorks.Api/wwwroot/index.html
Normal file
56
FabWorks.Api/wwwroot/index.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FabWorks</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@300;400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="css/styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<aside class="sidebar">
|
||||
<div class="sidebar-brand">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M2 20V8l4-4h6l2 2h6a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2z"/>
|
||||
<path d="M8 10v6" opacity="0.5"/>
|
||||
<path d="M12 8v8" opacity="0.5"/>
|
||||
<path d="M16 11v3" opacity="0.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
<nav class="sidebar-nav">
|
||||
<a class="nav-item active" data-page="exports" onclick="router.go('exports')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
|
||||
<span class="nav-tooltip">Exports</span>
|
||||
</a>
|
||||
<a class="nav-item" data-page="drawings" onclick="router.go('drawings')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><path d="M9 21V9"/></svg>
|
||||
<span class="nav-tooltip">Drawings</span>
|
||||
</a>
|
||||
<a class="nav-item" data-page="files" onclick="router.go('files')">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
|
||||
<span class="nav-tooltip">Files</span>
|
||||
</a>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<div class="main">
|
||||
<div class="topbar">
|
||||
<div class="topbar-left">
|
||||
<h2 id="page-title">Exports</h2>
|
||||
<span class="topbar-tag" id="page-tag"></span>
|
||||
</div>
|
||||
<div id="topbar-actions"></div>
|
||||
</div>
|
||||
<div class="page-content" id="page-content"></div>
|
||||
</div>
|
||||
|
||||
<script src="js/icons.js?v=3"></script>
|
||||
<script src="js/helpers.js?v=3"></script>
|
||||
<script src="js/components.js?v=3"></script>
|
||||
<script src="js/pages.js?v=3"></script>
|
||||
<script src="js/router.js?v=3"></script>
|
||||
<script>router.init();</script>
|
||||
</body>
|
||||
</html>
|
||||
155
FabWorks.Api/wwwroot/js/components.js
Normal file
155
FabWorks.Api/wwwroot/js/components.js
Normal file
@@ -0,0 +1,155 @@
|
||||
/* ─── BOM Detail Expansion ─── */
|
||||
function renderBomDetails(b) {
|
||||
let html = '<div class="bom-expand-content">';
|
||||
|
||||
if (b.cutTemplate) {
|
||||
const ct = b.cutTemplate;
|
||||
const displayName = ct.dxfFilePath?.split(/[/\\]/).pop() || '';
|
||||
html += `
|
||||
<div class="bom-section-title">${icons.laser} Cut Template</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item"><span class="lbl">File</span><span class="val">${esc(displayName)}</span></div>
|
||||
<div class="info-item"><span class="lbl">Thickness</span><span class="val">${fmtThickness(ct.thickness)}</span></div>
|
||||
<div class="info-item"><span class="lbl">K-Factor</span><span class="val">${ct.kFactor != null ? ct.kFactor : '\u2014'}</span></div>
|
||||
<div class="info-item"><span class="lbl">Bend Radius</span><span class="val">${ct.defaultBendRadius != null ? ct.defaultBendRadius.toFixed(4) + '"' : '\u2014'}</span></div>
|
||||
</div>`;
|
||||
|
||||
if (ct.contentHash) {
|
||||
html += `<div style="margin-top:10px">
|
||||
<a class="btn btn-cyan btn-sm" href="/api/files/blob/${encodeURIComponent(ct.contentHash)}?ext=dxf&download=true&name=${encodeURIComponent(displayName)}" onclick="event.stopPropagation()">${icons.download} Download DXF</a>
|
||||
<span style="font-family:var(--font-mono);font-size:13px;color:var(--text-dim);margin-left:8px">${esc(displayName)}</span>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
if (b.formProgram) {
|
||||
const fp = b.formProgram;
|
||||
html += `
|
||||
<div class="bom-section-title">${icons.bend} Form Program</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item"><span class="lbl">Program</span><span class="val">${esc(fp.programName)}</span></div>
|
||||
<div class="info-item"><span class="lbl">Thickness</span><span class="val">${fmtThickness(fp.thickness)}</span></div>
|
||||
<div class="info-item"><span class="lbl">Material</span><span class="val">${esc(fp.materialType)}</span></div>
|
||||
<div class="info-item"><span class="lbl">K-Factor</span><span class="val">${fp.kFactor != null ? fp.kFactor : '\u2014'}</span></div>
|
||||
<div class="info-item"><span class="lbl">Bends</span><span class="val">${fp.bendCount}</span></div>
|
||||
<div class="info-item"><span class="lbl">Upper Tools</span><span class="val">${esc(fp.upperToolNames) || '\u2014'}</span></div>
|
||||
<div class="info-item"><span class="lbl">Lower Tools</span><span class="val">${esc(fp.lowerToolNames) || '\u2014'}</span></div>
|
||||
</div>
|
||||
${fp.setupNotes ? `<div style="margin-top:8px;padding:8px 12px;background:var(--amber-dim);border-radius:4px;font-size:13px;font-family:var(--font-mono);color:var(--amber)"><span class="lbl">Setup Notes</span>${esc(fp.setupNotes)}</div>` : ''}`;
|
||||
}
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
}
|
||||
|
||||
function toggleEquipGroup(id) {
|
||||
const group = document.getElementById(id);
|
||||
const icon = document.getElementById(id + '-icon');
|
||||
if (!group) return;
|
||||
group.classList.toggle('collapsed');
|
||||
if (icon) icon.classList.toggle('open', !group.classList.contains('collapsed'));
|
||||
}
|
||||
|
||||
function toggleBomRow(id) {
|
||||
const row = document.getElementById(id);
|
||||
const icon = document.getElementById(id + '-icon');
|
||||
if (!row) return;
|
||||
const visible = row.style.display !== 'none';
|
||||
row.style.display = visible ? 'none' : '';
|
||||
if (icon) icon.classList.toggle('open', !visible);
|
||||
}
|
||||
|
||||
/* ─── Cut List Modal ─── */
|
||||
function showCutListModal(bomItems) {
|
||||
const cutItems = bomItems.filter(b => b.cutTemplate);
|
||||
if (cutItems.length === 0) {
|
||||
showToast('No cut templates found');
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = cutItems.map(b => {
|
||||
const ct = b.cutTemplate;
|
||||
const name = ct.cutTemplateName || ct.dxfFilePath?.split(/[/\\]/).pop()?.replace(/\.dxf$/i, '') || b.partName || '';
|
||||
const qty = b.qty ?? '';
|
||||
return { name, qty };
|
||||
});
|
||||
|
||||
const tableRows = rows.map((r, i) => `
|
||||
<tr style="animation: fadeSlideIn 0.15s ease ${0.02 * i}s forwards; opacity: 0">
|
||||
<td style="font-family:var(--font-mono);font-weight:600">${esc(r.name)}</td>
|
||||
<td style="font-family:var(--font-mono);text-align:center">${r.qty}</td>
|
||||
</tr>`).join('');
|
||||
|
||||
// Remove existing modal if any
|
||||
const existing = document.getElementById('cut-list-modal');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'cut-list-modal';
|
||||
modal.className = 'modal-overlay';
|
||||
modal.innerHTML = `
|
||||
<div class="modal-panel">
|
||||
<div class="modal-header">
|
||||
<span>${icons.laser} Cut List</span>
|
||||
<span class="badge badge-count">${cutItems.length} templates</span>
|
||||
<span style="margin-left:auto;display:flex;gap:6px">
|
||||
<button class="btn btn-cyan btn-sm" onclick="copyCutList()" id="copy-cut-list-btn">${icons.clipboard} Copy</button>
|
||||
<button class="btn btn-sm" onclick="closeCutListModal()">${icons.close}</button>
|
||||
</span>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th style="width:60px;text-align:center">Qty</th>
|
||||
</tr></thead>
|
||||
<tbody>${tableRows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
// Store data for copy
|
||||
modal._cutData = rows;
|
||||
// Close on backdrop click
|
||||
modal.addEventListener('click', e => { if (e.target === modal) closeCutListModal(); });
|
||||
// Close on Escape
|
||||
modal._keyHandler = e => { if (e.key === 'Escape') closeCutListModal(); };
|
||||
document.addEventListener('keydown', modal._keyHandler);
|
||||
}
|
||||
|
||||
function closeCutListModal() {
|
||||
const modal = document.getElementById('cut-list-modal');
|
||||
if (!modal) return;
|
||||
document.removeEventListener('keydown', modal._keyHandler);
|
||||
modal.remove();
|
||||
}
|
||||
|
||||
function copyCutList() {
|
||||
const modal = document.getElementById('cut-list-modal');
|
||||
if (!modal || !modal._cutData) return;
|
||||
|
||||
const text = modal._cutData.map(r => `${r.name}\t${r.qty}`).join('\n');
|
||||
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
const btn = document.getElementById('copy-cut-list-btn');
|
||||
if (btn) {
|
||||
btn.innerHTML = `${icons.check} Copied!`;
|
||||
btn.classList.remove('btn-cyan');
|
||||
btn.classList.add('btn-green');
|
||||
setTimeout(() => {
|
||||
btn.innerHTML = `${icons.clipboard} Copy`;
|
||||
btn.classList.remove('btn-green');
|
||||
btn.classList.add('btn-cyan');
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showToast(msg) {
|
||||
const t = document.createElement('div');
|
||||
t.className = 'toast';
|
||||
t.textContent = msg;
|
||||
document.body.appendChild(t);
|
||||
setTimeout(() => t.remove(), 2500);
|
||||
}
|
||||
50
FabWorks.Api/wwwroot/js/helpers.js
Normal file
50
FabWorks.Api/wwwroot/js/helpers.js
Normal file
@@ -0,0 +1,50 @@
|
||||
function fmtSize(b) {
|
||||
if (!b) return '0 B';
|
||||
const k = 1024, s = ['B','KB','MB','GB'];
|
||||
const i = Math.floor(Math.log(b) / Math.log(k));
|
||||
return parseFloat((b / Math.pow(k, i)).toFixed(1)) + ' ' + s[i];
|
||||
}
|
||||
|
||||
function fmtDate(d) {
|
||||
if (!d) return '';
|
||||
const dt = new Date(d);
|
||||
return dt.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) +
|
||||
' ' + dt.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
}
|
||||
|
||||
function fmtThickness(t) {
|
||||
if (t == null) return '\u2014';
|
||||
return `<span style="font-family:var(--font-mono)">${t.toFixed(4)}"</span>`;
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
return s ? s.replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''') : '';
|
||||
}
|
||||
|
||||
function setPage(title, tag = '') {
|
||||
document.getElementById('page-title').textContent = title;
|
||||
document.getElementById('page-tag').textContent = tag;
|
||||
document.getElementById('page-tag').style.display = tag ? '' : 'none';
|
||||
}
|
||||
|
||||
const api = {
|
||||
async get(url) {
|
||||
const r = await fetch(url);
|
||||
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
|
||||
return r.json();
|
||||
},
|
||||
async del(url) {
|
||||
const r = await fetch(url, { method: 'DELETE' });
|
||||
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`);
|
||||
}
|
||||
};
|
||||
|
||||
async function deleteExport(id) {
|
||||
if (!confirm('Delete this export record? This cannot be undone.')) return;
|
||||
try {
|
||||
await api.del(`/api/exports/${id}`);
|
||||
router.dispatch();
|
||||
} catch (err) {
|
||||
alert('Failed to delete: ' + err.message);
|
||||
}
|
||||
}
|
||||
23
FabWorks.Api/wwwroot/js/icons.js
Normal file
23
FabWorks.Api/wwwroot/js/icons.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const icons = {
|
||||
search: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>`,
|
||||
folder: `<svg viewBox="0 0 24 24" fill="var(--amber-dim)" stroke="var(--amber)" stroke-width="1.5"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>`,
|
||||
fileDxf: `<svg viewBox="0 0 24 24" fill="none" stroke="var(--cyan)" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,
|
||||
filePdf: `<svg viewBox="0 0 24 24" fill="none" stroke="var(--red)" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,
|
||||
fileGeneric: `<svg viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>`,
|
||||
download: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>`,
|
||||
back: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>`,
|
||||
chevron: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>`,
|
||||
laser: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--cyan)" stroke-width="1.2"><circle cx="8" cy="8" r="2"/><path d="M8 2v3M8 11v3M2 8h3M11 8h3" opacity="0.5"/></svg>`,
|
||||
bend: `<svg viewBox="0 0 16 16" fill="none" stroke="var(--amber)" stroke-width="1.2"><path d="M3 13V7a4 4 0 0 1 4-4h6"/><polyline points="10 6 13 3 10 0" transform="translate(0,2)"/></svg>`,
|
||||
trash: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>`,
|
||||
clipboard: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="2" width="6" height="4" rx="1"/><path d="M9 2H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2h-2"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="15" y2="16"/></svg>`,
|
||||
check: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"/></svg>`,
|
||||
close: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`,
|
||||
};
|
||||
|
||||
function fileIcon(name) {
|
||||
const ext = name.split('.').pop().toLowerCase();
|
||||
if (ext === 'dxf') return icons.fileDxf;
|
||||
if (ext === 'pdf') return icons.filePdf;
|
||||
return icons.fileGeneric;
|
||||
}
|
||||
393
FabWorks.Api/wwwroot/js/pages.js
Normal file
393
FabWorks.Api/wwwroot/js/pages.js
Normal file
@@ -0,0 +1,393 @@
|
||||
const pages = {
|
||||
async exports(params) {
|
||||
const actions = document.getElementById('topbar-actions');
|
||||
const content = document.getElementById('page-content');
|
||||
setPage('Exports');
|
||||
|
||||
const searchVal = params.q || '';
|
||||
actions.innerHTML = `
|
||||
<div class="search-box">
|
||||
${icons.search}
|
||||
<input type="text" id="export-search" placeholder="Search drawing, part, user..." value="${esc(searchVal)}">
|
||||
</div>`;
|
||||
|
||||
content.innerHTML = `<div class="loading">Loading exports</div>`;
|
||||
|
||||
const searchInput = document.getElementById('export-search');
|
||||
let debounce;
|
||||
searchInput.addEventListener('input', () => {
|
||||
clearTimeout(debounce);
|
||||
debounce = setTimeout(() => router.go('exports', { q: searchInput.value }), 400);
|
||||
});
|
||||
|
||||
try {
|
||||
const searchQ = searchVal ? `&search=${encodeURIComponent(searchVal)}` : '';
|
||||
const data = await api.get(`/api/exports?take=500${searchQ}`);
|
||||
|
||||
if (data.items.length === 0) {
|
||||
content.innerHTML = `<div class="empty">No exports found.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
setPage('Exports', `${data.items.length} exports`);
|
||||
|
||||
const rows = data.items.map((e, i) => `
|
||||
<tr class="clickable" onclick="router.go('drawing-detail', {id: '${encodeURIComponent(e.drawingNumber)}', eid: '${e.id}'})" style="animation: fadeSlideIn 0.2s ease ${0.02 * Math.min(i, 25)}s forwards; opacity: 0">
|
||||
<td style="font-family:var(--font-mono);color:var(--text-dim);font-size:13px">${e.id}</td>
|
||||
<td><strong>${esc(e.drawingNumber) || '<span style="color:var(--text-dim)">\u2014</span>'}</strong></td>
|
||||
<td style="color:var(--text-secondary);font-size:13px">${esc(e.title) || ''}</td>
|
||||
<td><span class="badge badge-count">${e.bomItemCount}</span></td>
|
||||
<td style="color:var(--text-secondary)">${esc(e.exportedBy)}</td>
|
||||
<td style="font-family:var(--font-mono);font-size:13px;color:var(--text-secondary);white-space:nowrap">${fmtDate(e.exportedAt)}</td>
|
||||
<td><button class="btn btn-red btn-sm" onclick="event.stopPropagation();deleteExport(${e.id})">${icons.trash}</button></td>
|
||||
</tr>`).join('');
|
||||
|
||||
content.innerHTML = `
|
||||
<div class="card animate-in">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th style="width:50px">#</th>
|
||||
<th>Drawing</th>
|
||||
<th>Title</th>
|
||||
<th style="width:80px">Items</th>
|
||||
<th>Exported By</th>
|
||||
<th style="width:180px">Date</th>
|
||||
<th style="width:50px"></th>
|
||||
</tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
</div>`;
|
||||
} catch (err) {
|
||||
content.innerHTML = `<div class="empty">Error: ${esc(err.message)}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
async drawings(params) {
|
||||
const actions = document.getElementById('topbar-actions');
|
||||
const content = document.getElementById('page-content');
|
||||
setPage('Drawings');
|
||||
|
||||
const searchVal = (params && params.q) || '';
|
||||
actions.innerHTML = `
|
||||
<div class="search-box">
|
||||
${icons.search}
|
||||
<input type="text" id="drawing-search" placeholder="Search drawing, part, user..." value="${esc(searchVal)}">
|
||||
</div>`;
|
||||
|
||||
content.innerHTML = `<div class="loading">Loading drawings</div>`;
|
||||
|
||||
const searchInput = document.getElementById('drawing-search');
|
||||
let debounce;
|
||||
searchInput.addEventListener('input', () => {
|
||||
clearTimeout(debounce);
|
||||
debounce = setTimeout(() => router.go('drawings', { q: searchInput.value }), 400);
|
||||
});
|
||||
|
||||
try {
|
||||
const searchQ = searchVal ? `&search=${encodeURIComponent(searchVal)}` : '';
|
||||
const data = await api.get(`/api/exports?take=500${searchQ}`);
|
||||
|
||||
if (data.items.length === 0) {
|
||||
content.innerHTML = `<div class="empty">No drawings found.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Deduplicate: keep only the latest export per drawing number
|
||||
const seen = new Set();
|
||||
const unique = data.items.filter(e => {
|
||||
const dn = e.drawingNumber || '';
|
||||
if (seen.has(dn)) return false;
|
||||
seen.add(dn);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Group by equipment number (first token of drawing number)
|
||||
const groups = new Map();
|
||||
unique.forEach(e => {
|
||||
const dn = e.drawingNumber || '';
|
||||
const spaceIdx = dn.indexOf(' ');
|
||||
const equip = spaceIdx > 0 ? dn.substring(0, spaceIdx) : (dn || 'Other');
|
||||
if (!groups.has(equip)) groups.set(equip, []);
|
||||
groups.get(equip).push(e);
|
||||
});
|
||||
|
||||
// Sort equipment groups by number descending (most recent equipment first)
|
||||
const sortedGroups = [...groups.entries()].sort((a, b) => {
|
||||
const numA = parseInt(a[0]) || 0;
|
||||
const numB = parseInt(b[0]) || 0;
|
||||
return numB - numA;
|
||||
});
|
||||
|
||||
const uniqueEquip = sortedGroups.length;
|
||||
const uniqueDrawings = unique.length;
|
||||
setPage('Drawings', `${uniqueDrawings} drawings / ${uniqueEquip} equipment`);
|
||||
|
||||
const groupsHtml = sortedGroups.map(([equip, items], gi) => {
|
||||
const totalBom = items.reduce((s, e) => s + e.bomItemCount, 0);
|
||||
|
||||
const rows = items.map((e, i) => {
|
||||
const dn = e.drawingNumber || '';
|
||||
const spaceIdx = dn.indexOf(' ');
|
||||
const drawingPart = spaceIdx > 0 ? dn.substring(spaceIdx + 1) : dn;
|
||||
|
||||
return `
|
||||
<tr class="clickable" onclick="router.go('drawing-detail', {id: '${encodeURIComponent(e.drawingNumber)}'})" style="animation: fadeSlideIn 0.2s ease ${0.02 * i}s forwards; opacity: 0">
|
||||
<td><strong>${esc(drawingPart) || '<span style="color:var(--text-dim)">\u2014</span>'}</strong></td>
|
||||
<td style="color:var(--text-secondary);font-size:13px">${esc(e.title) || ''}</td>
|
||||
<td><span class="badge badge-count">${e.bomItemCount}</span></td>
|
||||
<td style="color:var(--text-secondary)">${esc(e.exportedBy)}</td>
|
||||
<td style="font-family:var(--font-mono);font-size:13px;color:var(--text-secondary);white-space:nowrap">${fmtDate(e.exportedAt)}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
return `
|
||||
<div class="equip-group animate-in" id="equip-${esc(equip)}" style="animation-delay:${0.04 * gi}s">
|
||||
<div class="equip-header" onclick="toggleEquipGroup('equip-${esc(equip)}')">
|
||||
<span class="chevron-toggle open" id="equip-${esc(equip)}-icon">${icons.chevron}</span>
|
||||
<span class="equip-header-number">${esc(equip)}</span>
|
||||
<div class="equip-header-meta">
|
||||
<span class="equip-header-stat"><strong>${items.length}</strong> drawings</span>
|
||||
<span class="equip-header-stat"><strong>${totalBom}</strong> items</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="equip-body">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Drawing</th>
|
||||
<th>Title</th>
|
||||
<th style="width:80px">Items</th>
|
||||
<th>Exported By</th>
|
||||
<th style="width:180px">Latest Export</th>
|
||||
</tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
content.innerHTML = `
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card animate-in"><div class="stat-label">Drawings</div><div class="stat-value">${uniqueDrawings}</div></div>
|
||||
<div class="stat-card animate-in"><div class="stat-label">Equipment</div><div class="stat-value">${uniqueEquip}</div></div>
|
||||
</div>
|
||||
${groupsHtml}`;
|
||||
} catch (err) {
|
||||
content.innerHTML = `<div class="empty">Error: ${esc(err.message)}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
async drawingDetail(drawingEncoded, params) {
|
||||
const drawingNumber = decodeURIComponent(drawingEncoded);
|
||||
const exportId = params?.eid ? parseInt(params.eid) : null;
|
||||
const actions = document.getElementById('topbar-actions');
|
||||
const content = document.getElementById('page-content');
|
||||
setPage(drawingNumber, 'drawing');
|
||||
actions.innerHTML = '';
|
||||
content.innerHTML = `<div class="loading">Loading drawing</div>`;
|
||||
|
||||
try {
|
||||
const exports = await api.get(`/api/exports/by-drawing?drawingNumber=${encodeURIComponent(drawingNumber)}`);
|
||||
|
||||
if (exports.length === 0) {
|
||||
content.innerHTML = `
|
||||
<a class="back-link" onclick="router.go('drawings')">${icons.back} Back to drawings</a>
|
||||
<div class="empty">No exports found for this drawing.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
let allBom;
|
||||
const singleExport = exportId ? exports.find(e => e.id === exportId) : null;
|
||||
if (singleExport) {
|
||||
// Viewing a specific export - show only its BOM items
|
||||
allBom = (singleExport.bomItems || []).map(b => ({ ...b, exportId: singleExport.id, exportedAt: singleExport.exportedAt }));
|
||||
} else {
|
||||
// Viewing drawing overview - deduplicate by itemNo, keeping latest revision (exports are newest-first)
|
||||
const bomByItem = new Map();
|
||||
exports.forEach(exp => {
|
||||
(exp.bomItems || []).forEach(b => {
|
||||
if (!bomByItem.has(b.itemNo)) {
|
||||
bomByItem.set(b.itemNo, { ...b, exportId: exp.id, exportedAt: exp.exportedAt });
|
||||
}
|
||||
});
|
||||
});
|
||||
allBom = [...bomByItem.values()];
|
||||
}
|
||||
|
||||
// Store for cut list modal
|
||||
window._currentBom = allBom;
|
||||
|
||||
const bomRows = allBom.map((b, i) => {
|
||||
const hasDetails = b.cutTemplate || b.formProgram;
|
||||
const toggleId = `dbom-${b.id}`;
|
||||
return `
|
||||
<tr class="${hasDetails ? 'clickable' : ''}" ${hasDetails ? `onclick="toggleBomRow('${toggleId}')"` : ''} style="animation: fadeSlideIn 0.25s ease ${0.03 * i}s forwards; opacity: 0">
|
||||
<td style="width:32px">${hasDetails ? `<span class="chevron-toggle" id="${toggleId}-icon">${icons.chevron}</span>` : ''}</td>
|
||||
<td style="font-family:var(--font-mono);font-weight:600;color:var(--cyan)">${esc(b.itemNo)}</td>
|
||||
<td><strong>${esc(b.partName)}</strong></td>
|
||||
<td style="font-family:var(--font-mono);text-align:center;font-size:13px">${b.cutTemplate?.revision ?? ''}</td>
|
||||
<td style="color:var(--text-secondary)">${esc(b.description)}</td>
|
||||
<td><span style="font-family:var(--font-mono);font-size:13px">${esc(b.material)}</span></td>
|
||||
<td style="font-family:var(--font-mono);text-align:center">${b.qty ?? ''}</td>
|
||||
<td style="font-family:var(--font-mono);text-align:center">${b.totalQty ?? ''}</td>
|
||||
<td>
|
||||
${b.cutTemplate ? `<span class="badge badge-cyan">${icons.laser} DXF</span>` : ''}
|
||||
${b.formProgram ? `<span class="badge badge-amber">${icons.bend} Form</span>` : ''}
|
||||
</td>
|
||||
</tr>
|
||||
${hasDetails ? `<tr class="bom-expand-row" id="${toggleId}" style="display:none"><td colspan="9">${renderBomDetails(b)}</td></tr>` : ''}`;
|
||||
}).join('');
|
||||
|
||||
const backLink = singleExport
|
||||
? `<a class="back-link" onclick="router.go('exports')">${icons.back} Back to exports</a>`
|
||||
: `<a class="back-link" onclick="router.go('drawings')">${icons.back} Back to drawings</a>`;
|
||||
|
||||
const statsHtml = singleExport
|
||||
? `<div class="stats-grid">
|
||||
<div class="stat-card animate-in"><div class="stat-label">Exported By</div><div class="stat-value stat-sm">${esc(singleExport.exportedBy)}</div></div>
|
||||
<div class="stat-card animate-in"><div class="stat-label">BOM Items</div><div class="stat-value">${allBom.length}</div></div>
|
||||
<div class="stat-card animate-in"><div class="stat-label">Exported</div><div class="stat-value stat-sm">${fmtDate(singleExport.exportedAt)}</div></div>
|
||||
</div>`
|
||||
: `<div class="stats-grid">
|
||||
<div class="stat-card animate-in"><div class="stat-label">Exports</div><div class="stat-value">${exports.length}</div></div>
|
||||
<div class="stat-card animate-in"><div class="stat-label">BOM Items</div><div class="stat-value">${allBom.length}</div></div>
|
||||
<div class="stat-card animate-in"><div class="stat-label">Latest Export</div><div class="stat-value stat-sm">${fmtDate(exports[0].exportedAt)}</div></div>
|
||||
</div>`;
|
||||
|
||||
const bomHeader = singleExport ? 'BOM Items' : 'All BOM Items';
|
||||
const activeExport = singleExport || exports[0];
|
||||
const dxfCount = allBom.filter(b => b.cutTemplate?.contentHash).length;
|
||||
const pdfHash = activeExport.pdfContentHash;
|
||||
const pdfName = encodeURIComponent((drawingNumber || 'drawing') + '.pdf');
|
||||
|
||||
content.innerHTML = `
|
||||
${backLink}
|
||||
${statsHtml}
|
||||
|
||||
<div class="card animate-in">
|
||||
<div class="card-header">
|
||||
${bomHeader}
|
||||
<span class="badge badge-count">${allBom.length} items</span>
|
||||
<span style="margin-left:auto;display:flex;gap:6px">
|
||||
${dxfCount > 0 ? `<button class="btn btn-sm" onclick="showCutListModal(window._currentBom)">${icons.clipboard} Cut List</button>` : ''}
|
||||
${pdfHash ? `<a class="btn btn-amber btn-sm" href="/api/filebrowser/download?hash=${encodeURIComponent(pdfHash)}&ext=pdf&name=${pdfName}">${icons.download} PDF</a>` : ''}
|
||||
${dxfCount > 0 ? (singleExport
|
||||
? `<a class="btn btn-cyan btn-sm" href="/api/exports/${activeExport.id}/download-dxfs">${icons.download} All DXFs</a>`
|
||||
: `<a class="btn btn-cyan btn-sm" href="/api/exports/download-dxfs?drawingNumber=${encodeURIComponent(drawingNumber)}">${icons.download} All DXFs</a>`
|
||||
) : ''}
|
||||
</span>
|
||||
</div>
|
||||
${allBom.length ? `
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th style="width:32px"></th>
|
||||
<th style="width:60px">Item</th>
|
||||
<th>Part Name</th>
|
||||
<th style="width:45px;text-align:center">Rev</th>
|
||||
<th>Description</th>
|
||||
<th>Material</th>
|
||||
<th style="width:50px;text-align:center">Qty</th>
|
||||
<th style="width:55px;text-align:center">Total</th>
|
||||
<th style="width:120px">Data</th>
|
||||
</tr></thead>
|
||||
<tbody>${bomRows}</tbody>
|
||||
</table>` : '<div class="empty">No BOM items.</div>'}
|
||||
</div>`;
|
||||
} catch (err) {
|
||||
content.innerHTML = `<div class="empty">Error: ${esc(err.message)}</div>`;
|
||||
}
|
||||
},
|
||||
|
||||
async files(params) {
|
||||
const actions = document.getElementById('topbar-actions');
|
||||
const content = document.getElementById('page-content');
|
||||
setPage('Files');
|
||||
|
||||
const searchVal = params.q || '';
|
||||
actions.innerHTML = `
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<div class="search-box">
|
||||
${icons.search}
|
||||
<input type="text" id="file-search" placeholder="Search drawing number, filename..." value="${esc(searchVal)}">
|
||||
</div>
|
||||
<select id="file-type-filter" style="background:var(--surface);border:1px solid var(--border);border-radius:4px;padding:6px 10px;color:var(--text);font-family:var(--font-body);font-size:14px;height:36px">
|
||||
<option value="">All types</option>
|
||||
<option value="dxf">DXF only</option>
|
||||
<option value="pdf">PDF only</option>
|
||||
</select>
|
||||
</div>`;
|
||||
|
||||
content.innerHTML = `<div class="loading">Loading files</div>`;
|
||||
|
||||
const searchInput = document.getElementById('file-search');
|
||||
const typeFilter = document.getElementById('file-type-filter');
|
||||
let debounce;
|
||||
const refresh = () => {
|
||||
clearTimeout(debounce);
|
||||
debounce = setTimeout(() => router.go('files', { q: searchInput.value + (typeFilter.value ? '&type=' + typeFilter.value : '') }), 400);
|
||||
};
|
||||
searchInput.addEventListener('input', refresh);
|
||||
typeFilter.addEventListener('change', refresh);
|
||||
|
||||
// Parse search and type from combined param
|
||||
let searchQ = searchVal;
|
||||
let typeQ = '';
|
||||
if (searchVal.includes('&type=')) {
|
||||
const parts = searchVal.split('&type=');
|
||||
searchQ = parts[0];
|
||||
typeQ = parts[1] || '';
|
||||
searchInput.value = searchQ;
|
||||
typeFilter.value = typeQ;
|
||||
}
|
||||
|
||||
try {
|
||||
let url = '/api/filebrowser/files?';
|
||||
if (searchQ) url += `search=${encodeURIComponent(searchQ)}&`;
|
||||
if (typeQ) url += `type=${encodeURIComponent(typeQ)}&`;
|
||||
const data = await api.get(url);
|
||||
|
||||
setPage('Files', `${data.total} files`);
|
||||
|
||||
if (data.files.length === 0) {
|
||||
content.innerHTML = `<div class="empty">No files found.</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = data.files.map((f, i) => {
|
||||
const ext = f.fileType || f.fileName.split('.').pop().toLowerCase();
|
||||
const hashShort = f.contentHash ? f.contentHash.substring(0, 12) : '';
|
||||
return `
|
||||
<tr style="animation: fadeSlideIn 0.25s ease ${0.02 * i}s forwards; opacity: 0">
|
||||
<td><div class="file-name-cell">${ext === 'pdf' ? icons.filePdf : icons.fileDxf}<a href="/api/filebrowser/download?hash=${encodeURIComponent(f.contentHash)}&ext=${ext}&name=${encodeURIComponent(f.fileName)}">${esc(f.fileName)}</a></div></td>
|
||||
<td><span class="badge ${ext === 'dxf' ? 'badge-cyan' : 'badge-amber'}">${ext.toUpperCase()}</span></td>
|
||||
<td style="color:var(--text-secondary)">${esc(f.drawingNumber)}</td>
|
||||
<td style="font-family:var(--font-mono);font-size:13px;text-align:center;color:var(--text-secondary)">${f.revision != null ? f.revision : '\u2014'}</td>
|
||||
<td style="font-family:var(--font-mono);font-size:13px;color:var(--text-secondary)">${f.thickness != null ? f.thickness.toFixed(4) + '"' : '\u2014'}</td>
|
||||
<td style="font-family:var(--font-mono);font-size:13px;color:var(--text-secondary)">${fmtDate(f.createdAt)}</td>
|
||||
<td style="font-family:var(--font-mono);font-size:12px;color:var(--text-dim)">${esc(hashShort)}</td>
|
||||
<td style="white-space:nowrap">
|
||||
<a class="btn btn-cyan btn-sm" href="/api/filebrowser/download?hash=${encodeURIComponent(f.contentHash)}&ext=${ext}&name=${encodeURIComponent(f.fileName)}">${icons.download}</a>
|
||||
</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
|
||||
content.innerHTML = `
|
||||
<div class="card animate-in">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Name</th>
|
||||
<th style="width:60px">Type</th>
|
||||
<th>Drawing</th>
|
||||
<th style="width:45px;text-align:center">Rev</th>
|
||||
<th style="width:90px">Thickness</th>
|
||||
<th style="width:170px">Date</th>
|
||||
<th style="width:100px">Hash</th>
|
||||
<th style="width:90px">Actions</th>
|
||||
</tr></thead>
|
||||
<tbody>${rows}</tbody>
|
||||
</table>
|
||||
</div>`;
|
||||
} catch (err) {
|
||||
content.innerHTML = `<div class="empty">Error: ${esc(err.message)}</div>`;
|
||||
}
|
||||
}
|
||||
};
|
||||
36
FabWorks.Api/wwwroot/js/router.js
Normal file
36
FabWorks.Api/wwwroot/js/router.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const router = {
|
||||
go(page, params = {}) {
|
||||
const qParts = [];
|
||||
if (params.q) qParts.push('q=' + encodeURIComponent(params.q));
|
||||
if (params.eid) qParts.push('eid=' + encodeURIComponent(params.eid));
|
||||
const hash = page + (params.id ? '/' + params.id : '') + (qParts.length ? '?' + qParts.join('&') : '');
|
||||
location.hash = hash;
|
||||
},
|
||||
parse() {
|
||||
const h = location.hash.slice(1) || 'exports';
|
||||
const [path, qs] = h.split('?');
|
||||
const parts = path.split('/');
|
||||
const params = {};
|
||||
if (qs) qs.split('&').forEach(p => { const [k,v] = p.split('='); params[k] = decodeURIComponent(v); });
|
||||
return { page: parts[0], id: parts[1], params };
|
||||
},
|
||||
init() {
|
||||
window.addEventListener('hashchange', () => this.dispatch());
|
||||
this.dispatch();
|
||||
},
|
||||
dispatch() {
|
||||
const { page, id, params } = this.parse();
|
||||
document.querySelectorAll('.nav-item').forEach(el => {
|
||||
el.classList.toggle('active',
|
||||
el.dataset.page === page ||
|
||||
(page === 'drawing-detail' && el.dataset.page === 'drawings'));
|
||||
});
|
||||
switch(page) {
|
||||
case 'exports': pages.exports(params); break;
|
||||
case 'drawings': pages.drawings(params); break;
|
||||
case 'drawing-detail': pages.drawingDetail(id, params); break;
|
||||
case 'files': pages.files(params); break;
|
||||
default: pages.exports(params);
|
||||
}
|
||||
}
|
||||
};
|
||||
94
FabWorks.Core/Data/FabWorksDbContext.cs
Normal file
94
FabWorks.Core/Data/FabWorksDbContext.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using FabWorks.Core.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FabWorks.Core.Data
|
||||
{
|
||||
public class FabWorksDbContext : DbContext
|
||||
{
|
||||
public DbSet<ExportRecord> ExportRecords { get; set; }
|
||||
public DbSet<BomItem> BomItems { get; set; }
|
||||
public DbSet<CutTemplate> CutTemplates { get; set; }
|
||||
public DbSet<FormProgram> FormPrograms { get; set; }
|
||||
public DbSet<Drawing> Drawings { get; set; }
|
||||
|
||||
public FabWorksDbContext(DbContextOptions<FabWorksDbContext> options) : base(options) { }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.Entity<ExportRecord>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.DrawingNumber).HasMaxLength(100);
|
||||
entity.Property(e => e.Title).HasMaxLength(200);
|
||||
entity.Property(e => e.EquipmentNo).HasMaxLength(50);
|
||||
entity.Property(e => e.DrawingNo).HasMaxLength(50);
|
||||
entity.Property(e => e.SourceFilePath).HasMaxLength(500);
|
||||
entity.Property(e => e.OutputFolder).HasMaxLength(500);
|
||||
entity.Property(e => e.ExportedBy).HasMaxLength(100);
|
||||
entity.Property(e => e.PdfContentHash).HasMaxLength(64);
|
||||
|
||||
entity.HasMany(e => e.BomItems)
|
||||
.WithOne(b => b.ExportRecord)
|
||||
.HasForeignKey(b => b.ExportRecordId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(e => e.Drawing)
|
||||
.WithMany(d => d.ExportRecords)
|
||||
.HasForeignKey(e => e.DrawingId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<BomItem>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.ID);
|
||||
entity.Property(e => e.ItemNo).HasMaxLength(50);
|
||||
entity.Property(e => e.PartNo).HasMaxLength(100);
|
||||
entity.Property(e => e.Description).HasMaxLength(500);
|
||||
entity.Property(e => e.PartName).HasMaxLength(200);
|
||||
entity.Property(e => e.ConfigurationName).HasMaxLength(100);
|
||||
entity.Property(e => e.Material).HasMaxLength(100);
|
||||
|
||||
entity.HasOne(e => e.CutTemplate)
|
||||
.WithOne(ct => ct.BomItem)
|
||||
.HasForeignKey<CutTemplate>(ct => ct.BomItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
entity.HasOne(e => e.FormProgram)
|
||||
.WithOne(fp => fp.BomItem)
|
||||
.HasForeignKey<FormProgram>(fp => fp.BomItemId)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<CutTemplate>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.DxfFilePath).HasMaxLength(500);
|
||||
entity.Property(e => e.CutTemplateName).HasMaxLength(100);
|
||||
entity.Property(e => e.ContentHash).HasMaxLength(64);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<FormProgram>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.ProgramFilePath).HasMaxLength(500);
|
||||
entity.Property(e => e.ContentHash).HasMaxLength(64);
|
||||
entity.Property(e => e.ProgramName).HasMaxLength(200);
|
||||
entity.Property(e => e.MaterialType).HasMaxLength(50);
|
||||
entity.Property(e => e.UpperToolNames).HasMaxLength(500);
|
||||
entity.Property(e => e.LowerToolNames).HasMaxLength(500);
|
||||
entity.Property(e => e.SetupNotes).HasMaxLength(2000);
|
||||
});
|
||||
|
||||
modelBuilder.Entity<Drawing>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.Property(e => e.DrawingNumber).HasMaxLength(100);
|
||||
entity.Property(e => e.Title).HasMaxLength(200);
|
||||
entity.Property(e => e.PdfContentHash).HasMaxLength(64);
|
||||
entity.HasIndex(e => e.DrawingNumber).IsUnique();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
17
FabWorks.Core/FabWorks.Core.csproj
Normal file
17
FabWorks.Core/FabWorks.Core.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.11">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
270
FabWorks.Core/Migrations/20260218171742_InitialCreate.Designer.cs
generated
Normal file
270
FabWorks.Core/Migrations/20260218171742_InitialCreate.Designer.cs
generated
Normal file
@@ -0,0 +1,270 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
[DbContext(typeof(FabWorksDbContext))]
|
||||
[Migration("20260218171742_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
|
||||
|
||||
b.Property<string>("ConfigurationName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<int>("ExportRecordId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ItemNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Material")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PartName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("PartNo")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int?>("Qty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("TotalQty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("ExportRecordId");
|
||||
|
||||
b.ToTable("BomItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("CutTemplateName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<double?>("DefaultBendRadius")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("DxfFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CutTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DrawingNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("EquipmentNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime>("ExportedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ExportedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("OutputFolder")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("SourceFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BendCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("LowerToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("MaterialType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("ProgramFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("ProgramName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("SetupNotes")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("UpperToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("FormPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.ExportRecord", "ExportRecord")
|
||||
.WithMany("BomItems")
|
||||
.HasForeignKey("ExportRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExportRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("CutTemplate")
|
||||
.HasForeignKey("FabWorks.Core.Models.CutTemplate", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("FormProgram")
|
||||
.HasForeignKey("FabWorks.Core.Models.FormProgram", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Navigation("CutTemplate");
|
||||
|
||||
b.Navigation("FormProgram");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Navigation("BomItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
151
FabWorks.Core/Migrations/20260218171742_InitialCreate.cs
Normal file
151
FabWorks.Core/Migrations/20260218171742_InitialCreate.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExportRecords",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DrawingNumber = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
Title = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||
EquipmentNo = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||
DrawingNo = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||
SourceFilePath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
OutputFolder = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
ExportedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
ExportedBy = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
PdfContentHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ExportRecords", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "BomItems",
|
||||
columns: table => new
|
||||
{
|
||||
ID = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
ItemNo = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||
PartNo = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
SortOrder = table.Column<int>(type: "int", nullable: false),
|
||||
Qty = table.Column<int>(type: "int", nullable: true),
|
||||
TotalQty = table.Column<int>(type: "int", nullable: true),
|
||||
Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
PartName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||
ConfigurationName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
Material = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
ExportRecordId = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_BomItems", x => x.ID);
|
||||
table.ForeignKey(
|
||||
name: "FK_BomItems_ExportRecords_ExportRecordId",
|
||||
column: x => x.ExportRecordId,
|
||||
principalTable: "ExportRecords",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CutTemplates",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DxfFilePath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
ContentHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
CutTemplateName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
Thickness = table.Column<double>(type: "float", nullable: true),
|
||||
KFactor = table.Column<double>(type: "float", nullable: true),
|
||||
DefaultBendRadius = table.Column<double>(type: "float", nullable: true),
|
||||
BomItemId = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CutTemplates", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CutTemplates_BomItems_BomItemId",
|
||||
column: x => x.BomItemId,
|
||||
principalTable: "BomItems",
|
||||
principalColumn: "ID",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "FormPrograms",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
ProgramFilePath = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
ContentHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
ProgramName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||
Thickness = table.Column<double>(type: "float", nullable: true),
|
||||
MaterialType = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
|
||||
KFactor = table.Column<double>(type: "float", nullable: true),
|
||||
BendCount = table.Column<int>(type: "int", nullable: false),
|
||||
UpperToolNames = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
LowerToolNames = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
|
||||
SetupNotes = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: true),
|
||||
BomItemId = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_FormPrograms", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_FormPrograms_BomItems_BomItemId",
|
||||
column: x => x.BomItemId,
|
||||
principalTable: "BomItems",
|
||||
principalColumn: "ID",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_BomItems_ExportRecordId",
|
||||
table: "BomItems",
|
||||
column: "ExportRecordId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CutTemplates_BomItemId",
|
||||
table: "CutTemplates",
|
||||
column: "BomItemId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_FormPrograms_BomItemId",
|
||||
table: "FormPrograms",
|
||||
column: "BomItemId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "CutTemplates");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "FormPrograms");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "BomItems");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExportRecords");
|
||||
}
|
||||
}
|
||||
}
|
||||
273
FabWorks.Core/Migrations/20260219134027_AddCutTemplateRevision.Designer.cs
generated
Normal file
273
FabWorks.Core/Migrations/20260219134027_AddCutTemplateRevision.Designer.cs
generated
Normal file
@@ -0,0 +1,273 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
[DbContext(typeof(FabWorksDbContext))]
|
||||
[Migration("20260219134027_AddCutTemplateRevision")]
|
||||
partial class AddCutTemplateRevision
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
|
||||
|
||||
b.Property<string>("ConfigurationName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<int>("ExportRecordId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ItemNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Material")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PartName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("PartNo")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int?>("Qty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("TotalQty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("ExportRecordId");
|
||||
|
||||
b.ToTable("BomItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("CutTemplateName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<double?>("DefaultBendRadius")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("DxfFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CutTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DrawingNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("EquipmentNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime>("ExportedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ExportedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("OutputFolder")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("SourceFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BendCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("LowerToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("MaterialType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("ProgramFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("ProgramName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("SetupNotes")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("UpperToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("FormPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.ExportRecord", "ExportRecord")
|
||||
.WithMany("BomItems")
|
||||
.HasForeignKey("ExportRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExportRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("CutTemplate")
|
||||
.HasForeignKey("FabWorks.Core.Models.CutTemplate", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("FormProgram")
|
||||
.HasForeignKey("FabWorks.Core.Models.FormProgram", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Navigation("CutTemplate");
|
||||
|
||||
b.Navigation("FormProgram");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Navigation("BomItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCutTemplateRevision : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Revision",
|
||||
table: "CutTemplates",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Revision",
|
||||
table: "CutTemplates");
|
||||
}
|
||||
}
|
||||
}
|
||||
325
FabWorks.Core/Migrations/20260220125334_AddDrawingEntity.Designer.cs
generated
Normal file
325
FabWorks.Core/Migrations/20260220125334_AddDrawingEntity.Designer.cs
generated
Normal file
@@ -0,0 +1,325 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
[DbContext(typeof(FabWorksDbContext))]
|
||||
[Migration("20260220125334_AddDrawingEntity")]
|
||||
partial class AddDrawingEntity
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
|
||||
|
||||
b.Property<string>("ConfigurationName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<int>("ExportRecordId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ItemNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Material")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PartName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("PartNo")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int?>("Qty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("TotalQty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("ExportRecordId");
|
||||
|
||||
b.ToTable("BomItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("CutTemplateName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<double?>("DefaultBendRadius")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("DxfFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CutTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.Drawing", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DrawingNumber")
|
||||
.IsUnique()
|
||||
.HasFilter("[DrawingNumber] IS NOT NULL");
|
||||
|
||||
b.ToTable("Drawings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("DrawingId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("DrawingNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("EquipmentNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime>("ExportedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ExportedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("OutputFolder")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("SourceFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DrawingId");
|
||||
|
||||
b.ToTable("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BendCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("LowerToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("MaterialType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("ProgramFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("ProgramName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("SetupNotes")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("UpperToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("FormPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.ExportRecord", "ExportRecord")
|
||||
.WithMany("BomItems")
|
||||
.HasForeignKey("ExportRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExportRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("CutTemplate")
|
||||
.HasForeignKey("FabWorks.Core.Models.CutTemplate", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.Drawing", "Drawing")
|
||||
.WithMany("ExportRecords")
|
||||
.HasForeignKey("DrawingId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Drawing");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("FormProgram")
|
||||
.HasForeignKey("FabWorks.Core.Models.FormProgram", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Navigation("CutTemplate");
|
||||
|
||||
b.Navigation("FormProgram");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.Drawing", b =>
|
||||
{
|
||||
b.Navigation("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Navigation("BomItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
75
FabWorks.Core/Migrations/20260220125334_AddDrawingEntity.cs
Normal file
75
FabWorks.Core/Migrations/20260220125334_AddDrawingEntity.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDrawingEntity : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "DrawingId",
|
||||
table: "ExportRecords",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Drawings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "int", nullable: false)
|
||||
.Annotation("SqlServer:Identity", "1, 1"),
|
||||
DrawingNumber = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
Title = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
|
||||
PdfContentHash = table.Column<string>(type: "nvarchar(64)", maxLength: 64, nullable: true),
|
||||
Revision = table.Column<int>(type: "int", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Drawings", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExportRecords_DrawingId",
|
||||
table: "ExportRecords",
|
||||
column: "DrawingId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Drawings_DrawingNumber",
|
||||
table: "Drawings",
|
||||
column: "DrawingNumber",
|
||||
unique: true,
|
||||
filter: "[DrawingNumber] IS NOT NULL");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_ExportRecords_Drawings_DrawingId",
|
||||
table: "ExportRecords",
|
||||
column: "DrawingId",
|
||||
principalTable: "Drawings",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_ExportRecords_Drawings_DrawingId",
|
||||
table: "ExportRecords");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Drawings");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_ExportRecords_DrawingId",
|
||||
table: "ExportRecords");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DrawingId",
|
||||
table: "ExportRecords");
|
||||
}
|
||||
}
|
||||
}
|
||||
325
FabWorks.Core/Migrations/20260220130029_SeedDrawingsFromExistingExports.Designer.cs
generated
Normal file
325
FabWorks.Core/Migrations/20260220130029_SeedDrawingsFromExistingExports.Designer.cs
generated
Normal file
@@ -0,0 +1,325 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
[DbContext(typeof(FabWorksDbContext))]
|
||||
[Migration("20260220130029_SeedDrawingsFromExistingExports")]
|
||||
partial class SeedDrawingsFromExistingExports
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
|
||||
|
||||
b.Property<string>("ConfigurationName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<int>("ExportRecordId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ItemNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Material")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PartName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("PartNo")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int?>("Qty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("TotalQty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("ExportRecordId");
|
||||
|
||||
b.ToTable("BomItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("CutTemplateName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<double?>("DefaultBendRadius")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("DxfFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CutTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.Drawing", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DrawingNumber")
|
||||
.IsUnique()
|
||||
.HasFilter("[DrawingNumber] IS NOT NULL");
|
||||
|
||||
b.ToTable("Drawings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("DrawingId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("DrawingNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("EquipmentNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime>("ExportedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ExportedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("OutputFolder")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("SourceFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DrawingId");
|
||||
|
||||
b.ToTable("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BendCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("LowerToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("MaterialType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("ProgramFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("ProgramName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("SetupNotes")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("UpperToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("FormPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.ExportRecord", "ExportRecord")
|
||||
.WithMany("BomItems")
|
||||
.HasForeignKey("ExportRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExportRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("CutTemplate")
|
||||
.HasForeignKey("FabWorks.Core.Models.CutTemplate", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.Drawing", "Drawing")
|
||||
.WithMany("ExportRecords")
|
||||
.HasForeignKey("DrawingId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Drawing");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("FormProgram")
|
||||
.HasForeignKey("FabWorks.Core.Models.FormProgram", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Navigation("CutTemplate");
|
||||
|
||||
b.Navigation("FormProgram");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.Drawing", b =>
|
||||
{
|
||||
b.Navigation("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Navigation("BomItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class SeedDrawingsFromExistingExports : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// Create Drawing records from existing ExportRecords (latest hash per DrawingNumber)
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO Drawings (DrawingNumber, Title, PdfContentHash, Revision)
|
||||
SELECT
|
||||
sub.DrawingNumber,
|
||||
sub.Title,
|
||||
sub.PdfContentHash,
|
||||
1
|
||||
FROM (
|
||||
SELECT
|
||||
e.DrawingNumber,
|
||||
e.Title,
|
||||
e.PdfContentHash,
|
||||
ROW_NUMBER() OVER (PARTITION BY e.DrawingNumber ORDER BY e.Id DESC) AS rn
|
||||
FROM ExportRecords e
|
||||
WHERE e.DrawingNumber IS NOT NULL
|
||||
AND e.PdfContentHash IS NOT NULL
|
||||
) sub
|
||||
WHERE sub.rn = 1;
|
||||
");
|
||||
|
||||
// Link ExportRecords to their Drawing
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE er
|
||||
SET er.DrawingId = d.Id
|
||||
FROM ExportRecords er
|
||||
INNER JOIN Drawings d ON d.DrawingNumber = er.DrawingNumber
|
||||
WHERE er.PdfContentHash IS NOT NULL;
|
||||
");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.Sql("UPDATE ExportRecords SET DrawingId = NULL;");
|
||||
migrationBuilder.Sql("DELETE FROM Drawings;");
|
||||
}
|
||||
}
|
||||
}
|
||||
325
FabWorks.Core/Migrations/20260220171747_MoveRevisionFromDrawingToExportRecord.Designer.cs
generated
Normal file
325
FabWorks.Core/Migrations/20260220171747_MoveRevisionFromDrawingToExportRecord.Designer.cs
generated
Normal file
@@ -0,0 +1,325 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
[DbContext(typeof(FabWorksDbContext))]
|
||||
[Migration("20260220171747_MoveRevisionFromDrawingToExportRecord")]
|
||||
partial class MoveRevisionFromDrawingToExportRecord
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
|
||||
|
||||
b.Property<string>("ConfigurationName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<int>("ExportRecordId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ItemNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Material")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PartName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("PartNo")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int?>("Qty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("TotalQty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("ExportRecordId");
|
||||
|
||||
b.ToTable("BomItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("CutTemplateName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<double?>("DefaultBendRadius")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("DxfFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CutTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.Drawing", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DrawingNumber")
|
||||
.IsUnique()
|
||||
.HasFilter("[DrawingNumber] IS NOT NULL");
|
||||
|
||||
b.ToTable("Drawings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("DrawingId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("DrawingNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int?>("DrawingRevision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("EquipmentNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime>("ExportedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ExportedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("OutputFolder")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("SourceFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DrawingId");
|
||||
|
||||
b.ToTable("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BendCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("LowerToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("MaterialType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("ProgramFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("ProgramName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("SetupNotes")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("UpperToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("FormPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.ExportRecord", "ExportRecord")
|
||||
.WithMany("BomItems")
|
||||
.HasForeignKey("ExportRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExportRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("CutTemplate")
|
||||
.HasForeignKey("FabWorks.Core.Models.CutTemplate", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.Drawing", "Drawing")
|
||||
.WithMany("ExportRecords")
|
||||
.HasForeignKey("DrawingId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Drawing");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("FormProgram")
|
||||
.HasForeignKey("FabWorks.Core.Models.FormProgram", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Navigation("CutTemplate");
|
||||
|
||||
b.Navigation("FormProgram");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.Drawing", b =>
|
||||
{
|
||||
b.Navigation("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Navigation("BomItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class MoveRevisionFromDrawingToExportRecord : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Revision",
|
||||
table: "Drawings");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "DrawingRevision",
|
||||
table: "ExportRecords",
|
||||
type: "int",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DrawingRevision",
|
||||
table: "ExportRecords");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "Revision",
|
||||
table: "Drawings",
|
||||
type: "int",
|
||||
nullable: false,
|
||||
defaultValue: 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
322
FabWorks.Core/Migrations/FabWorksDbContextModelSnapshot.cs
Normal file
322
FabWorks.Core/Migrations/FabWorksDbContextModelSnapshot.cs
Normal file
@@ -0,0 +1,322 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using FabWorks.Core.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace FabWorks.Core.Migrations
|
||||
{
|
||||
[DbContext(typeof(FabWorksDbContext))]
|
||||
partial class FabWorksDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.11")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("ID"));
|
||||
|
||||
b.Property<string>("ConfigurationName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<int>("ExportRecordId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ItemNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("Material")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PartName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("PartNo")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int?>("Qty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("SortOrder")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int?>("TotalQty")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("ExportRecordId");
|
||||
|
||||
b.ToTable("BomItems");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("CutTemplateName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<double?>("DefaultBendRadius")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("DxfFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<int>("Revision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("CutTemplates");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.Drawing", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DrawingNumber")
|
||||
.IsUnique()
|
||||
.HasFilter("[DrawingNumber] IS NOT NULL");
|
||||
|
||||
b.ToTable("Drawings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int?>("DrawingId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("DrawingNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("DrawingNumber")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<int?>("DrawingRevision")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("EquipmentNo")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<DateTime>("ExportedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ExportedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("nvarchar(100)");
|
||||
|
||||
b.Property<string>("OutputFolder")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("PdfContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("SourceFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DrawingId");
|
||||
|
||||
b.ToTable("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<int>("BendCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("BomItemId")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ContentHash")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<double?>("KFactor")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("LowerToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("MaterialType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("nvarchar(50)");
|
||||
|
||||
b.Property<string>("ProgramFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.Property<string>("ProgramName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("nvarchar(200)");
|
||||
|
||||
b.Property<string>("SetupNotes")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("nvarchar(2000)");
|
||||
|
||||
b.Property<double?>("Thickness")
|
||||
.HasColumnType("float");
|
||||
|
||||
b.Property<string>("UpperToolNames")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("nvarchar(500)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BomItemId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("FormPrograms");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.ExportRecord", "ExportRecord")
|
||||
.WithMany("BomItems")
|
||||
.HasForeignKey("ExportRecordId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ExportRecord");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.CutTemplate", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("CutTemplate")
|
||||
.HasForeignKey("FabWorks.Core.Models.CutTemplate", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.Drawing", "Drawing")
|
||||
.WithMany("ExportRecords")
|
||||
.HasForeignKey("DrawingId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("Drawing");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.FormProgram", b =>
|
||||
{
|
||||
b.HasOne("FabWorks.Core.Models.BomItem", "BomItem")
|
||||
.WithOne("FormProgram")
|
||||
.HasForeignKey("FabWorks.Core.Models.FormProgram", "BomItemId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("BomItem");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.BomItem", b =>
|
||||
{
|
||||
b.Navigation("CutTemplate");
|
||||
|
||||
b.Navigation("FormProgram");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.Drawing", b =>
|
||||
{
|
||||
b.Navigation("ExportRecords");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("FabWorks.Core.Models.ExportRecord", b =>
|
||||
{
|
||||
b.Navigation("BomItems");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
22
FabWorks.Core/Models/BomItem.cs
Normal file
22
FabWorks.Core/Models/BomItem.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace FabWorks.Core.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 ExportRecordId { get; set; }
|
||||
public virtual ExportRecord ExportRecord { get; set; }
|
||||
|
||||
public virtual CutTemplate CutTemplate { get; set; }
|
||||
public virtual FormProgram FormProgram { get; set; }
|
||||
}
|
||||
}
|
||||
33
FabWorks.Core/Models/CutTemplate.cs
Normal file
33
FabWorks.Core/Models/CutTemplate.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace FabWorks.Core.Models
|
||||
{
|
||||
public class CutTemplate
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DxfFilePath { get; set; } = "";
|
||||
public string ContentHash { get; set; }
|
||||
public string CutTemplateName { get; set; } = "";
|
||||
|
||||
private double? _thickness;
|
||||
public double? Thickness
|
||||
{
|
||||
get => _thickness;
|
||||
set => _thickness = value.HasValue ? Math.Round(value.Value, 8) : null;
|
||||
}
|
||||
|
||||
public int Revision { get; set; } = 1;
|
||||
|
||||
public double? KFactor { get; set; }
|
||||
|
||||
private double? _defaultBendRadius;
|
||||
public double? DefaultBendRadius
|
||||
{
|
||||
get => _defaultBendRadius;
|
||||
set => _defaultBendRadius = value.HasValue ? Math.Round(value.Value, 8) : null;
|
||||
}
|
||||
|
||||
public int BomItemId { get; set; }
|
||||
public virtual BomItem BomItem { get; set; }
|
||||
}
|
||||
}
|
||||
14
FabWorks.Core/Models/Drawing.cs
Normal file
14
FabWorks.Core/Models/Drawing.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FabWorks.Core.Models
|
||||
{
|
||||
public class Drawing
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DrawingNumber { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string PdfContentHash { get; set; }
|
||||
|
||||
public virtual ICollection<ExportRecord> ExportRecords { get; set; } = new List<ExportRecord>();
|
||||
}
|
||||
}
|
||||
25
FabWorks.Core/Models/ExportRecord.cs
Normal file
25
FabWorks.Core/Models/ExportRecord.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FabWorks.Core.Models
|
||||
{
|
||||
public class ExportRecord
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string DrawingNumber { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string EquipmentNo { get; set; }
|
||||
public string DrawingNo { get; set; }
|
||||
public string SourceFilePath { get; set; }
|
||||
public string OutputFolder { get; set; }
|
||||
public DateTime ExportedAt { get; set; }
|
||||
public string ExportedBy { get; set; }
|
||||
public string PdfContentHash { get; set; }
|
||||
|
||||
public int? DrawingId { get; set; }
|
||||
public int? DrawingRevision { get; set; }
|
||||
public virtual Drawing Drawing { get; set; }
|
||||
|
||||
public virtual ICollection<BomItem> BomItems { get; set; } = new List<BomItem>();
|
||||
}
|
||||
}
|
||||
20
FabWorks.Core/Models/FormProgram.cs
Normal file
20
FabWorks.Core/Models/FormProgram.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace FabWorks.Core.Models
|
||||
{
|
||||
public class FormProgram
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string ProgramFilePath { get; set; } = "";
|
||||
public string ContentHash { get; set; }
|
||||
public string ProgramName { get; set; } = "";
|
||||
public double? Thickness { get; set; }
|
||||
public string MaterialType { get; set; } = "";
|
||||
public double? KFactor { get; set; }
|
||||
public int BendCount { get; set; }
|
||||
public string UpperToolNames { get; set; } = "";
|
||||
public string LowerToolNames { get; set; } = "";
|
||||
public string SetupNotes { get; set; } = "";
|
||||
|
||||
public int BomItemId { get; set; }
|
||||
public virtual BomItem BomItem { get; set; }
|
||||
}
|
||||
}
|
||||
171
FabWorks.Core/PressBrake/Extensions.cs
Normal file
171
FabWorks.Core/PressBrake/Extensions.cs
Normal file
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace FabWorks.Core.PressBrake
|
||||
{
|
||||
internal static class Extensions
|
||||
{
|
||||
private static bool? ToBool(this string s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return null;
|
||||
|
||||
int intValue;
|
||||
|
||||
if (!int.TryParse(s, out intValue))
|
||||
return null;
|
||||
|
||||
return Convert.ToBoolean(intValue);
|
||||
}
|
||||
|
||||
public static bool ToBool(this XAttribute a, bool defaultValue = false)
|
||||
{
|
||||
if (a == null)
|
||||
return defaultValue;
|
||||
|
||||
var b = a.Value.ToBool();
|
||||
|
||||
return b != null ? b.Value : defaultValue;
|
||||
}
|
||||
|
||||
public static bool? ToBoolOrNull(this XAttribute a)
|
||||
{
|
||||
if (a == null)
|
||||
return null;
|
||||
|
||||
return a.Value.ToBool();
|
||||
}
|
||||
|
||||
private static int? ToInt(this string s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return null;
|
||||
|
||||
int intValue;
|
||||
|
||||
if (!int.TryParse(s, out intValue))
|
||||
return null;
|
||||
|
||||
return intValue;
|
||||
}
|
||||
|
||||
public static int ToInt(this XAttribute a, int defaultValue = 0)
|
||||
{
|
||||
if (a == null)
|
||||
return defaultValue;
|
||||
|
||||
var b = a.Value.ToInt();
|
||||
|
||||
return b != null ? b.Value : defaultValue;
|
||||
}
|
||||
|
||||
public static int? ToIntOrNull(this XAttribute a)
|
||||
{
|
||||
if (a == null)
|
||||
return null;
|
||||
|
||||
return a.Value.ToInt();
|
||||
}
|
||||
|
||||
public static int ToInt(this XElement a, int defaultValue = 0)
|
||||
{
|
||||
if (a == null)
|
||||
return defaultValue;
|
||||
|
||||
var b = a.Value.ToInt();
|
||||
|
||||
return b != null ? b.Value : defaultValue;
|
||||
}
|
||||
|
||||
public static int? ToIntOrNull(this XElement a)
|
||||
{
|
||||
if (a == null)
|
||||
return null;
|
||||
|
||||
return a.Value.ToInt();
|
||||
}
|
||||
|
||||
private static double? ToDouble(this string s)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(s))
|
||||
return null;
|
||||
|
||||
double d;
|
||||
|
||||
if (!double.TryParse(s, out d))
|
||||
return null;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public static double ToDouble(this XAttribute a, double defaultValue = 0)
|
||||
{
|
||||
if (a == null)
|
||||
return defaultValue;
|
||||
|
||||
var b = a.Value.ToDouble();
|
||||
|
||||
return b != null ? b.Value : defaultValue;
|
||||
}
|
||||
|
||||
public static double? ToDoubleOrNull(this XAttribute a)
|
||||
{
|
||||
if (a == null)
|
||||
return null;
|
||||
|
||||
return a.Value.ToDouble();
|
||||
}
|
||||
|
||||
public static double ToDouble(this XElement a, double defaultValue = 0)
|
||||
{
|
||||
if (a == null)
|
||||
return defaultValue;
|
||||
|
||||
var b = a.Value.ToDouble();
|
||||
|
||||
return b != null ? b.Value : defaultValue;
|
||||
}
|
||||
|
||||
public static double? ToDoubleOrNull(this XElement a)
|
||||
{
|
||||
if (a == null)
|
||||
return null;
|
||||
|
||||
return a.Value.ToDouble();
|
||||
}
|
||||
|
||||
public static DateTime? ToDateTime(this XAttribute a)
|
||||
{
|
||||
if (a == null || string.IsNullOrWhiteSpace(a.Value))
|
||||
return null;
|
||||
|
||||
DateTime d;
|
||||
|
||||
if (!DateTime.TryParse(a.Value, out d))
|
||||
return null;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public static TimeSpan? ToTimeSpan(this XElement e)
|
||||
{
|
||||
if (e == null || string.IsNullOrWhiteSpace(e.Value))
|
||||
return null;
|
||||
|
||||
TimeSpan d;
|
||||
|
||||
if (!TimeSpan.TryParse(e.Value, out d))
|
||||
return null;
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
public static DateTime RoundDown(this DateTime dt, TimeSpan d)
|
||||
{
|
||||
var modTicks = dt.Ticks % d.Ticks;
|
||||
var delta = -modTicks;
|
||||
|
||||
return new DateTime(dt.Ticks + delta, dt.Kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
FabWorks.Core/PressBrake/MatType.cs
Normal file
11
FabWorks.Core/PressBrake/MatType.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace FabWorks.Core.PressBrake
|
||||
{
|
||||
public enum MatType
|
||||
{
|
||||
MildSteel,
|
||||
HighStrengthSteel,
|
||||
Stainless,
|
||||
SoftAluminum,
|
||||
HardAluminum
|
||||
}
|
||||
}
|
||||
59
FabWorks.Core/PressBrake/Program.cs
Normal file
59
FabWorks.Core/PressBrake/Program.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace FabWorks.Core.PressBrake
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public Program()
|
||||
{
|
||||
UpperToolSets = new List<ToolSetup>();
|
||||
LowerToolSets = new List<ToolSetup>();
|
||||
Steps = new List<Step>();
|
||||
}
|
||||
|
||||
public int Version { get; set; }
|
||||
|
||||
public string ProgName { get; set; }
|
||||
|
||||
public string FilePath { get; set; }
|
||||
|
||||
public double MatThick { get; set; }
|
||||
|
||||
public MatType MatType { get; set; }
|
||||
|
||||
public double KFactor { get; set; }
|
||||
|
||||
public string TeachName { get; set; }
|
||||
|
||||
public string PartName { get; set; }
|
||||
|
||||
public string SetupNotes { get; set; }
|
||||
|
||||
public string ProgNotes { get; set; }
|
||||
|
||||
public bool RZEnabled { get; set; }
|
||||
|
||||
public List<ToolSetup> UpperToolSets { get; set; }
|
||||
|
||||
public List<ToolSetup> LowerToolSets { get; set; }
|
||||
|
||||
public List<Step> Steps { get; set; }
|
||||
|
||||
public static Program Load(string file)
|
||||
{
|
||||
var reader = new ProgramReader();
|
||||
reader.Read(file);
|
||||
return reader.Program;
|
||||
}
|
||||
|
||||
public static Program Load(Stream stream)
|
||||
{
|
||||
var reader = new ProgramReader();
|
||||
reader.Read(stream);
|
||||
return reader.Program;
|
||||
}
|
||||
}
|
||||
}
|
||||
149
FabWorks.Core/PressBrake/ProgramReader.cs
Normal file
149
FabWorks.Core/PressBrake/ProgramReader.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace FabWorks.Core.PressBrake
|
||||
{
|
||||
public class ProgramReader
|
||||
{
|
||||
public Program Program { get; set; }
|
||||
|
||||
public ProgramReader()
|
||||
{
|
||||
Program = new Program();
|
||||
}
|
||||
|
||||
public void Read(string file)
|
||||
{
|
||||
var xml = XDocument.Load(file);
|
||||
Program.FilePath = file;
|
||||
Read(xml);
|
||||
}
|
||||
|
||||
public void Read(Stream stream)
|
||||
{
|
||||
var xml = XDocument.Load(stream);
|
||||
Read(xml);
|
||||
}
|
||||
|
||||
private void Read(XDocument doc)
|
||||
{
|
||||
var data = doc.Root.Element("PressBrakeProgram");
|
||||
|
||||
Program.Version = data.Attribute("Version").ToInt();
|
||||
Program.ProgName = data.Attribute("ProgName")?.Value;
|
||||
Program.MatThick = data.Attribute("MatThick").ToDouble();
|
||||
Program.MatType = GetMaterialType(data.Attribute("MatType")?.Value);
|
||||
Program.KFactor = data.Attribute("KFactor").ToDouble();
|
||||
Program.TeachName = data.Attribute("TeachName")?.Value;
|
||||
Program.PartName = data.Attribute("PartName")?.Value;
|
||||
Program.SetupNotes = data.Attribute("SetupNotes")?.Value;
|
||||
Program.ProgNotes = data.Attribute("ProgNotes")?.Value;
|
||||
Program.RZEnabled = Convert.ToBoolean(data.Attribute("RZEnabled").ToInt());
|
||||
|
||||
foreach (var item in data.Element("UpperToolSets").Descendants("ToolSetup"))
|
||||
{
|
||||
var setup = ReadToolSetup(item);
|
||||
Program.UpperToolSets.Add(setup);
|
||||
}
|
||||
|
||||
foreach (var item in data.Element("LowerToolSets").Descendants("ToolSetup"))
|
||||
{
|
||||
var setup = ReadToolSetup(item);
|
||||
Program.LowerToolSets.Add(setup);
|
||||
}
|
||||
|
||||
foreach (var item in data.Element("StepData").Descendants("Step"))
|
||||
{
|
||||
var step = ReadStep(item);
|
||||
step.UpperTool = Program.UpperToolSets.FirstOrDefault(t => t.Id == step.UpperID);
|
||||
step.LowerTool = Program.LowerToolSets.FirstOrDefault(t => t.Id == step.LowerID);
|
||||
|
||||
Program.Steps.Add(step);
|
||||
}
|
||||
}
|
||||
|
||||
private ToolSetup ReadToolSetup(XElement x)
|
||||
{
|
||||
var setup = new ToolSetup();
|
||||
|
||||
setup.Name = x.Attribute("Name").Value;
|
||||
setup.Id = x.Attribute("ID").ToInt();
|
||||
setup.Length = x.Attribute("Length").ToDouble();
|
||||
setup.StackedHolderType = x.Attribute("StackedHolderType").ToInt();
|
||||
setup.HolderHeight = x.Attribute("HolderHeight").ToDouble();
|
||||
|
||||
foreach (var item in x.Descendants("SegEntry"))
|
||||
{
|
||||
var entry = new SegEntry();
|
||||
entry.SegValue = item.Attribute("SegValue").ToDouble();
|
||||
setup.Segments.Add(entry);
|
||||
}
|
||||
|
||||
return setup;
|
||||
}
|
||||
|
||||
private Step ReadStep(XElement x)
|
||||
{
|
||||
var step = new Step();
|
||||
|
||||
step.RevMode = x.Attribute("RevMode").ToInt();
|
||||
step.RevTons = x.Attribute("RevTons").ToDouble();
|
||||
step.MaxTons = x.Attribute("MaxTons").ToDouble();
|
||||
step.RevAbsPos = x.Attribute("RevAbsPos").ToDouble();
|
||||
step.ActualAng = x.Attribute("ActualAng").ToDouble();
|
||||
step.AngleAdj = x.Attribute("AngleAdj").ToDouble();
|
||||
step.BendLen = x.Attribute("BendLen").ToDouble();
|
||||
step.StrokeLen = x.Attribute("StrokeLen").ToDouble();
|
||||
step.UpperID = x.Attribute("UpperID").ToInt();
|
||||
step.LowerID = x.Attribute("LowerID").ToInt();
|
||||
step.SpdChgDwn = x.Attribute("SpdChgDwn").ToDouble();
|
||||
step.SpdChgUp = x.Attribute("SpdChgUp").ToDouble();
|
||||
step.Tilt = x.Attribute("Tilt").ToDouble();
|
||||
step.FormSpeed = x.Attribute("FormSpeed").ToDouble();
|
||||
step.XLeft = x.Attribute("XLeft").ToDouble();
|
||||
step.XRight = x.Attribute("XRight").ToDouble();
|
||||
step.RLeft = x.Attribute("RLeft").ToDouble();
|
||||
step.RRight = x.Attribute("RRight").ToDouble();
|
||||
step.ZLeft = x.Attribute("ZLeft").ToDouble();
|
||||
step.ZRight = x.Attribute("ZRight").ToDouble();
|
||||
step.FLeft = x.Attribute("FLeft").ToDouble();
|
||||
step.FRight = x.Attribute("FRight").ToDouble();
|
||||
step.SSLeft = x.Attribute("SSLeft").ToDouble();
|
||||
step.SSRight = x.Attribute("SSRight").ToDouble();
|
||||
step.ReturnSpd = x.Attribute("ReturnSpd").ToDouble();
|
||||
step.SideFlgHeight = x.Attribute("SideFlgHeight").ToDouble();
|
||||
|
||||
return step;
|
||||
}
|
||||
|
||||
private MatType GetMaterialType(string value)
|
||||
{
|
||||
if (value == null)
|
||||
return MatType.MildSteel;
|
||||
|
||||
int i;
|
||||
|
||||
if (!int.TryParse(value, out i))
|
||||
return MatType.MildSteel;
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
return MatType.MildSteel;
|
||||
case 1:
|
||||
return MatType.HighStrengthSteel;
|
||||
case 2:
|
||||
return MatType.Stainless;
|
||||
case 3:
|
||||
return MatType.SoftAluminum;
|
||||
case 4:
|
||||
return MatType.HardAluminum;
|
||||
}
|
||||
|
||||
return MatType.MildSteel;
|
||||
}
|
||||
}
|
||||
}
|
||||
7
FabWorks.Core/PressBrake/SegEntry.cs
Normal file
7
FabWorks.Core/PressBrake/SegEntry.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace FabWorks.Core.PressBrake
|
||||
{
|
||||
public class SegEntry
|
||||
{
|
||||
public double SegValue { get; set; }
|
||||
}
|
||||
}
|
||||
36
FabWorks.Core/PressBrake/Step.cs
Normal file
36
FabWorks.Core/PressBrake/Step.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace FabWorks.Core.PressBrake
|
||||
{
|
||||
public class Step
|
||||
{
|
||||
public int RevMode { get; set; }
|
||||
public double RevTons { get; set; }
|
||||
public double MaxTons { get; set; }
|
||||
public double RevAbsPos { get; set; }
|
||||
public double ActualAng { get; set; }
|
||||
public double AngleAdj { get; set; }
|
||||
public double BendLen { get; set; }
|
||||
public double StrokeLen { get; set; }
|
||||
public double Tilt { get; set; }
|
||||
public int UpperID { get; set; }
|
||||
public int LowerID { get; set; }
|
||||
public double SpdChgDwn { get; set; }
|
||||
public double SpdChgUp { get; set; }
|
||||
public double FormSpeed { get; set; }
|
||||
public double XLeft { get; set; }
|
||||
public double XRight { get; set; }
|
||||
public double RLeft { get; set; }
|
||||
public double RRight { get; set; }
|
||||
public double ZLeft { get; set; }
|
||||
public double ZRight { get; set; }
|
||||
public double FLeft { get; set; }
|
||||
public double FRight { get; set; }
|
||||
|
||||
public double SSLeft { get; set; }
|
||||
public double SSRight { get; set; }
|
||||
public double ReturnSpd { get; set; }
|
||||
public double SideFlgHeight { get; set; }
|
||||
|
||||
public ToolSetup UpperTool { get; set; }
|
||||
public ToolSetup LowerTool { get; set; }
|
||||
}
|
||||
}
|
||||
24
FabWorks.Core/PressBrake/ToolSetup.cs
Normal file
24
FabWorks.Core/PressBrake/ToolSetup.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FabWorks.Core.PressBrake
|
||||
{
|
||||
public class ToolSetup
|
||||
{
|
||||
public ToolSetup()
|
||||
{
|
||||
Segments = new List<SegEntry>();
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public int Id { get; set; }
|
||||
|
||||
public double Length { get; set; }
|
||||
|
||||
public int StackedHolderType { get; set; }
|
||||
|
||||
public double HolderHeight { get; set; }
|
||||
|
||||
public List<SegEntry> Segments { get; set; }
|
||||
}
|
||||
}
|
||||
32
FabWorks.Tests/FabWorks.Tests.csproj
Normal file
32
FabWorks.Tests/FabWorks.Tests.csproj
Normal file
@@ -0,0 +1,32 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.5.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FabWorks.Core\FabWorks.Core.csproj" />
|
||||
<ProjectReference Include="..\FabWorks.Api\FabWorks.Api.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
53
FabWorks.Tests/FormProgramServiceTests.cs
Normal file
53
FabWorks.Tests/FormProgramServiceTests.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using FabWorks.Api.Services;
|
||||
using Xunit;
|
||||
|
||||
namespace FabWorks.Tests
|
||||
{
|
||||
public class FormProgramServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void ParseFromFile_SamplePgm_PopulatesMaterialType()
|
||||
{
|
||||
var service = new FormProgramService();
|
||||
var fp = service.ParseFromFile("TestData/sample.pgm");
|
||||
|
||||
// ProgName is empty in the sample file, so verify MaterialType instead
|
||||
Assert.False(string.IsNullOrEmpty(fp.MaterialType));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseFromFile_SamplePgm_PopulatesThickness()
|
||||
{
|
||||
var service = new FormProgramService();
|
||||
var fp = service.ParseFromFile("TestData/sample.pgm");
|
||||
Assert.NotNull(fp.Thickness);
|
||||
Assert.True(fp.Thickness > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseFromFile_SamplePgm_PopulatesBendCount()
|
||||
{
|
||||
var service = new FormProgramService();
|
||||
var fp = service.ParseFromFile("TestData/sample.pgm");
|
||||
Assert.True(fp.BendCount > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseFromFile_SamplePgm_PopulatesToolNames()
|
||||
{
|
||||
var service = new FormProgramService();
|
||||
var fp = service.ParseFromFile("TestData/sample.pgm");
|
||||
Assert.False(string.IsNullOrEmpty(fp.UpperToolNames));
|
||||
Assert.False(string.IsNullOrEmpty(fp.LowerToolNames));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseFromFile_SamplePgm_ComputesContentHash()
|
||||
{
|
||||
var service = new FormProgramService();
|
||||
var fp = service.ParseFromFile("TestData/sample.pgm");
|
||||
Assert.NotNull(fp.ContentHash);
|
||||
Assert.Equal(64, fp.ContentHash.Length); // SHA256 hex = 64 chars
|
||||
}
|
||||
}
|
||||
}
|
||||
48
FabWorks.Tests/PressBrake/ProgramReaderTests.cs
Normal file
48
FabWorks.Tests/PressBrake/ProgramReaderTests.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using FabWorks.Core.PressBrake;
|
||||
using Xunit;
|
||||
|
||||
namespace FabWorks.Tests.PressBrake
|
||||
{
|
||||
public class ProgramReaderTests
|
||||
{
|
||||
[Fact]
|
||||
public void Load_SamplePgm_ParsesProgramAttributes()
|
||||
{
|
||||
var pgm = Program.Load("TestData/sample.pgm");
|
||||
|
||||
// ProgName may be empty on some exports; verify PartName was parsed instead
|
||||
Assert.False(string.IsNullOrEmpty(pgm.PartName));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_SamplePgm_ParsesThickness()
|
||||
{
|
||||
var pgm = Program.Load("TestData/sample.pgm");
|
||||
Assert.True(pgm.MatThick > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_SamplePgm_ParsesSteps()
|
||||
{
|
||||
var pgm = Program.Load("TestData/sample.pgm");
|
||||
Assert.NotEmpty(pgm.Steps);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_SamplePgm_ParsesToolSetups()
|
||||
{
|
||||
var pgm = Program.Load("TestData/sample.pgm");
|
||||
Assert.NotEmpty(pgm.UpperToolSets);
|
||||
Assert.NotEmpty(pgm.LowerToolSets);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_SamplePgm_ResolvesStepToolReferences()
|
||||
{
|
||||
var pgm = Program.Load("TestData/sample.pgm");
|
||||
var step = pgm.Steps[0];
|
||||
Assert.NotNull(step.UpperTool);
|
||||
Assert.NotNull(step.LowerTool);
|
||||
}
|
||||
}
|
||||
}
|
||||
593
FabWorks.Tests/TestData/sample.pgm
Normal file
593
FabWorks.Tests/TestData/sample.pgm
Normal file
@@ -0,0 +1,593 @@
|
||||
<?xml version="1.0" ?>
|
||||
<Document>
|
||||
<PressBrakeProgram Version="13" ProgName="" TeachName="" PartName="C:\Users\aj.REMCO\Desktop\4980 A05-1 PT02.part" SetupNotes="" ProgNotes="" MatThick="0.06" MatType="2" KFactor="0.42" RZEnabled="1" FEnabled="0" KFactorAuto="0" PartsBetween="0" DryRun="0" LeftFingerType="2" RightFingerType="2" HasPart="1" CBAngMode="1" CBVee="0.75" CBMute="20.752" CBClamp="20.492" CBDieAng="85" CBTopOfDie="20.442" ToolSelLock="0" NumSteps="8" GageType="6">
|
||||
<UpperToolSets>
|
||||
<ToolList Count="1">
|
||||
<ToolSetup Name="50210 with double riser" ID="1" Length="63" NumSegs="4">
|
||||
<SegStackup>
|
||||
<SegEntry SegValue="36"/>
|
||||
<SegEntry SegValue="18"/>
|
||||
<SegEntry SegValue="8"/>
|
||||
<SegEntry SegValue="1"/>
|
||||
</SegStackup>
|
||||
</ToolSetup>
|
||||
</ToolList>
|
||||
</UpperToolSets>
|
||||
<LowerToolSets>
|
||||
<ToolList Count="1">
|
||||
<ToolSetup Name="0.750 x 85 x 144" ID="1" Length="144"/>
|
||||
</ToolList>
|
||||
</LowerToolSets>
|
||||
<StepData>
|
||||
<Step RevMode="0" RevTons="17.5" MaxTons="9999" RevAbsPos="20.2004" ActualAng="90" BendLen="35.0859" StrokeLen="1.7399" UpperID="1" LowerID="1" SpdChgDwn="0.375" SpdChgUp="0.25" FormSpeed="20" CadStep="0" GageMode="0" GageAllowAuto="0" DimensionType="4" GagePause="0.1" XLeft="1.6729" XRight="1.6729" CBTopOfLDie="6.752" RLeft="0.05" RRight="0.05" ZLeft="-15.2429" ZRight="15.2429" FLeft="44" FRight="44" SSLeft="0" SSRight="0" ReturnSpd="20" GuardMute="0" GuardMode="0" SpecialToolMode="0" FingGagePt="1" AdaptRevPos="0" MuteOffset="0.25" CBTopStop="22.1819" CBAbsSpdChg="20.877" CBMute="20.752" EndStopDim="-17.5429" AutoAdjustment="5" SnapShotTaken="0" XValue="0" YValue="0" ZValue="0" Zoom="0" RotationHeight="0" RotationWidth="0" RotationDepth="0" ScalarValue="0" VectorXValue="0" VectorYValue="0" VectorZValue="0">
|
||||
<UIDSelectList Size="1">
|
||||
<UID IntValue="1"/>
|
||||
</UIDSelectList>
|
||||
<LIDSelectList Size="1">
|
||||
<LID IntValue="1"/>
|
||||
</LIDSelectList>
|
||||
<ProgOutputs>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
</ProgOutputs>
|
||||
</Step>
|
||||
<Step RevMode="0" RevTons="17.5" MaxTons="9999" RevAbsPos="20.2004" ActualAng="90" BendLen="35.7179" StrokeLen="8.8696" UpperID="1" LowerID="1" SpdChgDwn="0.375" SpdChgUp="0.25" FormSpeed="20" CadStep="0" GageMode="1" GageAllowAuto="0" DimensionType="4" GagePause="0.1" XLeft="8.4129" XRight="8.4129" CBTopOfLDie="6.752" RLeft="0.06" RRight="0.06" ZLeft="-16.7954" ZRight="0.3125" FLeft="44" FRight="44" SSLeft="0" SSRight="0" ReturnSpd="20" GuardMute="0" GuardMode="0" SpecialToolMode="0" FingGagePt="1" AdaptRevPos="0" MuteOffset="0.25" CBTopStop="24" CBAbsSpdChg="20.877" CBMute="20.752" EndStopDim="-17.859" AutoAdjustment="5" SnapShotTaken="0" XValue="0" YValue="0" ZValue="0" Zoom="0" RotationHeight="0" RotationWidth="0" RotationDepth="0" ScalarValue="0" VectorXValue="0" VectorYValue="0" VectorZValue="0">
|
||||
<UIDSelectList Size="1">
|
||||
<UID IntValue="1"/>
|
||||
</UIDSelectList>
|
||||
<LIDSelectList Size="1">
|
||||
<LID IntValue="1"/>
|
||||
</LIDSelectList>
|
||||
<ProgOutputs>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
</ProgOutputs>
|
||||
</Step>
|
||||
<Step RevMode="0" RevTons="17.5" MaxTons="9999" RevAbsPos="20.2004" ActualAng="90" BendLen="35.0859" StrokeLen="1.7399" UpperID="1" LowerID="1" SpdChgDwn="0.375" SpdChgUp="0.25" FormSpeed="20" CadStep="0" GageMode="0" GageAllowAuto="0" DimensionType="4" GagePause="0.1" XLeft="1.6729" XRight="1.6729" CBTopOfLDie="6.752" RLeft="0.05" RRight="0.05" ZLeft="-15.2429" ZRight="15.2429" FLeft="44" FRight="44" SSLeft="0" SSRight="0" ReturnSpd="20" GuardMute="0" GuardMode="0" SpecialToolMode="0" FingGagePt="1" AdaptRevPos="0" MuteOffset="0.25" SideFlgHeight="8.4199" CBTopStop="22.1819" CBAbsSpdChg="20.877" CBMute="20.752" EndStopDim="-17.5429" AutoAdjustment="5" SnapShotTaken="0" XValue="0" YValue="0" ZValue="0" Zoom="0" RotationHeight="0" RotationWidth="0" RotationDepth="0" ScalarValue="0" VectorXValue="0" VectorYValue="0" VectorZValue="0">
|
||||
<UIDSelectList Size="1">
|
||||
<UID IntValue="1"/>
|
||||
</UIDSelectList>
|
||||
<LIDSelectList Size="1">
|
||||
<LID IntValue="1"/>
|
||||
</LIDSelectList>
|
||||
<ProgOutputs>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
</ProgOutputs>
|
||||
</Step>
|
||||
<Step RevMode="0" RevTons="17.5" MaxTons="9999" RevAbsPos="20.2004" ActualAng="90" BendLen="35.7179" StrokeLen="8.8696" UpperID="1" LowerID="1" SpdChgDwn="0.375" SpdChgUp="0.25" FormSpeed="20" CadStep="0" GageMode="1" GageAllowAuto="0" DimensionType="4" GagePause="0.1" XLeft="8.4129" XRight="8.4129" CBTopOfLDie="6.752" RLeft="0.06" RRight="0.06" ZLeft="-0.3125" ZRight="16.7954" FLeft="44" FRight="44" SSLeft="0" SSRight="0" ReturnSpd="20" GuardMute="0" GuardMode="0" SpecialToolMode="0" FingGagePt="1" AdaptRevPos="0" MuteOffset="0.25" SideFlgHeight="8.4199" CBTopStop="24" CBAbsSpdChg="20.877" CBMute="20.752" EndStopDim="-17.859" AutoAdjustment="5" SnapShotTaken="0" XValue="0" YValue="0" ZValue="0" Zoom="0" RotationHeight="0" RotationWidth="0" RotationDepth="0" ScalarValue="0" VectorXValue="0" VectorYValue="0" VectorZValue="0">
|
||||
<UIDSelectList Size="1">
|
||||
<UID IntValue="1"/>
|
||||
</UIDSelectList>
|
||||
<LIDSelectList Size="1">
|
||||
<LID IntValue="1"/>
|
||||
</LIDSelectList>
|
||||
<ProgOutputs>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
</ProgOutputs>
|
||||
</Step>
|
||||
<Step RevMode="0" RevTons="17.5" MaxTons="9999" RevAbsPos="20.2004" ActualAng="90" BendLen="63.4609" StrokeLen="1.74" UpperID="1" LowerID="1" SpdChgDwn="0.375" SpdChgUp="0.25" FormSpeed="20" CadStep="0" GageMode="0" GageAllowAuto="0" DimensionType="4" GagePause="0.1" XLeft="1.6729" XRight="1.6729" CBTopOfLDie="6.752" RLeft="0.05" RRight="0.05" ZLeft="-29.1179" ZRight="29.1179" FLeft="44" FRight="44" SSLeft="0" SSRight="0" ReturnSpd="20" GuardMute="0" GuardMode="0" SpecialToolMode="0" FingGagePt="1" AdaptRevPos="0" MuteOffset="0.25" SideFlgHeight="8.4199" CBTopStop="22.182" CBAbsSpdChg="20.877" CBMute="20.752" EndStopDim="-31.7304" AutoAdjustment="5" SnapShotTaken="0" XValue="0" YValue="0" ZValue="0" Zoom="0" RotationHeight="0" RotationWidth="0" RotationDepth="0" ScalarValue="0" VectorXValue="0" VectorYValue="0" VectorZValue="0">
|
||||
<UIDSelectList Size="1">
|
||||
<UID IntValue="1"/>
|
||||
</UIDSelectList>
|
||||
<LIDSelectList Size="1">
|
||||
<LID IntValue="1"/>
|
||||
</LIDSelectList>
|
||||
<ProgOutputs>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
</ProgOutputs>
|
||||
</Step>
|
||||
<Step RevMode="0" RevTons="17.5" MaxTons="9999" RevAbsPos="20.2004" ActualAng="90" BendLen="63.4679" StrokeLen="8.8759" UpperID="1" LowerID="1" SpdChgDwn="0.375" SpdChgUp="0.25" FormSpeed="20" CadStep="0" GageMode="0" GageAllowAuto="0" DimensionType="4" GagePause="0.1" XLeft="8.4129" XRight="8.4129" CBTopOfLDie="6.752" RLeft="0.06" RRight="0.06" ZLeft="-30.6705" ZRight="30.6704" FLeft="44" FRight="44" SSLeft="0" SSRight="0" ReturnSpd="20" GuardMute="0" GuardMode="0" SpecialToolMode="0" FingGagePt="1" AdaptRevPos="0" MuteOffset="0.25" SideFlgHeight="8.4199" CBTopStop="24" CBAbsSpdChg="20.877" CBMute="20.752" EndStopDim="-31.734" AutoAdjustment="5" SnapShotTaken="0" XValue="0" YValue="0" ZValue="0" Zoom="0" RotationHeight="0" RotationWidth="0" RotationDepth="0" ScalarValue="0" VectorXValue="0" VectorYValue="0" VectorZValue="0">
|
||||
<UIDSelectList Size="1">
|
||||
<UID IntValue="1"/>
|
||||
</UIDSelectList>
|
||||
<LIDSelectList Size="1">
|
||||
<LID IntValue="1"/>
|
||||
</LIDSelectList>
|
||||
<ProgOutputs>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
</ProgOutputs>
|
||||
</Step>
|
||||
<Step RevMode="0" RevTons="17.5" MaxTons="9999" RevAbsPos="20.2004" ActualAng="90" BendLen="63.4609" StrokeLen="1.74" UpperID="1" LowerID="1" SpdChgDwn="0.375" SpdChgUp="0.25" FormSpeed="20" CadStep="0" GageMode="0" GageAllowAuto="0" DimensionType="4" GagePause="0.1" XLeft="1.6729" XRight="1.6729" CBTopOfLDie="6.752" RLeft="0.05" RRight="0.05" ZLeft="-29.1179" ZRight="29.1179" FLeft="44" FRight="44" SSLeft="0" SSRight="0" ReturnSpd="20" GuardMute="0" GuardMode="0" SpecialToolMode="0" FingGagePt="1" AdaptRevPos="0" MuteOffset="0.25" SideFlgHeight="8.4199" CBTopStop="22.182" CBAbsSpdChg="20.877" CBMute="20.752" EndStopDim="-31.7304" AutoAdjustment="5" SnapShotTaken="0" XValue="0" YValue="0" ZValue="0" Zoom="0" RotationHeight="0" RotationWidth="0" RotationDepth="0" ScalarValue="0" VectorXValue="0" VectorYValue="0" VectorZValue="0">
|
||||
<UIDSelectList Size="1">
|
||||
<UID IntValue="1"/>
|
||||
</UIDSelectList>
|
||||
<LIDSelectList Size="1">
|
||||
<LID IntValue="1"/>
|
||||
</LIDSelectList>
|
||||
<ProgOutputs>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
</ProgOutputs>
|
||||
</Step>
|
||||
<Step RevMode="0" RevTons="17.5" MaxTons="9999" RevAbsPos="20.2004" ActualAng="90" BendLen="63.4679" StrokeLen="8.8759" UpperID="1" LowerID="1" SpdChgDwn="0.375" SpdChgUp="0.25" FormSpeed="20" CadStep="0" GageMode="0" GageAllowAuto="0" DimensionType="4" GagePause="0.1" XLeft="8.4129" XRight="8.4129" CBTopOfLDie="6.752" RLeft="0.06" RRight="0.06" ZLeft="-30.6704" ZRight="30.6704" FLeft="44" FRight="44" SSLeft="0" SSRight="0" ReturnSpd="20" GuardMute="0" GuardMode="0" SpecialToolMode="0" FingGagePt="1" AdaptRevPos="0" MuteOffset="0.25" SideFlgHeight="8.4199" CBTopStop="24" CBAbsSpdChg="20.877" CBMute="20.752" EndStopDim="-31.734" AutoAdjustment="5" SnapShotTaken="0" XValue="0" YValue="0" ZValue="0" Zoom="0" RotationHeight="0" RotationWidth="0" RotationDepth="0" ScalarValue="0" VectorXValue="0" VectorYValue="0" VectorZValue="0">
|
||||
<UIDSelectList Size="1">
|
||||
<UID IntValue="1"/>
|
||||
</UIDSelectList>
|
||||
<LIDSelectList Size="1">
|
||||
<LID IntValue="1"/>
|
||||
</LIDSelectList>
|
||||
<ProgOutputs>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
<ProgOutData/>
|
||||
</ProgOutputs>
|
||||
</Step>
|
||||
</StepData>
|
||||
</PressBrakeProgram>
|
||||
<PressBrakePart Version="2" FlangeCount="9" MatType="2" KFactor="0.42" KFactorAuto="0" FingerSelPref="3">
|
||||
<Flanges>
|
||||
<Flange Version="5" FlangeID="1" FlangeDim="63.3409" FlangeWidth="1.554956" Editable="0" BendCount="1" TopListCount="1" FlangeWidthDisp="0" FlangeSource="1" OrgSegListCount="0" AdjustValue="0" IntBend="0">
|
||||
<Quaternion Version="1" SVal="1">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="0"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="1">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="0"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="1">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="0"/>
|
||||
</Quaternion>
|
||||
<BendList>
|
||||
<Bend Version="9" BendLength="63.460859" BendRadius="0.125" BendSeq="5" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="1" EndFlangeID="2" Rotate180="1" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="5" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="0.117968" YVal="31.73043" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="-0">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="1"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="0.000001" YVal="31.73043" ZVal="0"/>
|
||||
</Bend>
|
||||
</BendList>
|
||||
<TopList>
|
||||
<Feature3D Version="6" SegCount="6" OrgSegListCount="6">
|
||||
<SegmentList>
|
||||
<Segment3D XVal="0.117972" YVal="0.059985" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.122918" YVal="0.0625" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="1.672918" YVal="1.6125" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="1.672918" YVal="61.848358" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.122918" YVal="63.398358" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.117961" YVal="63.400878" ZVal="0.03" ZCent="0.03"/>
|
||||
</SegmentList>
|
||||
<OrgSegList>
|
||||
<Segment3D XVal="0.000003" YVal="0" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122918" YVal="0.0625" ZVal="0.03"/>
|
||||
<Segment3D XVal="1.672918" YVal="1.6125" ZVal="0.03"/>
|
||||
<Segment3D XVal="1.672918" YVal="61.848358" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122918" YVal="63.398358" ZVal="0.03"/>
|
||||
<Segment3D XVal="0" YVal="63.460859" ZVal="0.03"/>
|
||||
</OrgSegList>
|
||||
</Feature3D>
|
||||
</TopList>
|
||||
</Flange>
|
||||
<Flange Version="5" FlangeID="2" FlangeDim="63.5859" FlangeWidth="8.1099" Editable="0" BendCount="1" TopListCount="1" FlangeWidthDisp="0" FlangeSource="1" OrgSegListCount="0" AdjustValue="0.117967" IntBend="0">
|
||||
<Quaternion Version="1" SVal="-0">
|
||||
<Segment3D XVal="0.707107" YVal="0" ZVal="0.707107"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="-0">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="1"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="-0">
|
||||
<Segment3D XVal="0.707107" YVal="0" ZVal="0.707107"/>
|
||||
</Quaternion>
|
||||
<BendList>
|
||||
<Bend Version="9" BendLength="63.467927" BendRadius="0.125" BendSeq="6" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="2" EndFlangeID="3" Rotate180="1" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="6" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="8.109896" YVal="0" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="1">
|
||||
<Segment3D XVal="-0" YVal="-0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="8.34583" YVal="0" ZVal="0"/>
|
||||
</Bend>
|
||||
</BendList>
|
||||
<TopList>
|
||||
<Feature3D Version="6" SegCount="8" OrgSegListCount="8">
|
||||
<SegmentList>
|
||||
<Segment3D XVal="-0.000001" YVal="-31.790412" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004948" YVal="-31.792928" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104948" YVal="-31.792928" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109896" YVal="-31.790554" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109899" YVal="31.790554" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104945" YVal="31.79293" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004945" YVal="31.79293" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="31.790414" ZVal="0.03" ZCent="0.03"/>
|
||||
</SegmentList>
|
||||
<OrgSegList>
|
||||
<Segment3D XVal="0.000003" YVal="-31.73043" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122915" YVal="-31.792928" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222915" YVal="-31.792928" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.345832" YVal="-31.733962" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.345827" YVal="31.733966" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222912" YVal="31.79293" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122912" YVal="31.79293" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0.000003" YVal="31.73043" ZVal="0.03"/>
|
||||
</OrgSegList>
|
||||
</Feature3D>
|
||||
</TopList>
|
||||
</Flange>
|
||||
<Flange Version="5" FlangeID="3" FlangeDim="63.3599" FlangeWidth="35.609897" Editable="0" BendCount="3" TopListCount="1" FlangeWidthDisp="0" FlangeSource="1" OrgSegListCount="0" AdjustValue="0.117967" IntBend="0">
|
||||
<Quaternion Version="1" SVal="0">
|
||||
<Segment3D XVal="1" YVal="0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="-0">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="1"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="0">
|
||||
<Segment3D XVal="1" YVal="0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<BendList>
|
||||
<Bend Version="9" BendLength="63.467928" BendRadius="0.125" BendSeq="8" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="3" EndFlangeID="4" Rotate180="0" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="8" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="35.609893" YVal="-0.000003" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="1">
|
||||
<Segment3D XVal="-0" YVal="-0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="35.845827" YVal="-0.000003" ZVal="0"/>
|
||||
</Bend>
|
||||
<Bend Version="9" BendLength="35.717928" BendRadius="0.125" BendSeq="2" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="3" EndFlangeID="5" Rotate180="0" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="2" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="17.804945" YVal="-31.679949" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="-0.707107">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="0.707107"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="17.922911" YVal="-31.797916" ZVal="0"/>
|
||||
</Bend>
|
||||
<Bend Version="9" BendLength="35.717928" BendRadius="0.125" BendSeq="4" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="3" EndFlangeID="6" Rotate180="0" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="4" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="17.804948" YVal="31.679946" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="-0.707107">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="-0.707107"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="17.922915" YVal="31.797913" ZVal="0"/>
|
||||
</Bend>
|
||||
</BendList>
|
||||
<TopList>
|
||||
<Feature3D Version="6" SegCount="12" OrgSegListCount="12">
|
||||
<SegmentList>
|
||||
<Segment3D XVal="-0.000001" YVal="-31.677374" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004945" YVal="-31.675001" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.00257" YVal="-31.679952" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="35.607317" YVal="-31.679947" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="35.604945" YVal="-31.675002" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="35.609896" YVal="-31.677377" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="35.609893" YVal="31.67737" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="35.604948" YVal="31.674998" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="35.607323" YVal="31.679949" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.002576" YVal="31.679944" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004948" YVal="31.674999" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="31.677374" ZVal="0.03" ZCent="0.03"/>
|
||||
</SegmentList>
|
||||
<OrgSegList>
|
||||
<Segment3D XVal="0" YVal="-31.733964" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122912" YVal="-31.675001" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.063947" YVal="-31.797916" ZVal="0.03"/>
|
||||
<Segment3D XVal="35.781875" YVal="-31.797916" ZVal="0.03"/>
|
||||
<Segment3D XVal="35.722912" YVal="-31.675002" ZVal="0.03"/>
|
||||
<Segment3D XVal="35.845827" YVal="-31.733967" ZVal="0.03"/>
|
||||
<Segment3D XVal="35.845827" YVal="31.733961" ZVal="0.03"/>
|
||||
<Segment3D XVal="35.722915" YVal="31.674998" ZVal="0.03"/>
|
||||
<Segment3D XVal="35.781879" YVal="31.797912" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.063951" YVal="31.797913" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122915" YVal="31.674999" ZVal="0.03"/>
|
||||
<Segment3D XVal="0" YVal="31.733964" ZVal="0.03"/>
|
||||
</OrgSegList>
|
||||
</Feature3D>
|
||||
</TopList>
|
||||
</Flange>
|
||||
<Flange Version="5" FlangeID="4" FlangeDim="63.5859" FlangeWidth="8.109903" Editable="0" BendCount="1" TopListCount="1" FlangeWidthDisp="0" FlangeSource="1" OrgSegListCount="0" AdjustValue="0.117967" IntBend="0">
|
||||
<Quaternion Version="1" SVal="0">
|
||||
<Segment3D XVal="0.707107" YVal="0" ZVal="-0.707107"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="-0">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="1"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="0">
|
||||
<Segment3D XVal="1" YVal="0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<BendList>
|
||||
<Bend Version="9" BendLength="63.460859" BendRadius="0.125" BendSeq="7" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="4" EndFlangeID="7" Rotate180="0" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="7" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="8.109896" YVal="0.000001" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="-1">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="8.34583" YVal="0.000001" ZVal="0"/>
|
||||
</Bend>
|
||||
</BendList>
|
||||
<TopList>
|
||||
<Feature3D Version="6" SegCount="8" OrgSegListCount="8">
|
||||
<SegmentList>
|
||||
<Segment3D XVal="-0.000001" YVal="-31.790554" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004948" YVal="-31.792928" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104948" YVal="-31.792929" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109902" YVal="-31.79041" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109893" YVal="31.790416" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104951" YVal="31.792929" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004951" YVal="31.79293" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="31.790554" ZVal="0.03" ZCent="0.03"/>
|
||||
</SegmentList>
|
||||
<OrgSegList>
|
||||
<Segment3D XVal="0" YVal="-31.733964" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122915" YVal="-31.792928" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222915" YVal="-31.792929" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.34583" YVal="-31.730429" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.34583" YVal="31.730431" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222918" YVal="31.792929" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122918" YVal="31.79293" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0" YVal="31.733964" ZVal="0.03"/>
|
||||
</OrgSegList>
|
||||
</Feature3D>
|
||||
</TopList>
|
||||
</Flange>
|
||||
<Flange Version="5" FlangeID="5" FlangeDim="35.8359" FlangeWidth="8.109898" Editable="0" BendCount="2" TopListCount="1" FlangeWidthDisp="0" FlangeSource="1" OrgSegListCount="0" AdjustValue="0.117967" IntBend="0">
|
||||
<Quaternion Version="1" SVal="-0.5">
|
||||
<Segment3D XVal="-0.5" YVal="-0.5" ZVal="0.5"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="-0.707107">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="-0.707107"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="-0.5">
|
||||
<Segment3D XVal="-0.5" YVal="-0.5" ZVal="0.5"/>
|
||||
</Quaternion>
|
||||
<BendList>
|
||||
<Bend Version="9" BendLength="19.16793" BendRadius="0.125" BendSeq="1" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="5" EndFlangeID="8" Rotate180="0" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="1" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="8.109897" YVal="8.271464" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="1">
|
||||
<Segment3D XVal="-0" YVal="-0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="8.345831" YVal="8.271464" ZVal="0"/>
|
||||
</Bend>
|
||||
<Bend Version="9" BendLength="15.917929" BendRadius="0.125" BendSeq="1" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="5" EndFlangeID="8" Rotate180="0" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="1" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="8.109895" YVal="-9.896465" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="1">
|
||||
<Segment3D XVal="-0" YVal="-0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="8.345829" YVal="-9.896465" ZVal="0"/>
|
||||
</Bend>
|
||||
</BendList>
|
||||
<TopList>
|
||||
<Feature3D Version="6" SegCount="12" OrgSegListCount="12">
|
||||
<SegmentList>
|
||||
<Segment3D XVal="-0.000001" YVal="-17.915554" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.00495" YVal="-17.917929" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.10495" YVal="-17.917928" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109895" YVal="-17.915413" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109896" YVal="-1.937499" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104949" YVal="-1.937499" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104949" YVal="-1.312499" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109898" YVal="-1.312499" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109898" YVal="17.915413" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104948" YVal="17.91793" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004948" YVal="17.917929" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="17.915555" ZVal="0.03" ZCent="0.03"/>
|
||||
</SegmentList>
|
||||
<OrgSegList>
|
||||
<Segment3D XVal="0.000002" YVal="-17.858964" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122916" YVal="-17.917929" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222916" YVal="-17.917928" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.345831" YVal="-17.855428" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.34583" YVal="-1.937499" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222916" YVal="-1.937499" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222916" YVal="-1.312499" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.345831" YVal="-1.312499" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.345828" YVal="17.85543" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222915" YVal="17.91793" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122915" YVal="17.917929" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0.000002" YVal="17.858964" ZVal="0.03"/>
|
||||
</OrgSegList>
|
||||
</Feature3D>
|
||||
</TopList>
|
||||
</Flange>
|
||||
<Flange Version="5" FlangeID="6" FlangeDim="35.8359" FlangeWidth="8.109899" Editable="0" BendCount="2" TopListCount="1" FlangeWidthDisp="0" FlangeSource="1" OrgSegListCount="0" AdjustValue="0.117967" IntBend="0">
|
||||
<Quaternion Version="1" SVal="0.5">
|
||||
<Segment3D XVal="-0.5" YVal="0.5" ZVal="0.5"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="0.707107">
|
||||
<Segment3D XVal="-0" YVal="0" ZVal="-0.707107"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="0.5">
|
||||
<Segment3D XVal="-0.5" YVal="0.5" ZVal="0.5"/>
|
||||
</Quaternion>
|
||||
<BendList>
|
||||
<Bend Version="9" BendLength="19.167929" BendRadius="0.125" BendSeq="3" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="6" EndFlangeID="9" Rotate180="0" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="3" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="8.109896" YVal="-8.271465" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="1">
|
||||
<Segment3D XVal="-0" YVal="-0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="8.345829" YVal="-8.271465" ZVal="0"/>
|
||||
</Bend>
|
||||
<Bend Version="9" BendLength="15.917929" BendRadius="0.125" BendSeq="3" BendAllow="0.2359" StartDimOffset="0" EndDimOffset="0" StartFlangeID="6" EndFlangeID="9" Rotate180="0" BendCenter="0" UpperToolID="1" LowerToolID="1" DesiredBendSeq="3" FingerSelect="1" FingerGagePoint="1" IntBend="0" FingerGagePointRight="1">
|
||||
<Segment3D XVal="8.109897" YVal="9.896464" ZVal="0"/>
|
||||
<Quaternion Version="1" SVal="1">
|
||||
<Segment3D XVal="-0" YVal="-0" ZVal="-0"/>
|
||||
</Quaternion>
|
||||
<Segment3D XVal="8.345831" YVal="9.896464" ZVal="0"/>
|
||||
</Bend>
|
||||
</BendList>
|
||||
<TopList>
|
||||
<Feature3D Version="6" SegCount="12" OrgSegListCount="12">
|
||||
<SegmentList>
|
||||
<Segment3D XVal="-0.000001" YVal="-17.915554" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.00495" YVal="-17.917929" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.10495" YVal="-17.917928" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109895" YVal="-17.915413" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109897" YVal="1.312501" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104949" YVal="1.312501" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104949" YVal="1.937501" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109898" YVal="1.937501" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.109898" YVal="17.915413" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="8.104948" YVal="17.91793" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004948" YVal="17.917929" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="17.915555" ZVal="0.03" ZCent="0.03"/>
|
||||
</SegmentList>
|
||||
<OrgSegList>
|
||||
<Segment3D XVal="0.000002" YVal="-17.858964" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122916" YVal="-17.917929" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222916" YVal="-17.917928" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.345831" YVal="-17.855428" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.34583" YVal="1.312501" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222915" YVal="1.312501" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222915" YVal="1.937501" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.34583" YVal="1.937501" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.345828" YVal="17.85543" ZVal="0.03"/>
|
||||
<Segment3D XVal="8.222915" YVal="17.91793" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122915" YVal="17.917929" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0.000002" YVal="17.858964" ZVal="0.03"/>
|
||||
</OrgSegList>
|
||||
</Feature3D>
|
||||
</TopList>
|
||||
</Flange>
|
||||
<Flange Version="5" FlangeID="7" FlangeDim="63.3409" FlangeWidth="1.554952" Editable="0" BendCount="0" TopListCount="1" FlangeWidthDisp="0" FlangeSource="1" OrgSegListCount="0" AdjustValue="0.117967" IntBend="0">
|
||||
<Quaternion Version="1" SVal="-0">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="1"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="0">
|
||||
<Segment3D XVal="-0" YVal="0" ZVal="-1"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="-0">
|
||||
<Segment3D XVal="-1" YVal="-0" ZVal="0"/>
|
||||
</Quaternion>
|
||||
<TopList>
|
||||
<Feature3D Version="6" SegCount="6" OrgSegListCount="6">
|
||||
<SegmentList>
|
||||
<Segment3D XVal="-0.000001" YVal="-31.670448" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004951" YVal="-31.66793" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="1.554951" YVal="-30.11793" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="1.554948" YVal="30.117928" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004948" YVal="31.667928" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="31.670445" ZVal="0.03" ZCent="0.03"/>
|
||||
</SegmentList>
|
||||
<OrgSegList>
|
||||
<Segment3D XVal="0.000003" YVal="-31.73043" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122918" YVal="-31.66793" ZVal="0.03"/>
|
||||
<Segment3D XVal="1.672918" YVal="-30.11793" ZVal="0.03"/>
|
||||
<Segment3D XVal="1.672915" YVal="30.117928" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122915" YVal="31.667928" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0.000003" YVal="31.73043" ZVal="0.03"/>
|
||||
</OrgSegList>
|
||||
</Feature3D>
|
||||
</TopList>
|
||||
</Flange>
|
||||
<Flange Version="5" FlangeID="8" FlangeDim="35.5909" FlangeWidth="1.554951" Editable="0" BendCount="0" TopListCount="1" FlangeWidthDisp="0" FlangeSource="1" OrgSegListCount="0" AdjustValue="0.117967" IntBend="0">
|
||||
<Quaternion Version="1" SVal="-0.707107">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="0.707107"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="-0.707107">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="-0.707107"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="-0.707107">
|
||||
<Segment3D XVal="0" YVal="0" ZVal="0.707107"/>
|
||||
</Quaternion>
|
||||
<TopList>
|
||||
<Feature3D Version="6" SegCount="10" OrgSegListCount="10">
|
||||
<SegmentList>
|
||||
<Segment3D XVal="-0" YVal="-9.583965" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.554948" YVal="-9.583965" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.554948" YVal="-10.208965" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0" YVal="-10.208965" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="-26.06691" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004947" YVal="-26.064394" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="1.554947" YVal="-24.514394" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="1.554949" YVal="7.971464" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.00495" YVal="9.521464" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0" YVal="9.523981" ZVal="0.03" ZCent="0.03"/>
|
||||
</SegmentList>
|
||||
<OrgSegList>
|
||||
<Segment3D XVal="0" YVal="-9.583965" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.672915" YVal="-9.583965" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.672915" YVal="-10.208965" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0" YVal="-10.208965" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="-26.126894" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122913" YVal="-26.064394" ZVal="0.03"/>
|
||||
<Segment3D XVal="1.672914" YVal="-24.514394" ZVal="0.03"/>
|
||||
<Segment3D XVal="1.672916" YVal="7.971464" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122916" YVal="9.521464" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0" YVal="9.583965" ZVal="0.03"/>
|
||||
</OrgSegList>
|
||||
</Feature3D>
|
||||
</TopList>
|
||||
</Flange>
|
||||
<Flange Version="5" FlangeID="9" FlangeDim="35.5909" FlangeWidth="1.554949" Editable="0" BendCount="0" TopListCount="1" FlangeWidthDisp="0" FlangeSource="1" OrgSegListCount="0" AdjustValue="0.117967" IntBend="0">
|
||||
<Quaternion Version="1" SVal="0.707107">
|
||||
<Segment3D XVal="-0" YVal="-0" ZVal="0.707107"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="0.707107">
|
||||
<Segment3D XVal="-0" YVal="0" ZVal="-0.707107"/>
|
||||
</Quaternion>
|
||||
<Quaternion Version="1" SVal="0.707107">
|
||||
<Segment3D XVal="-0" YVal="-0" ZVal="0.707107"/>
|
||||
</Quaternion>
|
||||
<TopList>
|
||||
<Feature3D Version="6" SegCount="10" OrgSegListCount="10">
|
||||
<SegmentList>
|
||||
<Segment3D XVal="-0" YVal="-9.523981" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004948" YVal="-9.521464" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="1.554948" YVal="-7.971464" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="1.554948" YVal="24.514393" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.004948" YVal="26.064393" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="26.06691" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0" YVal="10.208964" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.554948" YVal="10.208964" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="0.554948" YVal="9.583964" ZVal="0.03" ZCent="0.03"/>
|
||||
<Segment3D XVal="-0" YVal="9.583964" ZVal="0.03" ZCent="0.03"/>
|
||||
</SegmentList>
|
||||
<OrgSegList>
|
||||
<Segment3D XVal="-0" YVal="-9.583964" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122915" YVal="-9.521464" ZVal="0.03"/>
|
||||
<Segment3D XVal="1.672915" YVal="-7.971464" ZVal="0.03"/>
|
||||
<Segment3D XVal="1.672915" YVal="24.514393" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.122915" YVal="26.064393" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0.000001" YVal="26.126894" ZVal="0.03"/>
|
||||
<Segment3D XVal="0" YVal="10.208964" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.672915" YVal="10.208964" ZVal="0.03"/>
|
||||
<Segment3D XVal="0.672915" YVal="9.583964" ZVal="0.03"/>
|
||||
<Segment3D XVal="-0" YVal="9.583964" ZVal="0.03"/>
|
||||
</OrgSegList>
|
||||
</Feature3D>
|
||||
</TopList>
|
||||
</Flange>
|
||||
</Flanges>
|
||||
</PressBrakePart>
|
||||
</Document>
|
||||
89
README.md
Normal file
89
README.md
Normal file
@@ -0,0 +1,89 @@
|
||||
# ExportDXF
|
||||
|
||||
A Windows desktop application that automates exporting flat pattern DXF files from SolidWorks drawings, assemblies, and parts. Built for sheet metal fabrication workflows, it extracts BOM data, generates DXF flat patterns with etch/bend line markings, and exports drawing PDFs to a local output folder with SQL Server tracking.
|
||||
|
||||
## Features
|
||||
|
||||
- **Batch DXF export** from SolidWorks drawings, assemblies, or individual parts
|
||||
- **Flat pattern generation** with automatic sheet metal detection
|
||||
- **Etch line insertion** on bend-up lines for fabrication reference (via EtchBendLines library)
|
||||
- **PDF export** of SolidWorks drawings
|
||||
- **Local file export** -- saves DXFs and PDFs to a configurable output folder
|
||||
- **SQL Server database** -- tracks export history and BOM data via Entity Framework Core
|
||||
- **View flip control** with automatic, manual, and prefer-up strategies
|
||||
- **Active document tracking** -- connects to SolidWorks and displays the active document in the title bar
|
||||
- **BOM extraction** from drawing BOM tables or assembly component trees
|
||||
- **Drawing history dropdowns** -- equipment and drawing number filters populated from past exports
|
||||
|
||||
## Requirements
|
||||
|
||||
- Windows 10/11
|
||||
- .NET 8.0
|
||||
- SolidWorks (installed and licensed)
|
||||
- SQL Server (default: `localhost`, database: `ExportDxfDb`, Windows auth)
|
||||
|
||||
## Solution Structure
|
||||
|
||||
```
|
||||
ExportDXF.sln
|
||||
ExportDXF/ Main WinForms application
|
||||
EtchBendLines/ Library for adding etch lines to DXF files (git submodule)
|
||||
netDxf/ DXF file read/write library
|
||||
```
|
||||
|
||||
### Key Namespaces
|
||||
|
||||
| Namespace | Purpose |
|
||||
|-----------|---------|
|
||||
| `ExportDXF.Services` | Core services -- SolidWorks connection, DXF export, BOM extraction, PDF export, file export |
|
||||
| `ExportDXF.Forms` | WinForms UI -- main export form with log and BOM grids |
|
||||
| `ExportDXF.Models` | Data models -- BomItem, ExportRecord, ExportContext, SolidWorksDocument |
|
||||
| `ExportDXF.Data` | Entity Framework Core DbContext for SQL Server persistence |
|
||||
| `ExportDXF.ViewFlipDeciders` | Strategies for determining if a flat pattern view should be flipped |
|
||||
| `ExportDXF.ItemExtractors` | Extract component items from BOM tables and assemblies |
|
||||
| `ExportDXF.Utilities` | SolidWorks helpers, sheet metal property extraction, text utilities |
|
||||
| `ExportDXF.Extensions` | Extension methods for SolidWorks, UI, unit conversion, strings, TimeSpan |
|
||||
| `EtchBendLines` | Post-processes DXF files to add etch marks on bend-up lines |
|
||||
|
||||
## How It Works
|
||||
|
||||
1. **Startup** -- Connects to a running SolidWorks instance (or launches one) asynchronously. Loads past export history from the database to populate equipment/drawing dropdowns. Displays the active SolidWorks document name in the title bar.
|
||||
|
||||
2. **Export** -- Based on the active document type:
|
||||
- **Drawing**: Extracts BOM items from BOM tables, exports the drawing as PDF, then iterates through each component to generate flat pattern DXFs.
|
||||
- **Assembly**: Extracts components from the assembly tree and generates flat pattern DXFs for each sheet metal part.
|
||||
- **Part**: Generates a single flat pattern DXF for the active part.
|
||||
|
||||
3. **Post-processing** -- Each exported DXF is run through the EtchBendLines library, which identifies bend-up lines and adds short etch marks at their endpoints on a dedicated `ETCH` layer. Only up bends are etched because SolidWorks automatically flips the flat pattern so the shortest flange is the first bend. The etch marks let the press brake operator verify part orientation right off the laser table without flipping -- the first bend (shortest flange) will always have an etch line.
|
||||
|
||||
4. **Save** -- DXF and PDF files are copied to the configured output folder. Export records and BOM items (with sheet metal properties) are saved to the SQL Server database.
|
||||
|
||||
## Configuration
|
||||
|
||||
Settings are in `App.config`:
|
||||
|
||||
```xml
|
||||
<appSettings>
|
||||
<add key="MaxBendRadius" value="2.0" />
|
||||
<add key="ExportOutputFolder" value="C:\ExportDXF\Output" />
|
||||
</appSettings>
|
||||
<connectionStrings>
|
||||
<add name="ExportDxfDb"
|
||||
connectionString="Server=localhost;Database=ExportDxfDb;Trusted_Connection=True;TrustServerCertificate=True;"
|
||||
providerName="Microsoft.Data.SqlClient" />
|
||||
</connectionStrings>
|
||||
```
|
||||
|
||||
## DXF Filename Format
|
||||
|
||||
Exported files follow the pattern: `{DrawingNumber} PT{ItemNo}.dxf`
|
||||
|
||||
Example: `5007 A02 PT01.dxf`
|
||||
|
||||
## Building
|
||||
|
||||
Open `ExportDXF.sln` in Visual Studio and build. The EtchBendLines submodule must be initialized:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
32
docs/plans/2026-02-17-autofill-from-export-history-design.md
Normal file
32
docs/plans/2026-02-17-autofill-from-export-history-design.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Auto-fill Equipment/Drawing from Export History
|
||||
|
||||
## Problem
|
||||
|
||||
When a SolidWorks file is opened that doesn't match the `DrawingInfo` regex (e.g., `Conveyor Frame.sldasm` instead of `5028 A02 Conveyor.slddrw`), the equipment and drawing number dropdowns are left empty even though the file may have been exported before with known values.
|
||||
|
||||
## Decision
|
||||
|
||||
- **Lookup key:** SolidWorks source file path (`ActiveDocument.FilePath`)
|
||||
- **Storage:** Query the existing `ExportRecords` table (no new table or migration)
|
||||
- **Priority:** Database lookup first; fall back to title regex parse if no history found
|
||||
|
||||
## Design
|
||||
|
||||
### Modify `UpdateActiveDocumentDisplay()` in `MainForm.cs`
|
||||
|
||||
When the active document changes:
|
||||
|
||||
1. Query the DB for the most recent `ExportRecord` where `SourceFilePath` matches `activeDoc.FilePath` (case-insensitive)
|
||||
2. If found, parse the stored `DrawingNumber` via `DrawingInfo.Parse()` and auto-fill equipment/drawing dropdowns
|
||||
3. If not found, fall back to current behavior: `DrawingInfo.Parse(activeDoc.Title)`
|
||||
|
||||
### What doesn't change
|
||||
|
||||
- No schema changes, no new migration
|
||||
- Equipment/drawing dropdowns still populated with historical values in `InitializeDrawingDropdowns()`
|
||||
- Export flow untouched
|
||||
|
||||
### Error handling
|
||||
|
||||
- DB query wrapped in try/catch so failures don't break the UI
|
||||
- Case-insensitive path comparison (Windows paths are case-insensitive)
|
||||
1191
docs/plans/2026-02-17-fabworks-api.md
Normal file
1191
docs/plans/2026-02-17-fabworks-api.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user