From ba67ff7da743ff28f6f5d22a833873c515146c69 Mon Sep 17 00:00:00 2001 From: liuyu65 Date: Sat, 22 Aug 2026 17:20:02 +0800 Subject: [PATCH] Design Express Res initial commit --- AddIn/deploy/DesignExpress.Core.dll | Bin 0 -> 99840 bytes AddIn/deploy/DesignExpress.InventorLink.dll | Bin 0 -> 47104 bytes AddIn/deploy/DesignExpress.SolidWorksLink.dll | Bin 0 -> 30208 bytes AddIn/deploy/install.ps1 | 83 +++ AddIn/deploy/install_solidworks.ps1 | 59 ++ AddIn/src/Core/AppState.cs | 96 +++ AddIn/src/Core/CheckEngine.cs | 667 ++++++++++++++++++ AddIn/src/Core/Checks.cs | 115 +++ AddIn/src/Core/DesignQuery.cs | 208 ++++++ AddIn/src/Core/FitForm.cs | 199 ++++++ AddIn/src/Core/ICadHost.cs | 17 + AddIn/src/Core/KnowledgeBase.cs | 235 ++++++ AddIn/src/Core/MainForm.cs | 190 +++++ AddIn/src/Core/QueryForm.cs | 264 +++++++ AddIn/src/Core/Report.cs | 251 +++++++ AddIn/src/Core/Snapshots.cs | 90 +++ AddIn/src/Core/ToolIcons.cs | 236 +++++++ AddIn/src/InventorLink/AddInServer.cs | 211 ++++++ AddIn/src/InventorLink/InventorHost.cs | 458 ++++++++++++ AddIn/src/SolidWorksLink/SolidWorksHost.cs | 326 +++++++++ AddIn/src/SolidWorksLink/SwAddinServer.cs | 283 ++++++++ AddIn/src/SolidWorksLink/SwIcons.cs | 34 + README.md | 113 +++ 23 files changed, 4135 insertions(+) create mode 100644 AddIn/deploy/DesignExpress.Core.dll create mode 100644 AddIn/deploy/DesignExpress.InventorLink.dll create mode 100644 AddIn/deploy/DesignExpress.SolidWorksLink.dll create mode 100644 AddIn/deploy/install.ps1 create mode 100644 AddIn/deploy/install_solidworks.ps1 create mode 100644 AddIn/src/Core/AppState.cs create mode 100644 AddIn/src/Core/CheckEngine.cs create mode 100644 AddIn/src/Core/Checks.cs create mode 100644 AddIn/src/Core/DesignQuery.cs create mode 100644 AddIn/src/Core/FitForm.cs create mode 100644 AddIn/src/Core/ICadHost.cs create mode 100644 AddIn/src/Core/KnowledgeBase.cs create mode 100644 AddIn/src/Core/MainForm.cs create mode 100644 AddIn/src/Core/QueryForm.cs create mode 100644 AddIn/src/Core/Report.cs create mode 100644 AddIn/src/Core/Snapshots.cs create mode 100644 AddIn/src/Core/ToolIcons.cs create mode 100644 AddIn/src/InventorLink/AddInServer.cs create mode 100644 AddIn/src/InventorLink/InventorHost.cs create mode 100644 AddIn/src/SolidWorksLink/SolidWorksHost.cs create mode 100644 AddIn/src/SolidWorksLink/SwAddinServer.cs create mode 100644 AddIn/src/SolidWorksLink/SwIcons.cs create mode 100644 README.md diff --git a/AddIn/deploy/DesignExpress.Core.dll b/AddIn/deploy/DesignExpress.Core.dll new file mode 100644 index 0000000000000000000000000000000000000000..1fe15591951d93a90ae736a26d56a3a03273d6ed GIT binary patch literal 99840 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 literal 0 HcmV?d00001 diff --git a/AddIn/deploy/DesignExpress.InventorLink.dll b/AddIn/deploy/DesignExpress.InventorLink.dll new file mode 100644 index 0000000000000000000000000000000000000000..222a875843a417cf366ada14fdeece694158738d GIT binary patch literal 47104 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 + $clsid + $clsid + Design Express Res + 设计标注检查与设计数据查询工具(Core+Link 架构) + $dstLink + 1 + 1 + 0 + 25.. + 1 + 1 + +"@ +$addinFile = Join-Path $addinDir "DesignExpress.InventorLink.addin" +[System.IO.File]::WriteAllText($addinFile, $addinXml, [System.Text.Encoding]::UTF8) +Write-Host ".addin 已生成: $addinFile" + +# 清理旧版本文件 +Remove-Item (Join-Path $addinDir "InventorDesignCheck.dll") -Force -ErrorAction SilentlyContinue +Remove-Item (Join-Path $addinDir "InventorDesignCheck.addin") -Force -ErrorAction SilentlyContinue + +# 关键: 修复 AddInLoadRules(加载规则文件), 确保本加载项允许加载 +# 该文件为 UTF-16 二进制, 每条记录: "{GUID}" + DWORD(0=允许, 1=禁止) +$rulesFile = Join-Path $addinDir "AddInLoadRules" +if (Test-Path $rulesFile) { + $b = [System.IO.File]::ReadAllBytes($rulesFile) + $u = [System.Text.Encoding]::Unicode.GetString($b) + $guid = "{D422E0BF-02D0-4DAE-958A-7BBA80B718E9}" + $pos = $u.IndexOf($guid) + if ($pos -ge 0) { + $bytePos = $pos * 2 + $guid.Length * 2 + if ([BitConverter]::ToUInt32($b, $bytePos) -ne 0) { + $b[$bytePos] = 0; $b[$bytePos + 1] = 0; $b[$bytePos + 2] = 0; $b[$bytePos + 3] = 0 + [System.IO.File]::WriteAllBytes($rulesFile, $b) + Write-Host "已修复加载规则(允许加载)" + } + } +} + +Write-Host "部署完成! 请重启 Inventor, 功能区将出现 [Design Express Res] 选项卡。" diff --git a/AddIn/deploy/install_solidworks.ps1 b/AddIn/deploy/install_solidworks.ps1 new file mode 100644 index 0000000..5713657 --- /dev/null +++ b/AddIn/deploy/install_solidworks.ps1 @@ -0,0 +1,59 @@ +# Design Express Res - SolidWorks Link 部署脚本 (无需管理员权限, 但 HKLM 注册需手动确认一次) +# 说明: 首次部署后需正常退出并重启 SolidWorks 一次, 让选项卡定义持久化到注册表 +$ErrorActionPreference = "Stop" + +$swDir = Join-Path $env:APPDATA "DesignExpress\SolidWorks" +$clsid = "{4A7E023E-EDD8-4642-B337-425E02394C81}" +$progId = "DesignExpress.SolidWorksLink.SwAddin" +$asmName = "DesignExpress.SolidWorksLink, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" + +$srcCore = Join-Path $PSScriptRoot "DesignExpress.Core.dll" +$srcLink = Join-Path $PSScriptRoot "DesignExpress.SolidWorksLink.dll" +if (-not (Test-Path $srcCore)) { Write-Host "未找到 DesignExpress.Core.dll"; exit 1 } +if (-not (Test-Path $srcLink)) { Write-Host "未找到 DesignExpress.SolidWorksLink.dll"; exit 1 } + +New-Item -ItemType Directory -Force -Path $swDir | Out-Null +Copy-Item -LiteralPath $srcCore -Destination (Join-Path $swDir "DesignExpress.Core.dll") -Force +$dstLink = Join-Path $swDir "DesignExpress.SolidWorksLink.dll" +Copy-Item -LiteralPath $srcLink -Destination $dstLink -Force +Write-Host "DLL 已复制: $swDir" + +# 每用户 COM 注册 (HKCU) +$clsBase = "HKCU:\Software\Classes\CLSID\$clsid" +Remove-Item -Path $clsBase -Recurse -Force -ErrorAction SilentlyContinue +New-Item -Path $clsBase -Force | Out-Null +Set-ItemProperty -Path $clsBase -Name "(default)" -Value $progId +New-Item -Path "$clsBase\InprocServer32" -Force | Out-Null +Set-ItemProperty -Path "$clsBase\InprocServer32" -Name "(default)" -Value "mscoree.dll" +Set-ItemProperty -Path "$clsBase\InprocServer32" -Name "Class" -Value $progId +Set-ItemProperty -Path "$clsBase\InprocServer32" -Name "Assembly" -Value $asmName +$codebase = "file:///" + ($dstLink.Replace('\', '/')) +Set-ItemProperty -Path "$clsBase\InprocServer32" -Name "CodeBase" -Value $codebase +Set-ItemProperty -Path "$clsBase\InprocServer32" -Name "ThreadingModel" -Value "Both" + +$progBase = "HKCU:\Software\Classes\$progId" +Remove-Item -Path $progBase -Recurse -Force -ErrorAction SilentlyContinue +New-Item -Path "$progBase\CLSID" -Force | Out-Null +Set-ItemProperty -Path "$progBase\CLSID" -Name "(default)" -Value $clsid + +# SolidWorks 加载项注册 (当前用户, 启动时加载) +$swAddinKey = "HKCU:\Software\SolidWorks\AddIns\$clsid" +Remove-Item -Path $swAddinKey -Recurse -Force -ErrorAction SilentlyContinue +New-Item -Path $swAddinKey -Force | Out-Null +Set-ItemProperty -Path $swAddinKey -Name "(default)" -Value 1 -Type DWord +Set-ItemProperty -Path $swAddinKey -Name "Title" -Value "Design Express Res" +Set-ItemProperty -Path $swAddinKey -Name "Description" -Value "设计标注检查与设计数据查询工具" + +$swStartupKey = "HKCU:\Software\SolidWorks\AddInsStartup\$clsid" +New-Item -Path $swStartupKey -Force | Out-Null +Set-ItemProperty -Path $swStartupKey -Name "(default)" -Value 1 -Type DWord +Write-Host "SolidWorks 加载项已注册(启动时自动加载)" + +Write-Host "" +Write-Host "=== 下一步(重要) ===" +Write-Host "1. 若首次安装, 请以管理员身份导入注册表(加载项清单需要 HKLM):" +Write-Host " reg add ""HKLM\SOFTWARE\SolidWorks\AddIns\$clsid"" /ve /t REG_SZ /d 1 /f" +Write-Host " reg add ""HKLM\SOFTWARE\SolidWorks\AddIns\$clsid"" /v Title /t REG_SZ /d ""Design Express Res"" /f" +Write-Host "2. 启动 SolidWorks, 在 工具→插件 中勾选 Design Express Res" +Write-Host "3. 打开一个零件文档 -> 出现 Design Express Res 选项卡" +Write-Host "4. 正常退出 SolidWorks 并重新打开(持久化选项卡定义)" diff --git a/AddIn/src/Core/AppState.cs b/AddIn/src/Core/AppState.cs new file mode 100644 index 0000000..afce70a --- /dev/null +++ b/AddIn/src/Core/AppState.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; + +namespace DesignExpress.Core +{ + public class CheckResult + { + public string Stage; + public string Obj; + public string CheckID; + public string Name; + public string Result; + public string Note; + public string Basis; + } + + public static class AppState + { + public static ICadHost Host; + public static List Results = new List(); + + public static void Add(string stage, string obj, string id, string name, + string result, string note, string basis = "") + { + Results.Add(new CheckResult + { + Stage = stage, Obj = obj, CheckID = id, Name = name, + Result = result, Note = note, Basis = basis + }); + try + { + if (Host != null) Host.SetStatusText("[" + stage + "] " + obj + " - " + name); + } + catch { } + } + + public static void Count(out int pass, out int warn, out int fail, out int info) + { + pass = 0; warn = 0; fail = 0; info = 0; + foreach (CheckResult r in Results) + { + switch (r.Result) + { + case "通过": pass++; break; + case "警告": warn++; break; + case "未通过": fail++; break; + default: info++; break; + } + } + } + + public static string GetProp(Dictionary props, params string[] names) + { + if (props == null) return ""; + foreach (string n in names) + { + foreach (KeyValuePair kv in props) + { + if (string.Equals(kv.Key, n, StringComparison.OrdinalIgnoreCase)) + return kv.Value ?? ""; + } + } + return ""; + } + + public static bool IsDefaultMaterial(string s) + { + s = (s ?? "").Trim(); + return s == "" || s.Equals("默认", StringComparison.OrdinalIgnoreCase) || + s.Equals("Default", StringComparison.OrdinalIgnoreCase) || + s.Equals("Generic", StringComparison.OrdinalIgnoreCase); + } + + // 在多个文档的属性中搜索关键词 + public static bool HitKeywords(IEnumerable docs, string keys, out string hits) + { + hits = ""; + string[] kArr = keys.Split(','); + foreach (DocInfo d in docs) + { + if (d == null) continue; + string txt = GetProp(d.Props, "Comments", "备注", "注释", "Keywords", "关键词", + "Description", "描述", "名称", "Title", "标题"); + foreach (string k in kArr) + { + if ((txt + d.Name).ToUpper().Contains(k.ToUpper())) + { + if (hits.Length < 300) hits += d.Name + "[" + k + "]; "; + return true; + } + } + } + return false; + } + } +} diff --git a/AddIn/src/Core/CheckEngine.cs b/AddIn/src/Core/CheckEngine.cs new file mode 100644 index 0000000..c8417a8 --- /dev/null +++ b/AddIn/src/Core/CheckEngine.cs @@ -0,0 +1,667 @@ +using System; +using System.Collections.Generic; +using System.Windows.Forms; + +namespace DesignExpress.Core +{ + // 检查引擎: 只处理 DocSnapshot 纯数据, 不依赖任何 CAD 系统 + public static class CheckEngine + { + // ================= 零件阶段 ================= + + public static void RunPartChecks(DocSnapshot doc) + { + string objName = "零件"; + if (doc.Dirty) + AppState.Add("零件", objName, "P01", "文档保存状态", "警告", "存在未保存的修改", "建议检查前保存"); + else + AppState.Add("零件", objName, "P01", "文档保存状态", "通过", "已保存", ""); + + CheckPartProps(doc, objName); + CheckModelAnnotations(doc, objName); + CheckThreads(doc, objName); + CheckMaterialContact(doc, objName); + AppState.Add("零件", objName, "P15", "焊接标注检查", "信息", + "模型焊接符号由工程图 D19 焊接技术要求关键词检查覆盖(双面焊/打磨/标准号)", "spec 文件"); + CheckSurfaceTreat(doc, objName); + CheckStrength(doc, objName); + FitCheckLoop(doc); + } + + private static void CheckPartProps(DocSnapshot doc, string objName) + { + string v; + v = AppState.GetProp(doc.Props, "Part Number", "零件代号", "零件号", "图号"); + if (v == "") AppState.Add("零件", objName, "P02", "零件代号", "未通过", "未填写", "标题栏必填项"); + else AppState.Add("零件", objName, "P02", "零件代号", "通过", v, ""); + + v = AppState.GetProp(doc.Props, "Description", "描述", "名称", "部件名称"); + if (v == "") AppState.Add("零件", objName, "P03", "名称", "未通过", "未填写", "标题栏必填项"); + else AppState.Add("零件", objName, "P03", "名称", "通过", v, ""); + + string mat = doc.MaterialName; + if (mat == "") mat = AppState.GetProp(doc.Props, "Material", "材料", "材质"); + if (AppState.IsDefaultMaterial(mat)) + AppState.Add("零件", objName, "P04", "材料", "未通过", "材料未指定或为默认", "配合面/接触面必须明确材料"); + else + AppState.Add("零件", objName, "P04", "材料", "通过", mat, ""); + + v = AppState.GetProp(doc.Props, "Designer", "设计者", "设计"); + if (v == "") AppState.Add("零件", objName, "P05", "设计者", "未通过", "未填写", ""); + else AppState.Add("零件", objName, "P05", "设计者", "通过", v, ""); + + v = AppState.GetProp(doc.Props, "Checked By", "检查者", "审核者", "校核"); + if (v == "") AppState.Add("零件", objName, "P06", "审核者", "未通过", "未填写", ""); + else AppState.Add("零件", objName, "P06", "审核者", "通过", v, ""); + + v = AppState.GetProp(doc.Props, "Revision Number", "修订号", "版本号"); + if (v == "") AppState.Add("零件", objName, "P07", "修订号", "警告", "未填写修订号", "建议填写以追溯版本"); + else AppState.Add("零件", objName, "P07", "修订号", "通过", v, ""); + } + + private static void CheckModelAnnotations(DocSnapshot doc, string objName) + { + ModelInfo m = doc.Model; + if (m == null) m = new ModelInfo(); + int nDim = m.DimCount, nFcf = m.FcfCount, nTex = m.TexCount, nDat = m.DatumCount, nTn = m.ThreadNoteCount; + + if (nDim < 0) + AppState.Add("零件", objName, "P08", "模型标注统计", "信息", "无法读取模型标注", ""); + else if (nDim == 0) + AppState.Add("零件", objName, "P08", "模型标注统计", "警告", "模型上没有任何尺寸标注, 建议使用三维技术标注(PMI)", "设计标注要求"); + else + AppState.Add("零件", objName, "P08", "模型标注统计", "通过", + "模型尺寸 " + nDim + " 个; 形位公差 " + (nFcf < 0 ? "?" : nFcf.ToString()) + + " 个; 粗糙度 " + (nTex < 0 ? "?" : nTex.ToString()) + + " 个; 基准 " + (nDat < 0 ? "?" : nDat.ToString()) + + " 个; 螺纹孔标注 " + (nTn < 0 ? "?" : nTn.ToString()) + " 个", ""); + + if (nDim > 0) + { + if (!m.TolReadable) + AppState.Add("零件", objName, "P08b", "尺寸公差覆盖", "信息", "部分尺寸公差信息无法读取", ""); + else if (m.TolCount == 0) + AppState.Add("零件", objName, "P08b", "尺寸公差覆盖", "警告", nDim + " 个尺寸均未标注公差", "配合尺寸应标注公差"); + else + AppState.Add("零件", objName, "P08b", "尺寸公差覆盖", "通过", m.TolCount + "/" + nDim + " 个尺寸带公差", ""); + } + + if (nFcf < 0) + AppState.Add("零件", objName, "P08c", "形位公差标注", "信息", "无法读取", ""); + else if (nFcf == 0) + AppState.Add("零件", objName, "P08c", "形位公差标注", "警告", "模型未标注任何形位公差(如跳动/同轴度/垂直度/平面度)", "关键配合特征应标注形位公差"); + else + AppState.Add("零件", objName, "P08c", "形位公差标注", "通过", "共 " + nFcf + " 个形位公差标注", ""); + + if (nTex < 0) + AppState.Add("零件", objName, "P08d", "粗糙度标注", "信息", "无法读取", ""); + else if (nTex == 0) + AppState.Add("零件", objName, "P08d", "粗糙度标注", "警告", "模型未标注表面粗糙度", "配合面/密封面应标注粗糙度"); + else + AppState.Add("零件", objName, "P08d", "粗糙度标注", "通过", "共 " + nTex + " 个粗糙度标注(首个: " + m.FirstTexText + ")", ""); + } + + private static void CheckThreads(DocSnapshot doc, string objName) + { + ModelInfo m = doc.Model; + int n = m == null ? -1 : m.ThreadFeatureCount; + if (n < 0) + AppState.Add("零件", objName, "P13", "螺纹孔检查", "信息", "无法读取螺纹特征", ""); + else if (n == 0) + AppState.Add("零件", objName, "P13", "螺纹孔检查", "通过", "无螺纹特征", ""); + else + AppState.Add("零件", objName, "P13", "螺纹孔检查", "通过", "共 " + n + " 处螺纹特征(示例: " + m.ThreadDesignation + ")", "螺纹代号/深度/攻深需在工程图中完整标注"); + } + + private static void CheckMaterialContact(DocSnapshot doc, string objName) + { + string v = AppState.GetProp(doc.Props, "物料接触", "接触类别", "接触", "ContactClass", "MaterialContact"); + bool direct = false; + if (v != "") + { + if (v.Contains("直接")) direct = true; + AppState.Add("零件", objName, "P14", "物料接触检查", "通过", + direct ? "属性标注: 直接接触 (需满足 GMP 材料/粗糙度/无死角要求)" : "属性标注: " + v, + direct ? "GMP" : ""); + } + else + { + SimpleInput f = new SimpleInput("物料接触", "该零件是否与物料接触?\r\n1=直接接触\r\n2=间接接触(外表/非接触面)\r\n3=非接触\r\n0=跳过", "1"); + if (f.ShowDialog() == DialogResult.OK) + { + int ans = 0; + int.TryParse(f.Value.Trim(), out ans); + switch (ans) + { + case 1: direct = true; + AppState.Add("零件", objName, "P14", "物料接触检查", "通过", "人工确认: 直接接触", "建议在 iProperties 自定义属性中标注'物料接触=直接接触'以固化"); + break; + case 2: AppState.Add("零件", objName, "P14", "物料接触检查", "通过", "人工确认: 间接接触", ""); break; + case 3: AppState.Add("零件", objName, "P14", "物料接触检查", "通过", "人工确认: 非接触", ""); break; + default: AppState.Add("零件", objName, "P14", "物料接触检查", "警告", "未声明物料接触类别, 无法执行 GMP 专项检查", "建议标注接触类别"); break; + } + } + else + AppState.Add("零件", objName, "P14", "物料接触检查", "警告", "未声明物料接触类别, 无法执行 GMP 专项检查", "建议标注接触类别"); + } + + if (direct) + { + string mat = doc.MaterialName; + if (mat == "") mat = AppState.GetProp(doc.Props, "Material", "材料", "材质"); + if (AppState.IsDefaultMaterial(mat)) + AppState.Add("零件", objName, "P14b", "GMP 接触材料", "未通过", "材料未指定或为默认", "直接接触物料必须用卫生级不锈钢(316L)"); + else + { + string rec, basis, vv; + vv = KB.GmpMaterialJudge(mat, out rec, out basis); + AppState.Add("零件", objName, "P14b", "GMP 接触材料", vv, "材料: " + mat + "; " + rec, basis); + } + + ModelInfo m = doc.Model; + int nTex = m == null ? 0 : m.TexCount; + if (nTex > 0) + AppState.Add("零件", objName, "P14c", "GMP 接触面粗糙度", "通过", "已标注粗糙度(共 " + nTex + " 个)", "接触面 Ra<=0.8"); + else + AppState.Add("零件", objName, "P14c", "GMP 接触面粗糙度", "未通过", "接触面未标注粗糙度", "接触面 Ra<=0.8"); + + if (m == null || !m.MinFilletReadable) + AppState.Add("零件", objName, "P14d", "GMP 无死角检查", "信息", "无法读取圆角特征", ""); + else if (m.MinFilletRadiusMM > 0 && m.MinFilletRadiusMM < 10) + AppState.Add("零件", objName, "P14d", "GMP 无死角检查", "警告", + "存在小于 R10 的圆角(最小 R" + m.MinFilletRadiusMM.ToString("0.0") + " mm), 夹角处易积料", + "GMP: 夹角处圆角>=R10, 内夹角>=135度"); + else + AppState.Add("零件", objName, "P14d", "GMP 无死角检查", "通过", + m.MinFilletRadiusMM > 0 ? "最小圆角 R" + m.MinFilletRadiusMM.ToString("0.0") + " mm >= R10" : "未发现圆角特征, 请人工确认", + "GMP 无死角要求"); + } + } + + private static void CheckSurfaceTreat(DocSnapshot doc, string objName) + { + string v = AppState.GetProp(doc.Props, "表面处理", "表面", "Surfacing", "镀层", "防锈", "SurfaceTreatment"); + if (v == "") v = AppState.GetProp(doc.Props, "Comments", "备注", "注释"); + if (v != "" && (v.Contains("镀") || v.Contains("氧化") || v.Contains("抛光") || + v.Contains("防锈") || v.Contains("发黑") || v.Contains("喷砂"))) + AppState.Add("零件", objName, "P16", "表面处理/防锈", "通过", v, ""); + else + AppState.Add("零件", objName, "P16", "表面处理/防锈", "警告", "未标注表面处理/防锈要求(如镀镍是否需磨削/抛光、防锈包装)", "请确认是否适用"); + } + + private static void CheckStrength(DocSnapshot doc, string objName) + { + string mat = doc.MaterialName; + if (mat == "") mat = AppState.GetProp(doc.Props, "Material", "材料", "材质"); + if (AppState.IsDefaultMaterial(mat)) + AppState.Add("零件", objName, "P17", "结构强度/仿真", "未通过", "材料未指定, 无法进行强度分析", "需先指定材料"); + else + AppState.Add("零件", objName, "P17", "结构强度/仿真", "信息", + "材料: " + mat + "; 建议使用 CAD 内置应力分析或导出模型进行有限元/仿真校核", "AI/CAE 校核指引"); + } + + private static void FitCheckLoop(DocSnapshot doc) + { + SimpleInput ask = new SimpleInput("公差配合检查", "是否进行公差配合检查?\r\n1=是\r\n0=否(跳过)", "1"); + if (ask.ShowDialog() != DialogResult.OK) return; + int go = 0; + int.TryParse(ask.Value.Trim(), out go); + if (go != 1) + { + AppState.Add("零件", "零件", "P09", "公差配合检查", "信息", "用户跳过配合检查", ""); + return; + } + + ICadHost host = AppState.Host; + while (true) + { + double diaMM = 0; + string faceName = "手动输入"; + try + { + if (host != null) diaMM = host.PickCylinderDiameterMM(out faceName); + } + catch { diaMM = 0; } + if (diaMM <= 0) + { + SimpleInput di = new SimpleInput("公差配合检查", "请输入配合直径 (mm):", "30"); + if (di.ShowDialog() != DialogResult.OK) return; + double.TryParse(di.Value.Trim(), out diaMM); + if (diaMM <= 0) return; + } + + FitForm form = new FitForm(diaMM, faceName); + if (form.ShowDialog() != DialogResult.OK) break; + + foreach (FitItem it in form.Items) + { + string verdict = it.Verdict; + int seg = KB.SegIndex(it.DiaMM); + string note = "配合面: " + faceName + "; 直径 " + it.DiaMM.ToString("0.0") + " mm" + + (seg < 0 ? " (>500mm 超出表范围)" : " (尺寸段 " + KB.SegName(seg) + ")") + + "; 实际代码 " + it.Code + "; 推荐 " + it.Recommend + "; " + verdict; + AppState.Add("零件", "零件", "P09", "公差配合检查(" + it.Role + ")", verdict, note, it.Basis); + FitAdjacentChecks(doc, it.Role, it.DiaMM, faceName); + } + + if (MessageBox.Show("配合检查结果已加入。是否继续检查其他配合面?", "公差配合检查", + MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) + break; + } + } + + private static void FitAdjacentChecks(DocSnapshot doc, string role, double diaMM, string faceName) + { + string objName = "零件(" + faceName + ")"; + ModelInfo m = doc.Model; + int nFcf = m == null ? 0 : m.FcfCount; + int nTex = m == null ? 0 : m.TexCount; + if (nFcf > 0) + AppState.Add("零件", objName, "P10", "配合面形位公差", "通过", "模型已标注形位公差(共 " + nFcf + " 个)", "轴承位建议标注径向跳动/同轴度, 等级 IT5~IT6"); + else + AppState.Add("零件", objName, "P10", "配合面形位公差", "未通过", "配合面未标注形位公差", "轴承轴颈建议标注径向跳动/同轴度(IT5~IT6); 座孔标注同轴度/垂直度"); + + if (nTex > 0) + AppState.Add("零件", objName, "P11", "配合面粗糙度", "通过", "已标注粗糙度(共 " + nTex + " 个)", "推荐: " + KB.RaSuggest(role)); + else + AppState.Add("零件", objName, "P11", "配合面粗糙度", "未通过", "配合面未标注表面粗糙度", "推荐: " + KB.RaSuggest(role)); + + if (role.StartsWith("轴承")) + { + int seg = KB.SegIndex(diaMM); + string tolNote; + if (seg > 0) + tolNote = "尺寸段 " + KB.SegName(seg) + " 对应 IT6=" + KB.ItTol(6, seg) + "um, IT7=" + KB.ItTol(7, seg) + "um"; + else + tolNote = "直径超过 500mm, 无内置数据"; + AppState.Add("零件", objName, "P12", "轴承位公差等级", "信息", tolNote, "轴承轴颈公差等级一般取 IT5~IT6, 座孔 IT7"); + } + } + + // ================= 装配阶段 ================= + + public static void RunAssemblyChecks(DocSnapshot doc) + { + string objName = "装配"; + if (doc.Dirty) + AppState.Add("装配", objName, "A01", "文档保存状态", "警告", "存在未保存的修改", ""); + else + AppState.Add("装配", objName, "A01", "文档保存状态", "通过", "已保存", ""); + + GmpMaterialCheck(doc, objName); + SealCheck(doc, objName); + CleanCheck(doc, objName); + GmpRoughnessCheck(doc, objName); + InterferenceCheck(doc, objName); + StandardPartsCheck(doc, objName); + ElectricsCheck(doc, objName); + UtilityCheck(doc, objName); + AppState.Add("装配", objName, "A12", "运动干涉(动态)", "信息", + "建议使用 CAD 驱动约束/运动仿真验证运动干涉; 本工具提供静态干涉结果", "运动学检查需要仿真环境"); + } + + private static void GmpMaterialCheck(DocSnapshot doc, string objName) + { + int nPart = 0, nOk = 0, nWarn = 0, nBad = 0; + string badList = ""; + foreach (DocInfo d in doc.OpenDocs) + { + if (d == null) continue; + nPart++; + string rec, basis, v; + v = KB.GmpMaterialJudge(d.Material, out rec, out basis); + if (v == "合理") nOk++; + else if (v == "可接受" || v == "警告") nWarn++; + else nBad++; + if (v != "合理" && badList.Length < 400) + badList += d.Name + "(" + d.Material + "): " + rec + " | "; + } + if (nPart == 0) + AppState.Add("装配", objName, "A02", "GMP 材料合规", "信息", "未发现已加载的零件文档(仅检查已打开文档)", ""); + else if (nBad == 0 && nWarn == 0) + AppState.Add("装配", objName, "A02", "GMP 材料合规", "通过", "全部 " + nPart + " 个零件材料符合 GMP 要求", "GMP 设备设计规范"); + else if (nBad == 0) + AppState.Add("装配", objName, "A02", "GMP 材料合规", "警告", nWarn + "/" + nPart + " 个零件材料需关注: " + badList, "GMP 设备设计规范"); + else + AppState.Add("装配", objName, "A02", "GMP 材料合规", "未通过", nBad + " 个零件材料不合格, " + nWarn + " 个需关注: " + badList, "GMP 设备设计规范"); + } + + private static void SealCheck(DocSnapshot doc, string objName) + { + string names = ""; + int n = 0; + AssemblyInfo asm = doc.Asm; + if (asm != null) + { + foreach (string nm in asm.OccurrenceNames) + { + if (KB.IsSealPartName(nm)) + { + n++; + if (names.Length < 300) names += nm + "; "; + } + } + } + if (n > 0) + { + AppState.Add("装配", objName, "A03", "密封件存在性", "通过", "发现 " + n + " 个密封元件: " + names, "GMP 密封要求"); + AppState.Add("装配", objName, "A04", "密封设计提示", "信息", + "请核对: 密封槽尺寸是否符合 GB/T 3452.3(O型圈槽); 密封材料需符合 FDA 要求; 动密封面 Ra<=0.8", "GB/T 3452.3"); + } + else + { + DialogResult ans = MessageBox.Show("未在装配中发现密封元件(名称不含 密封圈/O型圈/垫片 等关键词)。\r\n该设备是否需要密封设计?", + "密封检查", MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (ans == DialogResult.Yes) + AppState.Add("装配", objName, "A03", "密封件存在性", "未通过", "未发现密封元件, 需补充密封设计或确认命名规范", "GMP 密封要求"); + else + AppState.Add("装配", objName, "A03", "密封件存在性", "通过", "无密封要求(用户确认)", ""); + } + } + + private static void CleanCheck(DocSnapshot doc, string objName) + { + string hits; + List docs = new List(); + docs.Add(new DocInfo { Name = doc.DisplayName, Props = doc.Props, Material = doc.MaterialName }); + docs.AddRange(doc.OpenDocs); + if (AppState.HitKeywords(docs, "清洁,清洗,CIP,SIP,死角,排空,卫生,可清洗", out hits)) + AppState.Add("装配", objName, "A05", "清洁性设计要求", "通过", "已体现清洁性设计说明: " + hits, "GMP 清洁性要求"); + else + AppState.Add("装配", objName, "A05", "清洁性设计要求", "警告", "未在文档属性中发现清洁/清洗/CIP/死角等设计要求说明", "GMP 要求: 设备应可清洁、无死角、易排空"); + } + + private static void GmpRoughnessCheck(DocSnapshot doc, string objName) + { + string noTex = ""; + int nPart = 0, nWith = 0; + foreach (DocInfo d in doc.OpenDocs) + { + if (d == null) continue; + nPart++; + if (d.TexCount > 0) nWith++; + else if (noTex.Length < 300) noTex += d.Name + "; "; + } + if (nPart == 0) + AppState.Add("装配", objName, "A06", "GMP 粗糙度标注", "信息", "未发现已加载的零件文档", ""); + else if (nWith == nPart) + AppState.Add("装配", objName, "A06", "GMP 粗糙度标注", "通过", "所有 " + nPart + " 个零件均标注了粗糙度", "GMP 接触面 Ra<=0.8"); + else + AppState.Add("装配", objName, "A06", "GMP 粗糙度标注", "警告", (nPart - nWith) + "/" + nPart + " 个零件未标注粗糙度: " + noTex, "GMP 接触面 Ra<=0.8; 建议物料接触面电解抛光"); + } + + private static void InterferenceCheck(DocSnapshot doc, string objName) + { + AssemblyInfo asm = doc.Asm; + if (asm == null) + { + AppState.Add("装配", objName, "A07", "静态干涉", "信息", "无法读取装配信息", ""); + return; + } + if (asm.InterferenceState == 2) + { + AppState.Add("装配", objName, "A07", "静态干涉", "信息", "干涉分析执行失败(" + asm.InterferenceError + "), 请使用 CAD 自带干涉检查验证", ""); + return; + } + if (asm.InterferenceState == 0) + AppState.Add("装配", objName, "A07", "静态干涉", "通过", "未发现静态干涉(共检查 " + asm.TopLevelCount + " 个顶层零部件)", "干涉检查"); + else + { + string detail = ""; + for (int j = 0; j < asm.Interferences.Count && detail.Length < 300; j++) + { + InterferencePair p = asm.Interferences[j]; + detail += p.Name1 + " 与 " + p.Name2 + " 体积 " + p.VolumeCM3.ToString("0.00") + "cm3; "; + } + AppState.Add("装配", objName, "A07", "静态干涉", "未通过", "发现 " + asm.Interferences.Count + " 处静态干涉: " + detail, "干涉检查"); + } + } + + private static void StandardPartsCheck(DocSnapshot doc, string objName) + { + AssemblyInfo asm = doc.Asm; + string stdNames = ""; + int stdN = 0, totalN = 0; + if (asm != null) + { + totalN = asm.OccurrenceNames.Count; + foreach (string nm in asm.OccurrenceNames) + { + if (KB.IsStdPartName(nm)) + { + stdN++; + if (stdNames.Length < 200) stdNames += nm + "; "; + } + } + } + if (totalN == 0) + AppState.Add("装配", objName, "A09", "外购件优先", "信息", "未统计到零部件", ""); + else + { + double ratio = (double)stdN / (double)totalN; + AppState.Add("装配", objName, "A09", "外购件优先", "通过", + "标准/外购件 " + stdN + "/" + totalN + " (" + ratio.ToString("0%") + "): " + stdNames, "优先选用标准外购件"); + } + } + + private static void ElectricsCheck(DocSnapshot doc, string objName) + { + string hits; + List docs = new List(); + docs.Add(new DocInfo { Name = doc.DisplayName, Props = doc.Props, Material = doc.MaterialName }); + docs.AddRange(doc.OpenDocs); + if (AppState.HitKeywords(docs, "西门子,SIEMENS,联网,以太网,PROFINET,PLC,IP65,防爆,电气,变频器,伺服", out hits)) + AppState.Add("装配", objName, "A10", "电气选型/西门子联网", "通过", hits, "电气元件选型技术要求"); + else + AppState.Add("装配", objName, "A10", "电气选型/西门子联网", "警告", "未发现电气选型/西门子联网相关说明", "如涉及电气元件, 需注明选型及联网(如西门子 PROFINET)要求"); + } + + private static void UtilityCheck(DocSnapshot doc, string objName) + { + string hits; + List docs = new List(); + docs.Add(new DocInfo { Name = doc.DisplayName, Props = doc.Props, Material = doc.MaterialName }); + docs.AddRange(doc.OpenDocs); + if (AppState.HitKeywords(docs, "施工,动力,公用工程,压缩空气,纯化水,蒸汽,排风,水电气,安装要求,配孔,吊顶", out hits)) + AppState.Add("装配", objName, "A11", "施工/公共动力系统", "通过", hits, "施工及公用动力安装技术要求"); + else + AppState.Add("装配", objName, "A11", "施工/公共动力系统", "警告", "未发现施工/公共动力系统相关说明(压缩空气/纯化水/蒸汽/排风/配孔/吊顶等)", "如涉及需补充技术要求"); + } + + // ================= 工程图阶段 ================= + + private class SpecGroup + { + public string ID; + public string Name; + public string Keys; + } + + private static readonly SpecGroup[] Specs = new SpecGroup[] + { + new SpecGroup { ID = "D18", Name = "物料接触类别声明", Keys = "接触,物料接触,直接接触,间接接触,接触类别" }, + new SpecGroup { ID = "D19", Name = "焊接技术要求", Keys = "焊接,双面焊,单面焊,打磨,焊缝,焊接标准" }, + new SpecGroup { ID = "D20", Name = "死角/夹角要求", Keys = "死角,无死角,夹角,圆角,清洁,排空,R10,135" }, + new SpecGroup { ID = "D21", Name = "公差要求完整性", Keys = "公差,配合,精度,未注公差" }, + new SpecGroup { ID = "D22", Name = "轴承位公差标注", Keys = "轴承,跳动,同轴,旋转精度" }, + new SpecGroup { ID = "D23", Name = "螺纹孔标注", Keys = "螺纹,螺孔,攻丝,丝锥,M6,M8,M10,M12,M16" }, + new SpecGroup { ID = "D24", Name = "表面处理/镀镍", Keys = "镀镍,镀铬,镀锌,电镀,抛光,表面处理,磨床,磨削,防锈,发黑,阳极氧化" }, + new SpecGroup { ID = "D25", Name = "平面度/平行度", Keys = "平面度,平行度,垂直度" }, + new SpecGroup { ID = "D26", Name = "特殊角度管加工", Keys = "角度管,弯管,折弯,倾斜,特殊角度" }, + new SpecGroup { ID = "D27", Name = "装配治具与检验", Keys = "治具,检具,检验,检测,夹具" }, + new SpecGroup { ID = "D28", Name = "加工辅助件要求", Keys = "辅助件,工艺,毛坯,余量,加工基准" }, + new SpecGroup { ID = "D29", Name = "外购件优先", Keys = "外购,标准件,优先采购" }, + new SpecGroup { ID = "D30", Name = "测试要求", Keys = "测试,试验,试压,气密,检漏,出厂测试" }, + new SpecGroup { ID = "D31", Name = "装配要求", Keys = "装配,安装,扭矩,紧固,间隙" }, + new SpecGroup { ID = "D32", Name = "防锈要求", Keys = "防锈,防锈油,防锈剂,包装,海运,真空包装" }, + new SpecGroup { ID = "D33", Name = "施工/公共动力系统", Keys = "施工,动力,公用工程,水电气,压缩空气,蒸汽,纯化水,排风" }, + new SpecGroup { ID = "D34", Name = "配孔技术要求", Keys = "配孔,配钻,现场配,扩孔" }, + new SpecGroup { ID = "D35", Name = "吊顶配件要求", Keys = "吊顶,吊架,吊装,悬挂" }, + new SpecGroup { ID = "D36", Name = "电气元件选型", Keys = "电气,元件,选型,防爆,防护等级,IP65" }, + new SpecGroup { ID = "D37", Name = "西门子联网器件", Keys = "西门子,SIEMENS,联网,以太网,PROFINET,总线" }, + new SpecGroup { ID = "D38", Name = "结构强度/仿真", Keys = "强度,仿真,有限元,FEA,应力" }, + new SpecGroup { ID = "D39", Name = "夹角R10/135度确认", Keys = "夹角,135,R10,圆角" } + }; + + public static void RunDrawingChecks(DocSnapshot doc) + { + string objName = "工程图"; + if (doc.Dirty) + AppState.Add("工程图", objName, "D00", "文档保存状态", "警告", "存在未保存的修改", ""); + else + AppState.Add("工程图", objName, "D00", "文档保存状态", "通过", "已保存", ""); + + int idx = 0; + foreach (SheetInfo sh in doc.Sheets) + { + idx++; + CheckSheet(doc, sh, idx); + } + RunSpecChecks(doc); + } + + private static void CheckSheet(DocSnapshot doc, SheetInfo sh, int idx) + { + string objName = "图纸" + idx + "(" + sh.Name + ")"; + + if (!sh.HasBorder && !sh.HasTitleBlock) + AppState.Add("工程图", objName, "D01", "图框", "未通过", "图纸没有图框/标题栏", ""); + else if (!sh.HasTitleBlock) + AppState.Add("工程图", objName, "D01", "图框", "警告", "有图框但无标题栏", ""); + else + AppState.Add("工程图", objName, "D01", "图框", "通过", "标题栏: " + sh.TitleBlockName, ""); + + CheckTitleBlockProps(doc, objName); + + int nViews = sh.ViewCount; + int nAnn = sh.DimCount + sh.FcfCount + sh.TexCount + sh.CenterlineCount + sh.BalloonCount + sh.NotesTexts.Count; + if (nViews == 0 && nAnn == 0) + AppState.Add("工程图", objName, "D03", "空白图纸", "未通过", "图纸无视图且无任何标注", ""); + else + AppState.Add("工程图", objName, "D03", "空白图纸", "通过", "视图 " + nViews + " 个, 标注 " + nAnn + " 个", ""); + + if (sh.UpdateError > 0) + AppState.Add("工程图", objName, "D04", "视图更新状态", "未通过", sh.UpdateError + " 个视图更新出错", ""); + else if (sh.UpdateNeeded > 0) + AppState.Add("工程图", objName, "D04", "视图更新状态", "未通过", sh.UpdateNeeded + " 个视图需要更新", "出图前必须更新视图"); + else + AppState.Add("工程图", objName, "D04", "视图更新状态", "通过", "所有视图已更新", ""); + + AppState.Add("工程图", objName, "D05", "剖视图统计", "通过", "共 " + sh.SectionCount + " 个剖视图", "剖视图需带剖面线及剖切位置标注"); + + if (sh.BorderRect != null) + { + int nOut = 0; + foreach (RectD r in sh.ViewRects) + { + if (RectOut(r, sh.BorderRect, 0.5)) nOut++; + } + if (nOut > 0) + AppState.Add("工程图", objName, "D06", "视图越界", "未通过", nOut + " 个视图超出图框", ""); + else + AppState.Add("工程图", objName, "D06", "视图越界", "通过", "视图均在图框内", ""); + } + else + AppState.Add("工程图", objName, "D06", "视图越界", "信息", "无图框可参考, 跳过", ""); + + AppState.Add("工程图", objName, "D07", "尺寸数量", "通过", "共 " + sh.DimCount + " 个尺寸", ""); + if (sh.DimCount == 0) + AppState.Add("工程图", objName, "D08", "尺寸公差", "信息", "无尺寸可统计", ""); + else if (sh.DimTolCount == 0 && sh.DimCount >= 5) + AppState.Add("工程图", objName, "D08", "尺寸公差", "警告", "共 " + sh.DimCount + " 个尺寸均未标注公差", "配合/关键尺寸必须标注公差"); + else + AppState.Add("工程图", objName, "D08", "尺寸公差", "通过", sh.DimTolCount + "/" + sh.DimCount + " 个尺寸带公差", ""); + if (sh.DimQuestionCount > 0) + AppState.Add("工程图", objName, "D09", "尺寸文本异常", "未通过", sh.DimQuestionCount + " 个尺寸文本含\"?\"(测量失败或未更新)", ""); + else + AppState.Add("工程图", objName, "D09", "尺寸文本异常", "通过", "尺寸文本正常", ""); + + AppState.Add("工程图", objName, "D10", "形位公差", "通过", "共 " + sh.FcfCount + " 个", ""); + AppState.Add("工程图", objName, "D11", "基准符号", "信息", "CAD API 不支持自动读取基准符号, 请人工核对", ""); + AppState.Add("工程图", objName, "D12", "表面粗糙度", "通过", "共 " + sh.TexCount + " 个", ""); + AppState.Add("工程图", objName, "D13", "焊接符号", "信息", "CAD API 不支持自动读取焊接符号, 由 D19 焊接技术要求关键词检查覆盖", "spec 文件"); + AppState.Add("工程图", objName, "D14", "孔标注", "信息", "CAD API 不支持自动读取孔标注, 由 D23 螺纹孔关键词检查覆盖", "spec 文件"); + AppState.Add("工程图", objName, "D15", "中心线/件号", "通过", "中心线 " + sh.CenterlineCount + " 个, 件号 " + sh.BalloonCount + " 个", ""); + + if (sh.BorderRect != null) + { + int nOut = 0; + string firstNote = ""; + foreach (RectD r in sh.FcfRects) + { + if (RectOut(r, sh.BorderRect, 0.2)) { nOut++; if (firstNote == "") firstNote = "形位公差"; } + } + foreach (RectD r in sh.TexRects) + { + if (RectOut(r, sh.BorderRect, 0.2)) { nOut++; if (firstNote == "") firstNote = "粗糙度"; } + } + if (nOut > 0) + AppState.Add("工程图", objName, "D16", "标注越界", "未通过", nOut + " 个标注超出图框, 如: " + firstNote, ""); + else + AppState.Add("工程图", objName, "D16", "标注越界", "通过", "标注均在图框内", ""); + } + else + AppState.Add("工程图", objName, "D16", "标注越界", "信息", "无图框可参考, 跳过", ""); + + AppState.Add("工程图", objName, "D17", "图幅", "信息", sh.SheetWidth + " x " + sh.SheetHeight + " (cm)", ""); + } + + private static bool RectOut(RectD r, RectD b, double margin) + { + if (r == null || b == null) return false; + return (r.L < b.L - margin) || (r.T < b.T - margin) || (r.R > b.R + margin) || (r.B > b.B + margin); + } + + private static void CheckTitleBlockProps(DocSnapshot doc, string objName) + { + string missing = ""; + string v = AppState.GetProp(doc.Props, "Part Number", "零件代号", "零件号", "图号"); + if (v == "") missing += "零件代号/图号 "; + v = AppState.GetProp(doc.Props, "Description", "描述", "名称", "部件名称"); + if (v == "") missing += "名称 "; + v = AppState.GetProp(doc.Props, "Material", "材料", "材质"); + if (v == "") missing += "材料 "; + v = AppState.GetProp(doc.Props, "Designer", "设计者", "设计"); + if (v == "") missing += "设计者 "; + v = AppState.GetProp(doc.Props, "Checked By", "检查者", "审核者", "校核"); + if (v == "") missing += "审核者 "; + v = AppState.GetProp(doc.Props, "Revision Number", "修订号", "版本号"); + if (v == "") missing += "修订号 "; + if (missing == "") + AppState.Add("工程图", objName, "D02", "标题栏属性", "通过", "图号/名称/材料/设计者/审核者/修订号 均已填写", ""); + else + AppState.Add("工程图", objName, "D02", "标题栏属性", "未通过", "未填写: " + missing, "标题栏信息完整性"); + } + + private static void RunSpecChecks(DocSnapshot doc) + { + string notes = ""; + foreach (SheetInfo sh in doc.Sheets) + { + foreach (string t in sh.NotesTexts) + notes += " " + t; + } + notes += " " + AppState.GetProp(doc.Props, "Comments", "备注", "注释", "Keywords", "关键词", "Description", "描述"); + + foreach (SpecGroup g in Specs) + { + string[] keys = g.Keys.Split(','); + bool found = false; + string hitKey = ""; + foreach (string k in keys) + { + if (notes.ToUpper().Contains(k.ToUpper())) + { + found = true; + hitKey = k; + break; + } + } + if (found) + AppState.Add("工程图", "工程图", g.ID, g.Name, "通过", "图纸中已标注相关要求(关键词: " + hitKey + ")", "spec 文件"); + else + AppState.Add("工程图", "工程图", g.ID, g.Name, "警告", "图纸技术要求中未发现[" + g.Name + "]相关要求, 如适用请补充标注或人工确认", "spec 文件"); + } + } + } +} diff --git a/AddIn/src/Core/Checks.cs b/AddIn/src/Core/Checks.cs new file mode 100644 index 0000000..76240df --- /dev/null +++ b/AddIn/src/Core/Checks.cs @@ -0,0 +1,115 @@ +using System; +using System.Windows.Forms; + +namespace DesignExpress.Core +{ + // Core 统一入口: 供各 CAD Link 调用(Inventor/Creo/SolidWorks) + public static class Checks + { + private static MainForm mForm; + + private static void EnsureForm() + { + if (mForm == null || mForm.IsDisposed) + mForm = new MainForm(); + } + + private static bool Prepare(out DocSnapshot snap, params string[] allowKinds) + { + snap = null; + ICadHost host = AppState.Host; + if (host == null) return false; + object doc = host.GetActiveDocument(); + if (doc == null) + { + MessageBox.Show("请先打开文档", "Design Express Res", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return false; + } + string kind = host.GetDocType(doc); + bool ok = false; + foreach (string k in allowKinds) + { + if (kind == k) ok = true; + } + if (!ok) + { + MessageBox.Show("当前文档类型不支持该检查", "Design Express Res", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return false; + } + try { snap = host.Capture(doc); } + catch (Exception ex) + { + MessageBox.Show("读取文档失败: " + ex.Message, "Design Express Res", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; + } + return snap != null; + } + + public static void PartCheck() + { + DocSnapshot snap; + if (!Prepare(out snap, "Part", "Assembly")) return; + EnsureForm(); + mForm.Show(); + mForm.Activate(); + mForm.RunAndLoad("零件", delegate { CheckEngine.RunPartChecks(snap); }); + } + + public static void AssemblyCheck() + { + DocSnapshot snap; + if (!Prepare(out snap, "Assembly")) return; + EnsureForm(); + mForm.Show(); + mForm.Activate(); + mForm.RunAndLoad("装配", delegate { CheckEngine.RunAssemblyChecks(snap); }); + } + + public static void DrawingCheck() + { + DocSnapshot snap; + if (!Prepare(out snap, "Drawing")) return; + EnsureForm(); + mForm.Show(); + mForm.Activate(); + mForm.RunAndLoad("工程图", delegate { CheckEngine.RunDrawingChecks(snap); }); + } + + public static void ExportReport() + { + if (AppState.Results.Count == 0) + { + MessageBox.Show("暂无检查结果, 请先执行检查", "Design Express Res", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; + } + SaveFileDialog dlg = new SaveFileDialog(); + dlg.Title = "保存检查报告"; + dlg.Filter = "Excel 报告 (*.xlsx)|*.xlsx|CSV 报告 (*.csv)|*.csv"; + dlg.FileName = "检查报告_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".xlsx"; + if (dlg.ShowDialog() == DialogResult.OK) + { + string file = dlg.FileName.ToLower().EndsWith(".csv") ? Report.WriteCsv(dlg.FileName) : Report.WriteExcel(dlg.FileName); + MessageBox.Show("报告已生成: " + file, "Design Express Res", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + } + + public static void Help() + { + MessageBox.Show( + "Design Express Res v" + Report.ToolVersion + "\r\n\r\n" + + "零件检查: 标注统计、公差配合(GB/T 275)、形位公差、粗糙度、螺纹孔、物料接触 GMP、表面处理、强度指引\r\n" + + "装配检查: GMP 材料、密封件、清洁性、粗糙度、静态干涉、外购件、电气/西门子、动力系统\r\n" + + "工程图检查: 图框/标题栏、视图更新、尺寸公差、标注统计、22 项 spec 技术要求关键词检查\r\n\r\n" + + "架构: Core 独立核心库 + 各 CAD Link 适配层(Inventor/Creo/SolidWorks), 类似 COMSOL LiveLink。\r\n" + + "报告输出为 Excel(汇总/明细/检查项清单), Excel 不可用时输出 CSV。", + "使用说明", MessageBoxButtons.OK, MessageBoxIcon.Information); + } + + // ============ 设计数据查询工具入口(独立系统) ============ + + public static void CirclipQuery() { QueryForm.ShowQuery(0); } + public static void KeywayQuery() { QueryForm.ShowQuery(1); } + public static void OringQuery() { QueryForm.ShowQuery(2); } + public static void BearingRingQuery() { QueryForm.ShowQuery(3); } + } +} diff --git a/AddIn/src/Core/DesignQuery.cs b/AddIn/src/Core/DesignQuery.cs new file mode 100644 index 0000000..8606e25 --- /dev/null +++ b/AddIn/src/Core/DesignQuery.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; + +namespace DesignExpress.Core +{ + public class QueryRow + { + public string Item; + public string Value; + public string Std; + public string Remark; + } + + public enum QueryKind + { + Circlip = 0, // 卡簧槽 GB/T 894.1 + Keyway = 1, // 键槽 GB/T 1095 + Oring = 2, // O 型密封圈 GB/T 3452.1 (规划中) + BearingRing = 3 // 轴承挡圈 (规划中) + } + + // 设计尺寸查询数据引擎: 纯数据表查询, 与 CAD 无关 + // 注: 数据为常用参考值, 正式设计请核对最新版标准 + public static class DesignQuery + { + private const string DISCLAIMER = "参考值, 以最新标准为准"; + + // ================= 键槽 (GB/T 1095) ================= + + private class KeywayRow + { + public double Dmin, Dmax; + public string KeySize; // b×h + public double T1, T2; // 轴槽深, 轮毂槽深 + public string LRange; // 键长范围 + } + + private static readonly KeywayRow[] Keyways = new KeywayRow[] + { + new KeywayRow { Dmin=6, Dmax=8, KeySize="2×2", T1=1.2, T2=1.0, LRange="6~20" }, + new KeywayRow { Dmin=8, Dmax=10, KeySize="3×3", T1=1.8, T2=1.4, LRange="6~36" }, + new KeywayRow { Dmin=10, Dmax=12, KeySize="4×4", T1=2.5, T2=1.8, LRange="8~45" }, + new KeywayRow { Dmin=12, Dmax=17, KeySize="5×5", T1=3.0, T2=2.3, LRange="10~56" }, + new KeywayRow { Dmin=17, Dmax=22, KeySize="6×6", T1=3.5, T2=2.8, LRange="14~70" }, + new KeywayRow { Dmin=22, Dmax=30, KeySize="8×7", T1=4.0, T2=3.3, LRange="18~90" }, + new KeywayRow { Dmin=30, Dmax=38, KeySize="10×8", T1=5.0, T2=3.3, LRange="22~110" }, + new KeywayRow { Dmin=38, Dmax=44, KeySize="12×8", T1=5.0, T2=3.3, LRange="28~140" }, + new KeywayRow { Dmin=44, Dmax=50, KeySize="14×9", T1=5.5, T2=3.8, LRange="36~160" }, + new KeywayRow { Dmin=50, Dmax=58, KeySize="16×10", T1=6.0, T2=4.3, LRange="45~180" }, + new KeywayRow { Dmin=58, Dmax=65, KeySize="18×11", T1=7.0, T2=4.4, LRange="50~200" }, + new KeywayRow { Dmin=65, Dmax=75, KeySize="20×12", T1=7.5, T2=4.9, LRange="56~220" }, + new KeywayRow { Dmin=75, Dmax=85, KeySize="22×14", T1=9.0, T2=5.4, LRange="63~250" }, + new KeywayRow { Dmin=85, Dmax=95, KeySize="25×14", T1=9.0, T2=5.4, LRange="70~280" }, + new KeywayRow { Dmin=95, Dmax=110, KeySize="28×16", T1=10.0, T2=6.4, LRange="80~320" }, + new KeywayRow { Dmin=110, Dmax=130, KeySize="32×18", T1=11.0, T2=7.4, LRange="90~360" }, + new KeywayRow { Dmin=130, Dmax=150, KeySize="36×20", T1=12.0, T2=8.4, LRange="100~400" }, + new KeywayRow { Dmin=150, Dmax=170, KeySize="40×22", T1=13.0, T2=9.4, LRange="100~400" }, + new KeywayRow { Dmin=170, Dmax=200, KeySize="45×25", T1=15.0, T2=10.4, LRange="110~450" }, + new KeywayRow { Dmin=200, Dmax=230, KeySize="50×28", T1=17.0, T2=11.4, LRange="125~500" }, + new KeywayRow { Dmin=230, Dmax=260, KeySize="56×32", T1=20.0, T2=12.4, LRange="140~500" }, + new KeywayRow { Dmin=260, Dmax=290, KeySize="63×32", T1=20.0, T2=12.4, LRange="160~500" }, + new KeywayRow { Dmin=290, Dmax=330, KeySize="70×36", T1=22.0, T2=14.4, LRange="180~500" }, + new KeywayRow { Dmin=330, Dmax=380, KeySize="80×40", T1=25.0, T2=15.4, LRange="200~500" }, + new KeywayRow { Dmin=380, Dmax=440, KeySize="90×45", T1=28.0, T2=17.4, LRange="220~500" }, + new KeywayRow { Dmin=440, Dmax=500, KeySize="100×50", T1=31.0, T2=19.5, LRange="250~500" } + }; + + private static readonly double[] KeyLenSeries = + { 6, 8, 10, 12, 14, 16, 18, 20, 22, 25, 28, 32, 36, 40, 45, 50, 56, 63, 70, 80, 90, 100, + 110, 125, 140, 160, 180, 200, 220, 250, 280, 320, 360, 400, 450, 500 }; + + private static List QueryKeyway(double d) + { + List rows = new List(); + KeywayRow hit = null; + foreach (KeywayRow k in Keyways) + { + if (d > k.Dmin && d <= k.Dmax) { hit = k; break; } + } + if (hit == null) + { + rows.Add(new QueryRow { Item = "查询结果", Value = "超出数据范围", Std = "GB/T 1095", Remark = "支持轴径 6~500mm" }); + return rows; + } + rows.Add(new QueryRow { Item = "适用轴径 d", Value = ">" + hit.Dmin + "~" + hit.Dmax, Std = "GB/T 1095", Remark = "" }); + rows.Add(new QueryRow { Item = "键公称尺寸 b×h", Value = hit.KeySize, Std = "GB/T 1095", Remark = "" }); + rows.Add(new QueryRow { Item = "轴槽深 t1", Value = hit.T1.ToString("0.0"), Std = "GB/T 1095", Remark = "轴直径 - t1 = 槽底直径" }); + rows.Add(new QueryRow { Item = "轮毂槽深 t2", Value = hit.T2.ToString("0.0"), Std = "GB/T 1095", Remark = "孔直径 + t2 = 槽底直径" }); + rows.Add(new QueryRow { Item = "键长范围 L", Value = hit.LRange, Std = "GB/T 1095", Remark = "从标准长度系列选取" }); + + // 长度系列中落在范围内的 + double lo = 0, hi = 0; + string[] parts = hit.LRange.Split('~'); + double.TryParse(parts[0], out lo); + double.TryParse(parts[1], out hi); + List sel = new List(); + foreach (double len in KeyLenSeries) + { + if (len >= lo && len <= hi) sel.Add(len.ToString("0")); + } + rows.Add(new QueryRow { Item = "可用键长", Value = string.Join(", ", sel.ToArray()), Std = "GB/T 1095", Remark = DISCLAIMER }); + return rows; + } + + // ================= 卡簧槽 - 轴用弹性挡圈 (GB/T 894.1 A型) ================= + + private class CirclipRow + { + public double D; // 轴径 + public double OD; // 挡圈外径 + public double Depth; // 槽深 + public double SlotW; // 槽宽 + } + + private static readonly CirclipRow[] Circlips = new CirclipRow[] + { + new CirclipRow { D=3, OD=8.2, Depth=0.4, SlotW=0.6 }, + new CirclipRow { D=4, OD=9.6, Depth=0.4, SlotW=0.6 }, + new CirclipRow { D=5, OD=11.0, Depth=0.5, SlotW=0.7 }, + new CirclipRow { D=6, OD=13.0, Depth=0.7, SlotW=0.9 }, + new CirclipRow { D=7, OD=14.5, Depth=0.7, SlotW=0.9 }, + new CirclipRow { D=8, OD=16.0, Depth=0.7, SlotW=0.9 }, + new CirclipRow { D=9, OD=17.5, Depth=0.8, SlotW=1.0 }, + new CirclipRow { D=10, OD=19.0, Depth=0.8, SlotW=1.0 }, + new CirclipRow { D=11, OD=21.0, Depth=0.8, SlotW=1.0 }, + new CirclipRow { D=12, OD=22.5, Depth=0.9, SlotW=1.2 }, + new CirclipRow { D=13, OD=24.0, Depth=0.9, SlotW=1.2 }, + new CirclipRow { D=14, OD=25.5, Depth=0.9, SlotW=1.2 }, + new CirclipRow { D=15, OD=27.0, Depth=0.9, SlotW=1.2 }, + new CirclipRow { D=16, OD=28.5, Depth=1.0, SlotW=1.2 }, + new CirclipRow { D=17, OD=30.0, Depth=1.0, SlotW=1.2 }, + new CirclipRow { D=18, OD=32.0, Depth=1.1, SlotW=1.5 }, + new CirclipRow { D=19, OD=33.5, Depth=1.1, SlotW=1.5 }, + new CirclipRow { D=20, OD=35.0, Depth=1.1, SlotW=1.5 }, + new CirclipRow { D=21, OD=36.5, Depth=1.1, SlotW=1.5 }, + new CirclipRow { D=22, OD=38.0, Depth=1.1, SlotW=1.5 }, + new CirclipRow { D=24, OD=41.0, Depth=1.2, SlotW=1.5 }, + new CirclipRow { D=25, OD=42.5, Depth=1.2, SlotW=1.5 }, + new CirclipRow { D=26, OD=44.0, Depth=1.2, SlotW=1.5 }, + new CirclipRow { D=28, OD=47.0, Depth=1.3, SlotW=1.8 }, + new CirclipRow { D=30, OD=50.0, Depth=1.3, SlotW=1.8 }, + new CirclipRow { D=32, OD=53.0, Depth=1.4, SlotW=1.8 }, + new CirclipRow { D=34, OD=56.0, Depth=1.4, SlotW=1.8 }, + new CirclipRow { D=35, OD=57.5, Depth=1.4, SlotW=1.8 }, + new CirclipRow { D=36, OD=59.0, Depth=1.5, SlotW=2.0 }, + new CirclipRow { D=38, OD=62.0, Depth=1.5, SlotW=2.0 }, + new CirclipRow { D=40, OD=65.0, Depth=1.5, SlotW=2.0 }, + new CirclipRow { D=42, OD=68.0, Depth=1.6, SlotW=2.0 }, + new CirclipRow { D=45, OD=72.0, Depth=1.6, SlotW=2.0 }, + new CirclipRow { D=48, OD=76.0, Depth=1.6, SlotW=2.0 }, + new CirclipRow { D=50, OD=79.0, Depth=1.7, SlotW=2.5 } + }; + + private static List QueryCirclip(double d) + { + List rows = new List(); + CirclipRow hit = null; + foreach (CirclipRow c in Circlips) + { + if (Math.Abs(c.D - d) < 0.001) { hit = c; break; } + } + if (hit == null) + { + // 未精确匹配: 取最接近的相邻挡圈 + CirclipRow lower = null, upper = null; + foreach (CirclipRow c in Circlips) + { + if (c.D < d && (lower == null || c.D > lower.D)) lower = c; + if (c.D > d && (upper == null || c.D < upper.D)) upper = c; + } + if (lower == null && upper == null) + { + rows.Add(new QueryRow { Item = "查询结果", Value = "超出数据范围", Std = "GB/T 894.1", Remark = "支持轴径 3~50mm" }); + return rows; + } + rows.Add(new QueryRow { Item = "查询结果", Value = "非标准轴径, 请就近选用", Std = "GB/T 894.1", Remark = DISCLAIMER }); + if (lower != null) + rows.Add(new QueryRow { Item = "小于轴径的挡圈", Value = "d=" + lower.D + " 外径D=" + lower.OD + " 槽深t=" + lower.Depth + " 槽宽m=" + lower.SlotW, Std = "GB/T 894.1", Remark = "" }); + if (upper != null) + rows.Add(new QueryRow { Item = "大于轴径的挡圈", Value = "d=" + upper.D + " 外径D=" + upper.OD + " 槽深t=" + upper.Depth + " 槽宽m=" + upper.SlotW, Std = "GB/T 894.1", Remark = "" }); + return rows; + } + rows.Add(new QueryRow { Item = "轴径 d", Value = hit.D.ToString("0"), Std = "GB/T 894.1", Remark = "A 型轴用弹性挡圈" }); + rows.Add(new QueryRow { Item = "挡圈外径 D", Value = hit.OD.ToString("0.0"), Std = "GB/T 894.1", Remark = "" }); + rows.Add(new QueryRow { Item = "沟槽直径 d1", Value = (hit.D - 2 * hit.Depth).ToString("0.0"), Std = "GB/T 894.1", Remark = "槽底直径 = d - 2t" }); + rows.Add(new QueryRow { Item = "沟槽深度 t", Value = hit.Depth.ToString("0.0"), Std = "GB/T 894.1", Remark = "" }); + rows.Add(new QueryRow { Item = "沟槽宽度 m", Value = hit.SlotW.ToString("0.0"), Std = "GB/T 894.1", Remark = "" }); + rows.Add(new QueryRow { Item = "安装说明", Value = "挡圈装入沟槽后应贴合槽底", Std = "GB/T 894.1", Remark = DISCLAIMER }); + return rows; + } + + // ================= 统一入口 ================= + + public static List Query(QueryKind kind, double d) + { + switch (kind) + { + case QueryKind.Keyway: return QueryKeyway(d); + case QueryKind.Circlip: return QueryCirclip(d); + default: + return new List + { + new QueryRow { Item = "查询结果", Value = "模块开发中", Std = "GB/T 3452.1", Remark = "" } + }; + } + } + } +} diff --git a/AddIn/src/Core/FitForm.cs b/AddIn/src/Core/FitForm.cs new file mode 100644 index 0000000..c0ef6d9 --- /dev/null +++ b/AddIn/src/Core/FitForm.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; + +namespace DesignExpress.Core +{ + public class FitItem + { + public string Role; + public string Code; + public double DiaMM; + public int Load; + public int Purpose; + public string Verdict; + public string Recommend; + public string Basis; + } + + // 公差配合检查对话框 + public class FitForm : Form + { + public List Items = new List(); + + private ComboBox cboRole, cboLoad, cboPurpose; + private TextBox txtDia, txtCode; + private Label lblResult; + private ListBox lst; + private Button btnCheck, btnDone; + private double _diaMM; + private string _faceName; + + public FitForm(double diaMM, string faceName) + { + _diaMM = diaMM; + _faceName = faceName; + BuildUI(); + } + + private void BuildUI() + { + Text = "公差配合检查 - 设计标注检查工具"; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; + MinimizeBox = false; + StartPosition = FormStartPosition.CenterScreen; + ClientSize = new Size(640, 480); + + int y = 12; + Label lblFace = new Label(); + lblFace.Text = "配合面: " + _faceName + " 直径: " + _diaMM.ToString("0.0") + " mm"; + lblFace.Location = new Point(12, y); lblFace.AutoSize = true; lblFace.Font = new Font(lblFace.Font, FontStyle.Bold); + Controls.Add(lblFace); + y += 28; + + Label lblRole = new Label(); lblRole.Text = "角色:"; lblRole.Location = new Point(12, y + 3); lblRole.AutoSize = true; + Controls.Add(lblRole); + cboRole = new ComboBox(); + cboRole.DropDownStyle = ComboBoxStyle.DropDownList; + cboRole.Items.AddRange(new object[] { "轴承-轴颈(旋转)", "轴承-轴颈(固定)", "轴承-座孔(外圈固定)", "轴承-座孔(外圈旋转)", "一般孔轴配合" }); + cboRole.SelectedIndex = 0; + cboRole.Location = new Point(90, y); cboRole.Width = 220; + Controls.Add(cboRole); + cboRole.SelectedIndexChanged += delegate { RoleChanged(); }; + y += 30; + + Label lblLoad = new Label(); lblLoad.Text = "载荷:"; lblLoad.Location = new Point(12, y + 3); lblLoad.AutoSize = true; + Controls.Add(lblLoad); + cboLoad = new ComboBox(); + cboLoad.DropDownStyle = ComboBoxStyle.DropDownList; + cboLoad.Items.AddRange(new object[] { "轻载 (P<=0.07C)", "中载 (0.07C0.15C)", "特重/冲击载荷" }); + cboLoad.SelectedIndex = 1; + cboLoad.Location = new Point(90, y); cboLoad.Width = 220; + Controls.Add(cboLoad); + y += 30; + + Label lblPurpose = new Label(); lblPurpose.Text = "用途:"; lblPurpose.Location = new Point(12, y + 3); lblPurpose.AutoSize = true; + Controls.Add(lblPurpose); + cboPurpose = new ComboBox(); + cboPurpose.DropDownStyle = ComboBoxStyle.DropDownList; + cboPurpose.Items.AddRange(new object[] { "滑动(有相对运动)", "定位(固定位置, 可拆)", "传动(传递扭矩)", "过盈连接(不拆卸)" }); + cboPurpose.SelectedIndex = 1; + cboPurpose.Location = new Point(90, y); cboPurpose.Width = 220; + Controls.Add(cboPurpose); + y += 30; + + Label lblDia = new Label(); lblDia.Text = "直径(mm):"; lblDia.Location = new Point(12, y + 3); lblDia.AutoSize = true; + Controls.Add(lblDia); + txtDia = new TextBox(); txtDia.Text = _diaMM.ToString("0.0"); txtDia.Location = new Point(90, y); txtDia.Width = 100; + Controls.Add(txtDia); + y += 30; + + Label lblCode = new Label(); lblCode.Text = "配合代码:"; lblCode.Location = new Point(12, y + 3); lblCode.AutoSize = true; + Controls.Add(lblCode); + txtCode = new TextBox(); txtCode.Text = "k6"; txtCode.Location = new Point(90, y); txtCode.Width = 140; + Controls.Add(txtCode); + Label lblCodeHint = new Label(); + lblCodeHint.Text = "轴承轴颈: 如 k6/js6/m6; 座孔: 如 H7/J7/K7; 一般配合: 如 H7/k6"; + lblCodeHint.Location = new Point(240, y + 3); lblCodeHint.AutoSize = true; + Controls.Add(lblCodeHint); + y += 34; + + btnCheck = new Button(); btnCheck.Text = "检查并加入列表"; btnCheck.Location = new Point(12, y); btnCheck.Width = 130; + btnCheck.Click += delegate { DoCheck(); }; + Controls.Add(btnCheck); + btnDone = new Button(); btnDone.Text = "完成 (生成报告)"; btnDone.Location = new Point(150, y); btnDone.Width = 130; + btnDone.Click += delegate { DialogResult = DialogResult.OK; Close(); }; + Controls.Add(btnDone); + y += 36; + + lblResult = new Label(); + lblResult.Text = ""; + lblResult.Location = new Point(12, y); lblResult.AutoSize = true; lblResult.MaximumSize = new Size(610, 0); + Controls.Add(lblResult); + y += 60; + + lst = new ListBox(); + lst.Location = new Point(12, y); lst.Width = 616; lst.Height = 140; + Controls.Add(lst); + + RoleChanged(); + } + + private void RoleChanged() + { + bool bearing = cboRole.SelectedIndex >= 0 && cboRole.SelectedIndex <= 3; + cboLoad.Enabled = bearing; + cboPurpose.Enabled = !bearing; + txtCode.Text = bearing ? "k6" : "H7/k6"; + } + + private void DoCheck() + { + double dia; + double.TryParse(txtDia.Text, out dia); + if (dia <= 0) { MessageBox.Show("请输入有效直径"); return; } + string code = txtCode.Text.Trim(); + if (code == "") { MessageBox.Show("请输入配合代码"); return; } + + string verdict, recommend, basis; + int load = cboLoad.SelectedIndex + 1; + int purpose = cboPurpose.SelectedIndex + 1; + + if (cboRole.SelectedIndex == 4) + verdict = KB.GeneralFitJudge(code, purpose, out recommend, out basis); + else + verdict = KB.FitJudge(code, (string)cboRole.SelectedItem, load, out recommend, out basis); + + FitItem it = new FitItem(); + it.Role = (string)cboRole.SelectedItem; + it.Code = code; + it.DiaMM = dia; + it.Load = load; + it.Purpose = purpose; + it.Verdict = verdict; + it.Recommend = recommend; + it.Basis = basis; + Items.Add(it); + + lblResult.Text = "判定: " + verdict + " 推荐: " + recommend + "\r\n" + basis; + lblResult.ForeColor = verdict == "合理" ? Color.Green : (verdict == "可接受" ? Color.DarkOrange : Color.Red); + lst.Items.Add(dia.ToString("0.0") + "mm " + it.Role + " 代码 " + code + " -> " + verdict + " (推荐 " + recommend + ")"); + txtCode.Clear(); + } + } + + // 通用单行输入对话框 + public class SimpleInput : Form + { + private TextBox txt; + public string Value { get { return txt.Text; } } + + public SimpleInput(string title, string prompt, string def) + { + Text = title; + FormBorderStyle = FormBorderStyle.FixedDialog; + MaximizeBox = false; MinimizeBox = false; + StartPosition = FormStartPosition.CenterScreen; + ClientSize = new Size(420, 110); + + Label lbl = new Label(); + lbl.Text = prompt; + lbl.Location = new Point(12, 12); lbl.AutoSize = true; lbl.MaximumSize = new Size(390, 40); + Controls.Add(lbl); + + txt = new TextBox(); + txt.Text = def; + txt.Location = new Point(12, 50); txt.Width = 396; + Controls.Add(txt); + + Button ok = new Button(); ok.Text = "确定"; ok.DialogResult = DialogResult.OK; + ok.Location = new Point(220, 78); ok.Width = 90; + Controls.Add(ok); + Button cancel = new Button(); cancel.Text = "取消"; cancel.DialogResult = DialogResult.Cancel; + cancel.Location = new Point(318, 78); cancel.Width = 90; + Controls.Add(cancel); + } + } +} diff --git a/AddIn/src/Core/ICadHost.cs b/AddIn/src/Core/ICadHost.cs new file mode 100644 index 0000000..a2ed8a6 --- /dev/null +++ b/AddIn/src/Core/ICadHost.cs @@ -0,0 +1,17 @@ +using System; + +namespace DesignExpress.Core +{ + // CAD 宿主抽象接口: Core 不依赖任何具体 CAD 系统, + // 由各 CAD 的 Link 适配器(Inventor/Creo/SolidWorks)实现, + // 架构类似 COMSOL LiveLink + public interface ICadHost + { + string AppName { get; } + object GetActiveDocument(); + string GetDocType(object doc); + DocSnapshot Capture(object doc); + double PickCylinderDiameterMM(out string faceName); + void SetStatusText(string text); + } +} diff --git a/AddIn/src/Core/KnowledgeBase.cs b/AddIn/src/Core/KnowledgeBase.cs new file mode 100644 index 0000000..1c85ed9 --- /dev/null +++ b/AddIn/src/Core/KnowledgeBase.cs @@ -0,0 +1,235 @@ +using System; + +namespace DesignExpress.Core +{ + // 知识库: 机械设计手册常用数据(简化参考版) + // 正式生产请以最新版《机械设计手册》及 GB/T 275, GB/T 1800, GB/T 1801 等标准为准 + public static class KB + { + public static int SegIndex(double dMM) + { + if (dMM <= 18) return 1; + if (dMM <= 30) return 2; + if (dMM <= 50) return 3; + if (dMM <= 80) return 4; + if (dMM <= 120) return 5; + if (dMM <= 180) return 6; + if (dMM <= 250) return 7; + if (dMM <= 315) return 8; + if (dMM <= 400) return 9; + if (dMM <= 500) return 10; + return -1; + } + + public static string SegName(int seg) + { + switch (seg) + { + case 1: return "<=18"; + case 2: return "18~30"; + case 3: return "30~50"; + case 4: return "50~80"; + case 5: return "80~120"; + case 6: return "120~180"; + case 7: return "180~250"; + case 8: return "250~315"; + case 9: return "315~400"; + case 10: return "400~500"; + default: return "?"; + } + } + + private static readonly double[,] IT = new double[11, 10] + { + { 0.8, 1, 1.2, 1.5, 2, 2.5, 3.5, 4, 4.5, 5 }, + { 1.2, 1.5, 2, 2.5, 3, 4, 4.5, 5, 6, 7 }, + { 2, 2.5, 3, 4, 5, 6, 7, 8, 9, 10 }, + { 3, 4, 5, 6, 8, 10, 12, 13, 15, 16 }, + { 4, 5, 7, 8, 10, 12, 14, 16, 18, 20 }, + { 6, 8, 10, 12, 15, 18, 20, 23, 25, 27 }, + { 10, 12, 16, 19, 22, 25, 28, 32, 36, 40 }, + { 14, 18, 22, 26, 30, 35, 40, 46, 52, 57 }, + { 25, 30, 36, 43, 52, 62, 72, 81, 89, 97 }, + { 40, 48, 58, 70, 84, 100, 115, 130, 140, 155 }, + { 60, 75, 90, 105, 130, 150, 175, 195, 210, 230 } + }; + + public static double ItTol(int itNo, int seg) + { + if (itNo >= 1 && itNo <= 11 && seg >= 1 && seg <= 10) return IT[itNo - 1, seg - 1]; + return -1; + } + + private static string LoadName(int load) + { + switch (load) + { + case 1: return "轻载(P<=0.07C)"; + case 2: return "中载(0.07C0.15C)"; + default: return "特重/冲击载荷"; + } + } + + public static void ParseFit(string code, out string hole, out string shaft) + { + hole = ""; shaft = ""; + string c = (code ?? "").Trim().ToUpper(); + int p = c.IndexOf('/'); + if (p > 0) + { + hole = c.Substring(0, p).Trim(); + shaft = c.Substring(p + 1).Trim(); + } + else hole = c; + } + + public static string FitJudge(string code, string role, int load, out string recommend, out string basis) + { + recommend = ""; + string c = (code ?? "").Trim().ToUpper(); + string rec = "", acc = ""; + basis = "机械设计手册 第2卷 / GB/T 275-2015 滚动轴承配合"; + switch (role) + { + case "轴承-轴颈(旋转)": + switch (load) + { + case 1: rec = "js6"; acc = "k6"; break; + case 2: rec = "k6"; acc = "m6"; break; + case 3: rec = "m6"; acc = "n6"; break; + default: rec = "n6"; acc = "p6,r6"; break; + } + basis += ": 内圈与旋转轴, " + LoadName(load) + ", 推荐 " + rec + " (可选 " + acc + ")"; + break; + case "轴承-轴颈(固定)": + rec = "h6"; acc = "g6,j6"; + basis += ": 内圈与不旋转轴, 推荐 " + rec + " (可选 " + acc + ")"; + break; + case "轴承-座孔(外圈固定)": + rec = "H7"; acc = "J7,H8"; + basis += ": 外圈与壳体孔(固定载荷), 推荐 " + rec + " (可选 " + acc + ")"; + break; + case "轴承-座孔(外圈旋转)": + rec = "K7"; acc = "M7,N7"; + basis += ": 外圈与壳体孔(旋转载荷), 推荐 " + rec + " (可选 " + acc + ")"; + break; + default: + basis = "未知角色"; + return "信息"; + } + recommend = rec; + if (c == "") return "信息"; + if (c == rec) return "合理"; + if (("," + acc + ",").Contains("," + c + ",")) return "可接受"; + return "不合理"; + } + + public static string GeneralFitJudge(string code, int purpose, out string recommend, out string basis) + { + string hole, shaft; + ParseFit(code, out hole, out shaft); + string c = (code ?? "").Trim().ToUpper(); + string fitType, fitUse; + LookupFitTable(hole + "/" + shaft, out fitType, out fitUse); + string rec = "", acc = "", purposeName = ""; + basis = "机械设计手册 第2卷 孔轴公差与配合 (GB/T 1801)"; + switch (purpose) + { + case 1: purposeName = "滑动配合"; rec = "H8/f7"; acc = "H9/d9,H7/g6"; break; + case 2: purposeName = "定位配合"; rec = "H7/js6"; acc = "H7/h6,H7/k6"; break; + case 3: purposeName = "传动配合"; rec = "H7/n6"; acc = "H7/p6"; break; + default: purposeName = "过盈连接"; rec = "H7/s6"; acc = "H7/r6,H7/u6"; break; + } + recommend = rec; + basis += ": " + purposeName + ", 推荐 " + rec + " (可选 " + acc + ")"; + if (fitType == "") + { + basis += "; 当前代码 " + c + " 不在常用配合表中, 请核对"; + return "信息"; + } + string needType = "间隙"; + if (purpose == 2) needType = "间隙/过渡"; + if (purpose == 3) needType = "过渡/过盈"; + if (purpose == 4) needType = "过盈"; + if (c == rec) return "合理"; + if (("," + acc + ",").Contains("," + c + ",")) return "可接受"; + if (needType.Contains(fitType)) + { + basis += "; 当前 " + c + " 为" + fitType + "配合(" + fitUse + "), 类型方向正确但非优先推荐"; + return "可接受"; + } + basis += "; 当前 " + c + " 为" + fitType + "配合(" + fitUse + "), 与用途不符"; + return "不合理"; + } + + public static void LookupFitTable(string code, out string fitType, out string fitUse) + { + fitType = ""; fitUse = ""; + switch ((code ?? "").Trim().ToUpper()) + { + case "H11/C11": fitType = "间隙"; fitUse = "低精度/粗糙滑动, 吊架等"; break; + case "H9/D9": fitType = "间隙"; fitUse = "较松滑动, 联轴器导向"; break; + case "H8/F7": fitType = "间隙"; fitUse = "一般滑动, 机床导轨低速"; break; + case "H7/G6": fitType = "间隙"; fitUse = "精密滑动, 导向件/移动齿轮"; break; + case "H7/H6": fitType = "间隙"; fitUse = "小间隙定位, 挂轮/联轴器"; break; + case "H7/JS6": fitType = "过渡"; fitUse = "定位, 手轮/隔套"; break; + case "H7/K6": fitType = "过渡"; fitUse = "精密定位, 隔套/滚动轴承内圈"; break; + case "H7/N6": fitType = "过渡"; fitUse = "较紧定位/传动, 齿轮与轴/连杆"; break; + case "H7/P6": fitType = "过盈"; fitUse = "小过盈, 定位兼传小扭矩"; break; + case "H7/R6": fitType = "过盈"; fitUse = "一般过盈, 受冲击装配"; break; + case "H7/S6": fitType = "过盈"; fitUse = "液压装配过盈"; break; + case "H7/U6": fitType = "过盈"; fitUse = "大过盈, 重型压装"; break; + } + } + + public static string RaSuggest(string role) + { + if (role.StartsWith("轴承-轴颈")) return "0.4~0.8 um (磨削)"; + if (role.StartsWith("轴承-座孔")) return "0.8~1.6 um (精镗/铰)"; + if (role == "密封") return "动密封面 0.4~0.8; 静密封面 0.8~1.6 um"; + if (role == "GMP") return "物料接触面 Ra<=0.8 (电解抛光 0.4)"; + return "一般配合面 1.6~3.2 um"; + } + + public static string GmpMaterialJudge(string matName, out string rec, out string basis) + { + string m = (matName ?? "").Trim().ToUpper(); + basis = "GMP 设备设计规范: 与物料接触部件应使用耐腐蚀、易清洁材料"; + rec = ""; + if (m == "") { rec = "应填写材料并选用卫生级不锈钢"; return "未通过"; } + if (m.Contains("316L") || m.Contains("904L") || m.Contains("2205")) { rec = m + " 符合 GMP 物料接触要求(首选)"; return "合理"; } + if (m.Contains("316")) { rec = m + " 符合 GMP 物料接触要求"; return "合理"; } + if (m.Contains("钛") || m.Contains("哈氏")) { rec = m + " 耐腐蚀, 符合 GMP 要求"; return "合理"; } + if (m.Contains("304") || m.Contains("不锈钢")) { rec = "304 级可接受, 接触面建议电解抛光; 长期接触含氯介质建议 316L"; return "可接受"; } + if (m.Contains("PTFE") || m.Contains("聚四氟") || m.Contains("PFA") || m.Contains("PVDF") || + m.Contains("EPDM") || m.Contains("三元乙丙") || m.Contains("FKM") || m.Contains("氟胶") || + m.Contains("硅胶") || m.Contains("PEEK") || m.Contains("聚丙烯") || m.Contains("玻璃")) + { rec = m + " 可用于密封/非金属部件, 需确认符合 FDA/药典要求及温度等级"; return "可接受"; } + if (m.Contains("铝") || m == "AL") { rec = "铝合金不可直接接触物料, 需阳极氧化/隔离处理"; return "警告"; } + if (m.Contains("钢") || m.Contains("铁")) { rec = "碳钢/普通钢不可接触物料, 需改不锈钢或加涂层隔离"; return "未通过"; } + if (m.Contains("铜") || m.Contains("锌")) { rec = "铜/锌及其合金不宜直接接触药品物料"; return "未通过"; } + rec = "无法识别的材料, 请人工确认"; + return "警告"; + } + + private static readonly string[] SealKeys = { "O型圈", "O形圈", "O-RING", "密封圈", "密封垫", "密封条", "密封件", "SEAL", "GASKET", "垫片", "EPDM", "三元乙丙", "氟胶", "硅胶圈" }; + private static readonly string[] StdKeys = { "螺栓", "螺钉", "螺母", "垫圈", "垫片", "销", "键", "轴承", "电机", "减速机", "气缸", "阀", "泵", "传感器", "PLC", "变频器", "伺服", "型材", "钢管", "法兰", "接头", "卡箍", "视镜", "密封圈", "联轴器", "导向轴", "导轨", "弹簧", "内六角", "GB", "TB", "ISO", "DIN", "JB" }; + + public static bool IsSealPartName(string nm) + { + string u = (nm ?? "").ToUpper(); + foreach (string k in SealKeys) + if (u.Contains(k.ToUpper())) return true; + return false; + } + + public static bool IsStdPartName(string nm) + { + string u = (nm ?? "").ToUpper(); + foreach (string k in StdKeys) + if (u.Contains(k.ToUpper())) return true; + return false; + } + } +} diff --git a/AddIn/src/Core/MainForm.cs b/AddIn/src/Core/MainForm.cs new file mode 100644 index 0000000..36e3ee1 --- /dev/null +++ b/AddIn/src/Core/MainForm.cs @@ -0,0 +1,190 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace DesignExpress.Core +{ + // 检查主窗口: TableLayoutPanel 百分比布局, 左树 25% + 右表 75%, 无固定尺寸不遮罩 + public class MainForm : Form + { + private DataGridView grid; + private TreeView tree; + private Label lblSummary; + private StatusStrip status; + private string mStage = ""; + + public MainForm() + { + BuildUI(); + } + + private void BuildUI() + { + Text = "Design Express Res - 设计标注检查"; + StartPosition = FormStartPosition.CenterScreen; + WindowState = FormWindowState.Normal; + Rectangle wa = Screen.PrimaryScreen.WorkingArea; + Size = new Size(wa.Width / 2, wa.Height / 2); + MinimumSize = new Size(720, 460); + + // 主布局: 两列百分比 25% / 75% + TableLayoutPanel master = new TableLayoutPanel(); + master.Dock = DockStyle.Fill; + master.ColumnCount = 2; + master.RowCount = 1; + master.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 25F)); + master.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 75F)); + master.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + master.Margin = new Padding(0); + master.Padding = new Padding(0); + + // 左: 检查项树 + tree = new TreeView(); + tree.Dock = DockStyle.Fill; + tree.FullRowSelect = true; + tree.ShowLines = true; + tree.ShowPlusMinus = true; + tree.HideSelection = false; + tree.BorderStyle = BorderStyle.FixedSingle; + FillTree(); + master.Controls.Add(tree, 0, 0); + + // 右: 汇总 + 结果表格 + Panel right = new Panel(); + right.Dock = DockStyle.Fill; + right.Margin = new Padding(0); + + grid = new DataGridView(); + grid.Dock = DockStyle.Fill; + grid.ReadOnly = true; + grid.AllowUserToAddRows = false; + grid.AllowUserToDeleteRows = false; + grid.SelectionMode = DataGridViewSelectionMode.FullRowSelect; + grid.MultiSelect = false; + grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells; + grid.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; + grid.BorderStyle = BorderStyle.None; + grid.Columns.Add("CheckID", "编号"); + grid.Columns.Add("Name", "检查项"); + grid.Columns.Add("Obj", "对象"); + grid.Columns.Add("Result", "结果"); + grid.Columns.Add("Note", "说明"); + grid.Columns.Add("Basis", "判定依据"); + grid.Columns["Note"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; + grid.Columns["Basis"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill; + right.Controls.Add(grid); + + lblSummary = new Label(); + lblSummary.Dock = DockStyle.Top; + lblSummary.AutoSize = true; + lblSummary.TextAlign = ContentAlignment.MiddleLeft; + lblSummary.Padding = new Padding(8, 6, 0, 6); + lblSummary.Font = new Font("Microsoft YaHei", 9.5f); + lblSummary.Text = "就绪: 请先打开文档, 然后在功能区点击检查按钮"; + right.Controls.Add(lblSummary); + + master.Controls.Add(right, 1, 0); + + // Dock 顺序: Fill 先 Add, Bottom 后 Add + Controls.Add(master); + status = new StatusStrip(); + Controls.Add(status); + + Shown += delegate { LoadCurrentResults(); }; + } + + private void FillTree() + { + tree.Nodes.Clear(); + TreeNode root = new TreeNode("检查项清单"); + TreeNode part = new TreeNode("零件阶段 (P)"); + part.Nodes.Add("P01~P07 保存状态/代号/名称/材料/设计者/审核者/修订号"); + part.Nodes.Add("P08 模型标注统计/公差覆盖/形位公差/粗糙度"); + part.Nodes.Add("P09 公差配合检查 (GB/T 275/GB/T 1801)"); + part.Nodes.Add("P10~P12 配合面形位公差/粗糙度/公差等级"); + part.Nodes.Add("P13 螺纹孔检查"); + part.Nodes.Add("P14 物料接触 + GMP 专项"); + part.Nodes.Add("P15 焊接标注"); + part.Nodes.Add("P16 表面处理/防锈"); + part.Nodes.Add("P17 结构强度/仿真指引"); + root.Nodes.Add(part); + + TreeNode asm = new TreeNode("装配阶段 (A)"); + asm.Nodes.Add("A01 文档保存状态"); + asm.Nodes.Add("A02 GMP 材料合规"); + asm.Nodes.Add("A03/A04 密封件存在性 + 密封设计提示"); + asm.Nodes.Add("A05 清洁性设计要求"); + asm.Nodes.Add("A06 GMP 粗糙度标注"); + asm.Nodes.Add("A07 静态干涉检查"); + asm.Nodes.Add("A08 运动干涉(动态)指引"); + asm.Nodes.Add("A09 外购件优先统计"); + asm.Nodes.Add("A10 电气选型/西门子联网"); + asm.Nodes.Add("A11 施工/公共动力系统"); + asm.Nodes.Add("A12 整机强度/仿真指引"); + root.Nodes.Add(asm); + + TreeNode drw = new TreeNode("工程图阶段 (D)"); + drw.Nodes.Add("D00~D09 保存/图框/标题栏/空白/更新/剖视/越界/尺寸/公差/文本"); + drw.Nodes.Add("D10~D17 形位公差/基准/粗糙度/焊接/孔标注/中心线/越界/图幅"); + drw.Nodes.Add("D18 物料接触类别声明"); + drw.Nodes.Add("D19 焊接技术要求(双面焊/打磨)"); + drw.Nodes.Add("D20 死角/夹角要求(R10/135度)"); + drw.Nodes.Add("D21~D25 公差/轴承位/螺纹孔/镀镍/平面度"); + drw.Nodes.Add("D26~D32 角度管/治具/辅助件/外购/测试/装配/防锈"); + drw.Nodes.Add("D33~D39 施工动力/配孔/吊顶/电气/西门子/强度/夹角"); + root.Nodes.Add(drw); + + tree.Nodes.Add(root); + root.ExpandAll(); + } + + public void LoadCurrentResults() + { + grid.Rows.Clear(); + int pass = 0, warn = 0, fail = 0, info = 0; + foreach (CheckResult r in AppState.Results) + { + int idx = grid.Rows.Add(r.CheckID, r.Name, r.Obj, r.Result, r.Note, r.Basis); + DataGridViewRow row = grid.Rows[idx]; + switch (r.Result) + { + case "通过": row.DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(0xE8, 0xFF, 0xE8); break; + case "警告": row.DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(0xFF, 0xFF, 0xE0); break; + case "未通过": row.DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(0xFF, 0xE0, 0xE0); break; + default: row.DefaultCellStyle.BackColor = System.Drawing.Color.FromArgb(0xF0, 0xF0, 0xF0); break; + } + switch (r.Result) + { + case "通过": pass++; break; + case "警告": warn++; break; + case "未通过": fail++; break; + default: info++; break; + } + } + lblSummary.Text = string.Format("检查项: {0} 通过: {1} 警告: {2} 未通过: {3} 信息: {4}", + AppState.Results.Count, pass, warn, fail, info); + status.Items.Clear(); + status.Items.Add("当前阶段: " + (mStage == "" ? "全部" : mStage)); + } + + public void RunAndLoad(string stage, Action run) + { + try + { + Cursor = Cursors.WaitCursor; + AppState.Results.Clear(); + mStage = stage; + run(); + LoadCurrentResults(); + } + catch (Exception ex) + { + MessageBox.Show("检查出错: " + ex.Message, "Design Express Res", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + finally + { + Cursor = Cursors.Default; + } + } + } +} diff --git a/AddIn/src/Core/QueryForm.cs b/AddIn/src/Core/QueryForm.cs new file mode 100644 index 0000000..5db76a8 --- /dev/null +++ b/AddIn/src/Core/QueryForm.cs @@ -0,0 +1,264 @@ +using System; +using System.Drawing; +using System.Windows.Forms; + +namespace DesignExpress.Core +{ + // 设计数据查询窗口: 一个按钮对应一个功能页, 无切换菜单 + // TableLayoutPanel 百分比布局: 头部 AutoSize(自适应文字高度), 左输入 35% / 右结果 65% + public class QueryForm : Form + { + private static QueryForm mForm; + + private class ModuleDef + { + public string Title; // 窗口标题 + public string SubTitle; // 页头主标题 + public string Desc; // 功能说明 + } + + private static readonly ModuleDef[] Modules = new ModuleDef[] + { + new ModuleDef { Title = "卡簧槽查询 (GB/T 894)", SubTitle = "轴用/孔用弹性挡圈槽尺寸查询", Desc = "输入轴径或孔径, 查询挡圈型号、槽径、槽宽、槽深等设计尺寸" }, + new ModuleDef { Title = "键槽查询 (GB/T 1095)", SubTitle = "平键键槽尺寸查询", Desc = "输入轴径, 查询键的公称尺寸(b×h×L)、轴槽深、轮毂槽深、键槽长度系列等" }, + new ModuleDef { Title = "O 型密封圈查询 (GB/T 3452.1)", SubTitle = "O 型密封圈设计", Desc = "输入密封轴径/孔径, 查询圈规格、沟槽尺寸(径向/轴向密封)、压缩量、挡圈配合等" }, + new ModuleDef { Title = "轴承挡圈查询", SubTitle = "轴承挡圈设计", Desc = "输入轴承外径/内径, 查询挡圈规格、挡边高度、安装槽尺寸、轴向定位设计等" } + }; + + private Label lblTitle; + private Label lblDesc; + private FlowLayoutPanel inFlow; + private DataGridView grid; + private Label outHint; + + public QueryForm(int moduleIndex) + { + BuildUI(moduleIndex); + } + + public static void ShowQuery(int moduleIndex) + { + if (moduleIndex < 0 || moduleIndex >= Modules.Length) return; + if (mForm == null || mForm.IsDisposed) + mForm = new QueryForm(moduleIndex); + mForm.ApplyModule(moduleIndex); + mForm.Show(); + mForm.Activate(); + } + + private void BuildUI(int moduleIndex) + { + StartPosition = FormStartPosition.CenterScreen; + WindowState = FormWindowState.Normal; + Rectangle wa = Screen.PrimaryScreen.WorkingArea; + Size = new Size(wa.Width / 2, wa.Height / 2); + MinimumSize = new Size(700, 460); + + // 主布局: 2 行(头部 AutoSize + 主体 100%), 主体 2 列(左 35% / 右 65%) + TableLayoutPanel master = new TableLayoutPanel(); + master.Dock = DockStyle.Fill; + master.ColumnCount = 2; + master.RowCount = 2; + master.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 35F)); + master.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 65F)); + master.RowStyles.Add(new RowStyle(SizeType.AutoSize)); + master.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); + master.Margin = new Padding(0); + master.Padding = new Padding(0); + + // 头部标题区: 占满两列, AutoSize 高度, 文字永远不会被裁剪 + FlowLayoutPanel headFlow = new FlowLayoutPanel(); + headFlow.AutoSize = true; + headFlow.Anchor = AnchorStyles.Left | AnchorStyles.Top | AnchorStyles.Right; + headFlow.FlowDirection = FlowDirection.TopDown; + headFlow.WrapContents = false; + headFlow.Padding = new Padding(10, 8, 0, 2); + headFlow.Margin = new Padding(0); + + lblTitle = new Label(); + lblTitle.Font = new Font("Microsoft YaHei", 12.5f, FontStyle.Bold); + lblTitle.AutoSize = true; + lblTitle.Margin = new Padding(0, 0, 0, 2); + headFlow.Controls.Add(lblTitle); + + lblDesc = new Label(); + lblDesc.Font = new Font("Microsoft YaHei", 9f); + lblDesc.ForeColor = System.Drawing.Color.Gray; + lblDesc.AutoSize = true; + lblDesc.Margin = new Padding(0); + headFlow.Controls.Add(lblDesc); + + master.Controls.Add(headFlow, 0, 0); + master.SetColumnSpan(headFlow, 2); + + // 左: 输入参数 (35%) + GroupBox input = new GroupBox(); + input.Dock = DockStyle.Fill; + input.Text = "输入参数"; + input.Font = new Font("Microsoft YaHei", 10f); + input.Margin = new Padding(6, 3, 3, 6); + + inFlow = new FlowLayoutPanel(); + inFlow.Dock = DockStyle.Fill; + inFlow.FlowDirection = FlowDirection.TopDown; + inFlow.WrapContents = false; + inFlow.Padding = new Padding(10, 8, 10, 8); + inFlow.AutoScroll = true; + input.Controls.Add(inFlow); + master.Controls.Add(input, 0, 1); + + // 右: 查询结果 (65%) + GroupBox result = new GroupBox(); + result.Dock = DockStyle.Fill; + result.Text = "查询结果"; + result.Font = new Font("Microsoft YaHei", 10f); + result.Margin = new Padding(3, 3, 6, 6); + + grid = new DataGridView(); + grid.Dock = DockStyle.Fill; + grid.ReadOnly = true; + grid.AllowUserToAddRows = false; + grid.AllowUserToDeleteRows = false; + grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; + grid.RowHeadersVisible = false; + grid.BackgroundColor = Color.White; + grid.BorderStyle = BorderStyle.None; + grid.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; + grid.Columns.Add("Item", "项目"); + grid.Columns.Add("Value", "数值"); + grid.Columns.Add("Std", "标准"); + grid.Columns.Add("Remark", "备注"); + result.Controls.Add(grid); + + outHint = new Label(); + outHint.Dock = DockStyle.Top; + outHint.AutoSize = true; + outHint.TextAlign = ContentAlignment.MiddleLeft; + outHint.Padding = new Padding(6, 4, 0, 4); + outHint.ForeColor = System.Drawing.Color.Gray; + outHint.Font = new Font("Microsoft YaHei", 9.5f); + result.Controls.Add(outHint); + + master.Controls.Add(result, 1, 1); + + Controls.Add(master); + + ApplyModule(moduleIndex); + } + + // 切换到指定功能模块(无菜单, 直接换内容) + private void ApplyModule(int moduleIndex) + { + ModuleDef m = Modules[moduleIndex]; + Text = "Design Express Res - " + m.Title; + lblTitle.Text = m.SubTitle; + lblDesc.Text = m.Desc; + BuildModuleUI((QueryKind)moduleIndex); + } + + private void BuildModuleUI(QueryKind kind) + { + inFlow.Controls.Clear(); + + if (kind == QueryKind.Keyway || kind == QueryKind.Circlip) + { + Label hint = new Label(); + hint.Text = "输入轴径, 点击查询获取设计尺寸"; + hint.Font = new Font("Microsoft YaHei", 9.5f); + hint.ForeColor = System.Drawing.Color.Gray; + hint.AutoSize = true; + hint.Margin = new Padding(0, 0, 0, 6); + inFlow.Controls.Add(hint); + + TextBox txtDia = new TextBox(); + txtDia.Width = 110; + txtDia.Font = new Font("Microsoft YaHei", 10f); + txtDia.Text = "30"; + + Button btnQuery = new Button(); + btnQuery.Text = "查询"; + btnQuery.Width = 70; + btnQuery.Height = 26; + btnQuery.Font = new Font("Microsoft YaHei", 9.5f); + + AddInputRow("轴径 d (mm):", txtDia, btnQuery); + + Label note = new Label(); + note.Text = "数据为常用参考值, 正式设计请核对最新版标准"; + note.Font = new Font("Microsoft YaHei", 8.5f); + note.ForeColor = System.Drawing.Color.Gray; + note.AutoSize = true; + note.Margin = new Padding(0, 10, 0, 0); + inFlow.Controls.Add(note); + + btnQuery.Click += delegate { DoQuery(kind, txtDia.Text); }; + txtDia.KeyDown += delegate(object s, KeyEventArgs e) + { + if (e.KeyCode == Keys.Enter) DoQuery(kind, txtDia.Text); + }; + } + else + { + Label hint1 = new Label(); + hint1.Text = "参数输入区(开发中)"; + hint1.Font = new Font("Microsoft YaHei", 10f, FontStyle.Bold); + hint1.AutoSize = true; + hint1.Margin = new Padding(0, 0, 0, 6); + inFlow.Controls.Add(hint1); + + foreach (string t in new string[] { + "后续在此放置:", + "直径/尺寸输入框", + "标准选择(系列/等级)", + "查询按钮" }) + { + Label l = new Label(); + l.Text = t; + l.AutoSize = true; + l.ForeColor = System.Drawing.Color.Gray; + l.Margin = new Padding(0, 0, 0, 2); + inFlow.Controls.Add(l); + } + } + outHint.Text = "请输入参数后点击查询"; + } + + private void AddInputRow(string labelText, TextBox txt, Button btn) + { + FlowLayoutPanel row = new FlowLayoutPanel(); + row.AutoSize = true; + row.WrapContents = false; + row.FlowDirection = FlowDirection.LeftToRight; + row.Margin = new Padding(0, 2, 0, 2); + + Label l = new Label(); + l.Text = labelText; + l.AutoSize = true; + l.Font = new Font("Microsoft YaHei", 10f); + l.Margin = new Padding(0, 5, 6, 0); + row.Controls.Add(l); + + if (txt != null) row.Controls.Add(txt); + if (btn != null) row.Controls.Add(btn); + + inFlow.Controls.Add(row); + } + + private void DoQuery(QueryKind kind, string diaText) + { + double d; + if (!double.TryParse(diaText.Trim(), out d) || d <= 0) + { + MessageBox.Show("请输入有效直径(mm)", "Design Express Res", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); + return; + } + grid.Rows.Clear(); + var rows = DesignQuery.Query(kind, d); + foreach (QueryRow r in rows) + { + grid.Rows.Add(r.Item, r.Value, r.Std, r.Remark); + } + outHint.Text = "查询完成: 共 " + rows.Count + " 项结果 (输入直径 d=" + d.ToString("0.0") + " mm)"; + } + } +} diff --git a/AddIn/src/Core/Report.cs b/AddIn/src/Core/Report.cs new file mode 100644 index 0000000..9441843 --- /dev/null +++ b/AddIn/src/Core/Report.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace DesignExpress.Core +{ + // 报告输出: Excel(COM 动态调用) 优先, CSV 兜底 + public static class Report + { + public const string ToolVersion = "1.3.0"; + + public static string WriteExcel(string path) + { + try + { + Type xlType = Type.GetTypeFromProgID("Excel.Application"); + if (xlType == null) return WriteCsv(path); + dynamic xl = Activator.CreateInstance(xlType); + try + { + xl.Visible = false; + xl.DisplayAlerts = false; + dynamic wb = xl.Workbooks.Add(); + + dynamic ws1 = wb.Worksheets[1]; + ws1.Name = "汇总"; + dynamic ws2 = wb.Worksheets.Add(Type.Missing, ws1); + ws2.Name = "明细"; + dynamic ws3 = wb.Worksheets.Add(Type.Missing, ws2); + ws3.Name = "检查项清单"; + + ws1.Cells[1, 1] = "设计标注检查报告"; + ws1.Cells[2, 1] = "检查时间: " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + ws1.Cells[3, 1] = "工具版本: " + ToolVersion; + + int pass, warn, fail, info; + AppState.Count(out pass, out warn, out fail, out info); + ws1.Cells[5, 1] = "检查项总数"; ws1.Cells[5, 2] = AppState.Results.Count; + ws1.Cells[6, 1] = "通过"; ws1.Cells[6, 2] = pass; + ws1.Cells[7, 1] = "警告"; ws1.Cells[7, 2] = warn; + ws1.Cells[8, 1] = "未通过"; ws1.Cells[8, 2] = fail; + ws1.Cells[9, 1] = "信息"; ws1.Cells[9, 2] = info; + + ws1.Cells[11, 1] = "按检查项统计:"; + ws1.Cells[12, 1] = "编号"; ws1.Cells[12, 2] = "检查项"; + ws1.Cells[12, 3] = "总数"; ws1.Cells[12, 4] = "通过"; + ws1.Cells[12, 5] = "警告"; ws1.Cells[12, 6] = "未通过"; + BuildCheckStat(ws1, 13); + ws1.Columns["A:F"].AutoFit(); + + ws2.Cells[1, 1] = "序号"; ws2.Cells[1, 2] = "阶段"; + ws2.Cells[1, 3] = "检查对象"; ws2.Cells[1, 4] = "编号"; + ws2.Cells[1, 5] = "检查项"; ws2.Cells[1, 6] = "结果"; + ws2.Cells[1, 7] = "说明"; ws2.Cells[1, 8] = "判定依据"; + int row = 2; + foreach (CheckResult r in AppState.Results) + { + ws2.Cells[row, 1] = row - 1; + ws2.Cells[row, 2] = r.Stage; + ws2.Cells[row, 3] = r.Obj; + ws2.Cells[row, 4] = r.CheckID; + ws2.Cells[row, 5] = r.Name; + ws2.Cells[row, 6] = r.Result; + ws2.Cells[row, 7] = r.Note; + ws2.Cells[row, 8] = r.Basis; + dynamic cell = ws2.Cells[row, 6]; + if (r.Result == "通过") cell.Interior.Color = 0x00C8FFC8; + else if (r.Result == "警告") cell.Interior.Color = 0x00C8FFFF; + else if (r.Result == "未通过") cell.Interior.Color = 0x00C8C8FF; + else cell.Interior.Color = 0x00E1E1E1; + row++; + } + ws2.Columns["A:H"].AutoFit(); + + ws3.Cells[1, 1] = "编号"; ws3.Cells[1, 2] = "检查项"; + ws3.Cells[1, 3] = "阶段"; ws3.Cells[1, 4] = "说明"; + string[,] L = CheckList; + for (int i = 0; i < L.GetLength(0); i++) + { + ws3.Cells[i + 2, 1] = L[i, 0]; + ws3.Cells[i + 2, 2] = L[i, 1]; + ws3.Cells[i + 2, 3] = L[i, 2]; + ws3.Cells[i + 2, 4] = L[i, 3]; + } + ws3.Columns["A:D"].AutoFit(); + + string save = path.ToLower().EndsWith(".xlsx") ? path : path + ".xlsx"; + wb.SaveAs(save, 51); + wb.Close(false); + xl.Quit(); + return save; + } + finally + { + try { xl.Quit(); } catch { } + } + } + catch + { + return WriteCsv(path); + } + } + + private static string[,] CheckList = BuildCheckList(); + + private static string[,] BuildCheckList() + { + string[,] L = new string[75, 4]; + string[] rows = new string[] + { + "P01,文档保存状态,零件,是否有未保存修改", + "P02,零件代号,零件,标题栏必填", + "P03,名称,零件,标题栏必填", + "P04,材料,零件,必填且非默认", + "P05,设计者,零件,标题栏必填", + "P06,审核者,零件,标题栏必填", + "P07,修订号,零件,版本追溯", + "P08,模型标注统计,零件,PMI 三维标注完整性", + "P08b,尺寸公差覆盖,零件,配合尺寸必须带公差", + "P08c,形位公差标注,零件,跳动/同轴度/平面度等", + "P08d,粗糙度标注,零件,配合/密封面必须标注", + "P09,公差配合检查,零件,轴承配合按 GB/T 275, 一般配合按 GB/T 1801", + "P10,配合面形位公差,零件,轴承位跳动/同轴度 IT5~IT6", + "P11,配合面粗糙度,零件,轴颈 0.4~0.8, 座孔 0.8~1.6", + "P12,轴承位公差等级,零件,轴颈 IT5~IT6, 座孔 IT7", + "P13,螺纹孔检查,零件,螺纹特征及标注齐全", + "P14,物料接触检查,零件,直接/间接/非接触分类", + "P14b,GMP 接触材料,零件,直接接触须用 316L 级", + "P14c,GMP 接触面粗糙度,零件,Ra<=0.8", + "P14d,GMP 无死角检查,零件,圆角>=R10, 内夹角>=135度", + "P15,焊接标注检查,零件,双面焊/打磨要求", + "P16,表面处理/防锈,零件,镀镍/磨床/抛光/防锈包装", + "P17,结构强度/仿真,零件,材料指定+CAE 校核指引", + "A01,文档保存状态,装配,是否有未保存修改", + "A02,GMP 材料合规,装配,全部零件材料合规", + "A03,密封件存在性,装配,密封圈/O型圈/垫片识别", + "A04,密封设计提示,装配,GB/T 3452.3 密封槽/FDA", + "A05,清洁性设计要求,装配,清洁/CIP/死角/排空", + "A06,GMP 粗糙度标注,装配,接触面 Ra<=0.8", + "A07,静态干涉,装配,干涉分析", + "A08,运动干涉(动态),装配,驱动约束/运动仿真", + "A09,外购件优先,装配,标准/外购件比例", + "A10,电气选型/西门子联网,装配,西门子/PROFINET/防爆/IP等级", + "A11,施工/公共动力系统,装配,压缩空气/纯化水/蒸汽/排风/配孔/吊顶", + "A12,整机强度/仿真指引,装配,建议应力分析/有限元校核", + "D00,文档保存状态,工程图,是否有未保存修改", + "D01,图框/标题栏,工程图,每张图纸必须有图框", + "D02,标题栏属性,工程图,图号/名称/材料/设计/审核/修订号", + "D03,空白图纸,工程图,无视图无标注", + "D04,视图更新状态,工程图,出图前必须更新视图", + "D05,剖视图统计,工程图,剖面线/剖切位置标注", + "D06,视图越界,工程图,视图超出图框", + "D07,尺寸数量,工程图,信息项", + "D08,尺寸公差,工程图,配合/关键尺寸必须带公差", + "D09,尺寸文本异常,工程图,含?的尺寸(测量失败)", + "D10,形位公差,工程图,信息项", + "D11,基准符号,工程图,API 不支持自动读取", + "D12,表面粗糙度,工程图,信息项", + "D13,焊接符号,工程图,API 不支持自动读取, 由 D19 覆盖", + "D14,孔标注,工程图,API 不支持自动读取, 由 D23 覆盖", + "D15,中心线/件号,工程图,信息项", + "D16,标注越界,工程图,标注超出图框", + "D17,图幅信息,工程图,信息项", + "D18,物料接触类别声明,工程图,直接/间接接触标注", + "D19,焊接技术要求,工程图,双面焊/打磨/焊接标准", + "D20,死角/夹角要求,工程图,无死角/夹角>=135度", + "D21,公差要求完整性,工程图,未注公差说明", + "D22,轴承位公差标注,工程图,跳动/同轴/旋转精度", + "D23,螺纹孔标注,工程图,螺纹代号/深度/攻深", + "D24,表面处理/镀镍,工程图,镀镍后是否磨床/抛光", + "D25,平面度/平行度,工程图,关键面平面度要求", + "D26,特殊角度管加工,工程图,角度管/弯管要求", + "D27,装配治具与检验,工程图,治具/检具/检验要求", + "D28,加工辅助件要求,工程图,工艺/毛坯/余量", + "D29,外购件优先,工程图,标准件优先采购", + "D30,测试要求,工程图,试压/气密/出厂测试", + "D31,装配要求,工程图,安装/扭矩/间隙", + "D32,防锈要求,工程图,防锈/包装/海运", + "D33,施工/公共动力系统,工程图,压缩空气/纯化水/蒸汽/排风", + "D34,配孔技术要求,工程图,配孔/配钻/现场配", + "D35,吊顶配件要求,工程图,吊顶/吊架/吊装", + "D36,电气元件选型,工程图,防爆/IP等级/选型", + "D37,西门子联网器件,工程图,西门子/PROFINET/联网", + "D38,结构强度/仿真,工程图,强度校核/仿真说明", + "D39,夹角R10/135度确认,工程图,死角夹角要求确认" + }; + for (int i = 0; i < rows.Length; i++) + { + string[] p = rows[i].Split(','); + for (int j = 0; j < 4; j++) + if (j < p.Length) L[i, j] = p[j]; + } + return L; + } + + private static void BuildCheckStat(dynamic ws, int startRow) + { + List ids = new List(); + List names = new List(); + foreach (CheckResult r in AppState.Results) + { + if (!ids.Contains(r.CheckID)) + { + ids.Add(r.CheckID); + names.Add(r.Name); + } + } + int row = startRow; + for (int i = 0; i < ids.Count; i++) + { + int nT = 0, nP = 0, nW = 0, nF = 0; + foreach (CheckResult r in AppState.Results) + { + if (r.CheckID == ids[i]) + { + nT++; + if (r.Result == "通过") nP++; + else if (r.Result == "警告") nW++; + else if (r.Result == "未通过") nF++; + } + } + ws.Cells[row, 1] = ids[i]; + ws.Cells[row, 2] = names[i]; + ws.Cells[row, 3] = nT; + ws.Cells[row, 4] = nP; + ws.Cells[row, 5] = nW; + ws.Cells[row, 6] = nF; + row++; + } + } + + public static string WriteCsv(string path) + { + string save = path.ToLower().EndsWith(".csv") ? path : path + ".csv"; + StringBuilder sb = new StringBuilder(); + sb.AppendLine("序号,阶段,检查对象,编号,检查项,结果,说明,判定依据"); + int i = 1; + foreach (CheckResult r in AppState.Results) + { + sb.Append(i).Append(',').Append(r.Stage).Append(',').Append(r.Obj).Append(',') + .Append(r.CheckID).Append(',').Append(r.Name).Append(',').Append(r.Result).Append(',') + .Append("\"").Append(r.Note.Replace("\"", "\"\"")).Append("\",\"") + .Append(r.Basis.Replace("\"", "\"\"")).AppendLine("\""); + i++; + } + System.IO.File.WriteAllText(save, sb.ToString(), new UTF8Encoding(true)); + return save; + } + } +} diff --git a/AddIn/src/Core/Snapshots.cs b/AddIn/src/Core/Snapshots.cs new file mode 100644 index 0000000..97fdd6f --- /dev/null +++ b/AddIn/src/Core/Snapshots.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; + +namespace DesignExpress.Core +{ + // 文档快照: Link 层把 CAD 文档状态拍成纯数据, Core 只处理快照, 与 CAD 完全解耦 + public class DocSnapshot + { + public string Kind = ""; // Part / Assembly / Drawing + public bool Dirty; + public string DisplayName = ""; + public string Folder = ""; + public Dictionary Props = new Dictionary(); + public string MaterialName = ""; + public ModelInfo Model; // 零件/装配的三维标注 + public AssemblyInfo Asm; // 装配 + public List Sheets = new List(); // 工程图 + public List OpenDocs = new List(); // 会话中已打开的文档 + } + + public class DocInfo + { + public string Name = ""; + public Dictionary Props = new Dictionary(); + public string Material = ""; + public int TexCount = 0; + } + + public class ModelInfo + { + public int DimCount = -1; + public int FcfCount = -1; + public int TexCount = -1; + public int DatumCount = -1; + public int ThreadNoteCount = -1; + public int TolCount = 0; + public bool TolReadable = true; + public string FirstTexText = ""; + public int ThreadFeatureCount = -1; + public string ThreadDesignation = ""; + public bool MinFilletReadable = false; + public double MinFilletRadiusMM = -1; + } + + public class AssemblyInfo + { + public List OccurrenceNames = new List(); + public List Interferences = new List(); + public int InterferenceState = 0; // 0=无干涉 1=发现干涉 2=检查失败 + public string InterferenceError = ""; + public int TopLevelCount = 0; + } + + public class InterferencePair + { + public string Name1 = ""; + public string Name2 = ""; + public double VolumeCM3 = 0; + } + + public class RectD + { + public double L, T, R, B; + } + + public class SheetInfo + { + public string Name = ""; + public bool HasBorder = false; + public bool HasTitleBlock = false; + public string TitleBlockName = ""; + public double SheetWidth = 0, SheetHeight = 0; + public RectD BorderRect; // null 表示无图框 + public int ViewCount = 0; + public int UpdateNeeded = 0; + public int UpdateError = 0; + public int SectionCount = 0; + public List ViewRects = new List(); + public int DimCount = 0; + public int DimTolCount = 0; + public int DimQuestionCount = 0; + public int FcfCount = 0; + public int TexCount = 0; + public int CenterlineCount = 0; + public int BalloonCount = 0; + public List NotesTexts = new List(); + public List FcfRects = new List(); + public List TexRects = new List(); + } +} diff --git a/AddIn/src/Core/ToolIcons.cs b/AddIn/src/Core/ToolIcons.cs new file mode 100644 index 0000000..2b1e83d --- /dev/null +++ b/AddIn/src/Core/ToolIcons.cs @@ -0,0 +1,236 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.IO; +using System.Runtime.InteropServices; + +namespace DesignExpress.Core +{ + // 功能按钮图标类型 + public enum ToolIcon + { + Part, // 零件-齿轮 + Asm, // 装配-方块 + Drw, // 工程图-图纸 + Export, // 导出-报告 + Help, // 说明-问号 + Circlip, // 卡簧槽-开口卡簧 + Keyway, // 键槽-轴槽 + Oring, // O型圈-圆环 + Bearing // 轴承挡圈-轴承 + } + + // 共享图标生成器: 仓鼠品牌形象 + 功能符号, 供 Inventor(.ico) 和 SolidWorks(图标条BMP) 复用 + public static class ToolIcons + { + private static readonly Color[] BgColors = new Color[] + { + Color.FromArgb(0x2E, 0x75, 0xB6), // Part 蓝 + Color.FromArgb(0x33, 0x8A, 0x4E), // Asm 绿 + Color.FromArgb(0xE6, 0x8A, 0x00), // Drw 橙 + Color.FromArgb(0x7B, 0x4F, 0xB0), // Export 紫 + Color.FromArgb(0x5F, 0x5F, 0x5F), // Help 灰 + Color.FromArgb(0x1F, 0x8A, 0x8A), // Circlip 青 + Color.FromArgb(0x8A, 0x5A, 0x2E), // Keyway 棕 + Color.FromArgb(0xC0, 0x3A, 0x2B), // Oring 红 + Color.FromArgb(0xD2, 0x5E, 0x8A) // Bearing 粉 + }; + + public static Color IconColor(ToolIcon icon) + { + return BgColors[(int)icon]; + } + + // 绘制单个图标到指定位置: 中央大功能符号 + 底部小仓鼠品牌点缀 + private static void DrawIcon(Graphics g, int ox, int oy, int size, ToolIcon icon, Color bg) + { + Color fur = Color.FromArgb(0xE8, 0xC0, 0x7D); + Color ear = Color.FromArgb(0xC8, 0x8A, 0x4F); + Color dark = Color.FromArgb(0x3A, 0x24, 0x10); + Color pink = Color.FromArgb(0xF2, 0x9E, 0xB6); + + using (GraphicsPath path = RoundedRect(ox + 1, oy + 1, size - 2, size - 2, 6)) + using (SolidBrush bgb = new SolidBrush(bg)) + { + g.FillPath(bgb, path); + } + + using (SolidBrush bEar = new SolidBrush(ear)) + using (SolidBrush bFur = new SolidBrush(fur)) + using (SolidBrush bDark = new SolidBrush(dark)) + using (SolidBrush bPink = new SolidBrush(pink)) + using (SolidBrush bWhite = new SolidBrush(Color.White)) + using (Pen pWhite = new Pen(Color.White, 2f)) + { + // 中央功能符号(大, 醒目) + int cx = ox + size / 2; + int cy = oy + size / 2 - 2; + switch (icon) + { + case ToolIcon.Part: // 齿轮 + g.FillEllipse(bWhite, cx - 4, cy - 4, 8, 8); + g.FillRectangle(bWhite, cx - 6.5f, cy - 1.5f, 13, 3); + g.FillRectangle(bWhite, cx - 1.5f, cy - 6.5f, 3, 13); + g.FillRectangle(bWhite, cx - 5.5f, cy - 5.5f, 3, 3); + g.FillRectangle(bWhite, cx + 2.5f, cy - 5.5f, 3, 3); + g.FillRectangle(bWhite, cx - 5.5f, cy + 2.5f, 3, 3); + g.FillRectangle(bWhite, cx + 2.5f, cy + 2.5f, 3, 3); + break; + case ToolIcon.Asm: // 三个方块(品字) + g.FillRectangle(bWhite, cx - 8, cy - 5, 6, 6); + g.FillRectangle(bWhite, cx + 2, cy - 5, 6, 6); + g.FillRectangle(bWhite, cx - 3, cy + 2, 6, 6); + break; + case ToolIcon.Drw: // 图纸 + g.DrawRectangle(pWhite, cx - 6, cy - 6, 12, 12); + g.DrawLine(pWhite, cx - 6, cy - 2, cx + 0, cy - 2); + g.DrawLine(pWhite, cx - 6, cy + 2, cx + 2, cy + 2); + g.DrawLine(pWhite, cx - 2, cy + 6, cx + 3, cy + 1); + break; + case ToolIcon.Export: // 报告表格 + g.FillRectangle(bWhite, cx - 6, cy - 6, 12, 12); + using (SolidBrush bBg = new SolidBrush(bg)) + { + g.FillRectangle(bBg, cx - 4, cy - 3, 8, 2); + g.FillRectangle(bBg, cx - 4, cy + 0, 8, 2); + g.FillRectangle(bBg, cx - 4, cy + 3, 5, 2); + } + break; + case ToolIcon.Help: // 问号 + using (Font f = new Font("Arial", 13f, FontStyle.Bold, GraphicsUnit.Pixel)) + { + g.DrawString("?", f, bWhite, cx - 4, cy - 9); + } + break; + case ToolIcon.Circlip: // 开口卡簧 + g.DrawArc(pWhite, cx - 6, cy - 5.5f, 12, 11, 40, 280); + break; + case ToolIcon.Keyway: // 轴键槽 + g.DrawEllipse(pWhite, cx - 6.5f, cy - 5, 13, 10); + g.FillRectangle(bWhite, cx - 3, cy - 5, 6, 5); + break; + case ToolIcon.Oring: // O型圈 + g.DrawEllipse(pWhite, cx - 6.5f, cy - 5, 13, 10); + g.DrawEllipse(pWhite, cx - 3.5f, cy - 2, 7, 4); + break; + case ToolIcon.Bearing: // 轴承 + g.DrawEllipse(pWhite, cx - 6.5f, cy - 5, 13, 10); + g.FillEllipse(bWhite, cx - 1.5f, cy - 0.5f, 3, 3); + g.FillEllipse(bWhite, cx - 5f, cy - 4f, 2, 2); + g.FillEllipse(bWhite, cx + 3f, cy - 4f, 2, 2); + g.FillEllipse(bWhite, cx - 5f, cy + 3f, 2, 2); + g.FillEllipse(bWhite, cx + 3f, cy + 3f, 2, 2); + break; + } + } + } + + // 直接绘制 Bitmap 并转 IPictureDisp(Inventor 按钮图标) + public static object CreatePictureDisp(ToolIcon icon, int size) + { + try + { + using (Bitmap bmp = new Bitmap(size, size)) + { + using (Graphics g = Graphics.FromImage(bmp)) + { + g.SmoothingMode = SmoothingMode.AntiAlias; + DrawIcon(g, 0, 0, size, icon, BgColors[(int)icon]); + } + IntPtr hbm = bmp.GetHbitmap(); + PICTDESC pd; + pd.cbSizeofstruct = Marshal.SizeOf(typeof(PICTDESC)); + pd.picType = 1; // CT_BITMAP + pd.hbitmap = hbm; + pd.hpal = IntPtr.Zero; + Guid iid = new Guid("7BF80981-BF32-101A-8BBB-00AA00300CAB"); // stdole.IPictureDisp 正确 IID(注意是81结尾) + object ppv; + int hr = OleCreatePictureIndirect(ref pd, ref iid, true, out ppv); + if (hr != 0 || ppv == null) return Type.Missing; + return ppv; + } + } + catch { return Type.Missing; } + } + + [StructLayout(LayoutKind.Sequential)] + private struct PICTDESC + { + public int cbSizeofstruct; + public int picType; + public IntPtr hbitmap; + public IntPtr hpal; + } + + [DllImport("oleaut32.dll")] + private static extern int OleCreatePictureIndirect(ref PICTDESC pDesc, ref Guid riid, bool fOwn, out object ppvObj); + + [DllImport("user32.dll")] + private static extern bool DeleteObject(IntPtr hObject); + + // 生成单个 .ico 文件 + public static string CreateIco(ToolIcon icon, int size, string file) + { + try + { + using (Bitmap bmp = new Bitmap(size, size)) + { + using (Graphics g = Graphics.FromImage(bmp)) + { + g.SmoothingMode = SmoothingMode.AntiAlias; + g.Clear(Color.FromArgb(0xFF, 0x00, 0xFF)); + DrawIcon(g, 0, 0, size, icon, BgColors[(int)icon]); + } + Icon ic = Icon.FromHandle(bmp.GetHicon()); + try + { + using (FileStream fs = new FileStream(file, FileMode.Create)) + ic.Save(fs); + } + finally + { + DestroyIcon(ic.Handle); + } + } + return file; + } + catch { return ""; } + } + + // 生成 SolidWorks 图标条 BMP(每格 size, 横向拼接) + public static string CreateStripBmp(ToolIcon[] icons, int size, string file) + { + try + { + int n = icons.Length; + using (Bitmap bmp = new Bitmap(size * n, size)) + { + using (Graphics g = Graphics.FromImage(bmp)) + { + g.SmoothingMode = SmoothingMode.AntiAlias; + g.Clear(Color.FromArgb(0xFF, 0x00, 0xFF)); + for (int i = 0; i < n; i++) + DrawIcon(g, i * size, 0, size, icons[i], BgColors[(int)icons[i]]); + } + bmp.Save(file, System.Drawing.Imaging.ImageFormat.Bmp); + } + return file; + } + catch { return ""; } + } + + [System.Runtime.InteropServices.DllImport("user32.dll")] + private static extern bool DestroyIcon(IntPtr hIcon); + + private static GraphicsPath RoundedRect(int x, int y, int w, int h, int r) + { + GraphicsPath p = new GraphicsPath(); + p.AddArc(x, y, r, r, 180, 90); + p.AddArc(x + w - r, y, r, r, 270, 90); + p.AddArc(x + w - r, y + h - r, r, r, 0, 90); + p.AddArc(x, y + h - r, r, r, 90, 90); + p.CloseFigure(); + return p; + } + } +} diff --git a/AddIn/src/InventorLink/AddInServer.cs b/AddIn/src/InventorLink/AddInServer.cs new file mode 100644 index 0000000..be3cedc --- /dev/null +++ b/AddIn/src/InventorLink/AddInServer.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using Inventor; +using DesignExpress.Core; + +[assembly: ComVisible(true)] + +namespace DesignExpress.InventorLink +{ + [Guid("D422E0BF-02D0-4DAE-958A-7BBA80B718E9")] + [ComVisible(true)] + [ClassInterface(ClassInterfaceType.None)] + [ProgId("DesignExpress.InventorLink.AddInServer")] + public class AddInServer : ApplicationAddInServer + { + public const string TabId = "InvDesignCheck_Tab"; + public const string PanelId = "InvDesignCheck_Panel"; + public const string PanelQueryId = "InvDesignCheck_PanelQuery"; + private Application mApp; + private string mClientId; + private static Dictionary mDefs = new Dictionary(); + + public void Activate(ApplicationAddInSite addInSiteObject, bool firstTime) + { + try { mApp = (Application)addInSiteObject.Application; } catch { } + try { mClientId = addInSiteObject.Parent.ClientId; } catch { } + if (mApp == null) return; + AppState.Host = new InventorHost(mApp); + try { CreateRibbon(); } catch { } + } + + private void CreateRibbon() + { + Inventor.UserInterfaceManager uim = mApp.UserInterfaceManager; + string[] ribNames = { "Part", "Assembly", "Drawing", "Presentation", "ZeroDoc" }; + foreach (string n in ribNames) + { + try + { + Inventor.Ribbon ribbon = uim.Ribbons[n]; + AddTabToRibbon(ribbon); + } + catch { } + } + } + + private void AddTabToRibbon(Inventor.Ribbon ribbon) + { + Inventor.RibbonTab tab = null; + try { tab = ribbon.RibbonTabs[TabId]; } catch { } + if (tab == null) + tab = ribbon.RibbonTabs.Add("Design Express Res", TabId, "", "", false, false); + Inventor.RibbonPanel panel = null; + try { panel = tab.RibbonPanels[PanelId]; } catch { } + if (panel == null) + panel = tab.RibbonPanels.Add("设计检查", PanelId, "", "", false); + + if (panel.CommandControls.Count == 0) + { + panel.CommandControls.AddButton(GetDef("零件检查", "InvDesignCheck_CmdPart", + "对当前零件进行三维标注、公差配合、GMP 检查", + "零件检查:标注统计、公差配合(GB/T 275)、形位公差、粗糙度、螺纹孔、物料接触 GMP、表面处理", + Checks.PartCheck)); + panel.CommandControls.AddButton(GetDef("装配检查", "InvDesignCheck_CmdAsm", + "对当前装配进行 GMP、密封、干涉、外购件检查", + "装配检查:GMP 材料合规、密封件、清洁性、粗糙度、静态干涉、外购件、电气/西门子、动力系统", + Checks.AssemblyCheck)); + panel.CommandControls.AddButton(GetDef("工程图检查", "InvDesignCheck_CmdDrw", + "对当前工程图进行标注与 spec 技术要求检查", + "工程图检查:图框、标题栏、视图更新、尺寸公差、标注统计、spec 技术要求关键词齐全性", + Checks.DrawingCheck)); + panel.CommandControls.AddButton(GetDef("导出报告", "InvDesignCheck_CmdExport", + "导出检查报告(Excel/CSV)", + "导出检查报告(Excel/CSV)", + Checks.ExportReport)); + panel.CommandControls.AddButton(GetDef("使用说明", "InvDesignCheck_CmdHelp", + "查看检查项清单与使用说明", + "查看检查项清单与使用说明", + Checks.Help)); + } + + Inventor.RibbonPanel panelQuery = null; + try { panelQuery = tab.RibbonPanels[PanelQueryId]; } catch { } + if (panelQuery == null) + panelQuery = tab.RibbonPanels.Add("设计数据查询", PanelQueryId, "", "", false); + if (panelQuery.CommandControls.Count == 0) + { + panelQuery.CommandControls.AddButton(GetDef("卡簧槽查询", "InvDesignCheck_CmdCirclip", + "轴用/孔用弹性挡圈槽尺寸查询 (GB/T 894)", + "卡簧槽: 输入轴径/孔径, 查询挡圈型号、槽径、槽宽、槽深", + Checks.CirclipQuery)); + panelQuery.CommandControls.AddButton(GetDef("键槽查询", "InvDesignCheck_CmdKeyway", + "平键键槽尺寸查询 (GB/T 1095)", + "键槽: 输入轴径, 查询键尺寸 b×h、轴槽深、轮毂槽深", + Checks.KeywayQuery)); + panelQuery.CommandControls.AddButton(GetDef("O型密封圈查询", "InvDesignCheck_CmdOring", + "O 型密封圈设计与沟槽尺寸查询 (GB/T 3452.1)", + "O型密封圈: 查询圈规格、沟槽尺寸、压缩量", + Checks.OringQuery)); + panelQuery.CommandControls.AddButton(GetDef("轴承挡圈查询", "InvDesignCheck_CmdBRing", + "轴承挡圈与挡边设计查询", + "轴承挡圈: 查询挡圈规格、挡边高度、安装槽尺寸", + Checks.BearingRingQuery)); + } + } + + private Inventor.ButtonDefinition GetDef(string display, string internalName, + string desc, string tip, Action action) + { + Inventor.ButtonDefinition def; + lock (mDefs) + { + if (mDefs.TryGetValue(internalName, out def)) return def; + // Inventor 图标需要 IPictureDisp(直接内存绘制, 不经过文件) + object stdIcon = Type.Missing, largeIcon = Type.Missing; + try + { + ToolIcon ic = IconEnum(IconName(internalName)); + stdIcon = ToolIcons.CreatePictureDisp(ic, 16); + largeIcon = ToolIcons.CreatePictureDisp(ic, 32); + } + catch { } + try + { + def = mApp.CommandManager.ControlDefinitions.AddButtonDefinition( + display, internalName, CommandTypesEnum.kNonShapeEditCmdType, mClientId, desc, tip, + stdIcon, largeIcon, ButtonDisplayEnum.kAlwaysDisplayText); + } + catch + { + def = mApp.CommandManager.ControlDefinitions.AddButtonDefinition( + display, internalName, CommandTypesEnum.kNonShapeEditCmdType, mClientId, desc, tip, + Type.Missing, Type.Missing, ButtonDisplayEnum.kAlwaysDisplayText); + } + mDefs[internalName] = def; + // 关键: 直接订阅按钮的 OnExecute 事件处理点击(与 Joyea 一致, 不依赖 ExecuteCommand 路由) + try + { + Inventor.ButtonDefinitionSink_Event evt = (Inventor.ButtonDefinitionSink_Event)def; + evt.OnExecute += delegate(NameValueMap context) + { + try { action(); } + catch (Exception ex) { System.Windows.Forms.MessageBox.Show("执行出错: " + ex.Message); } + }; + } + catch { } + } + return def; + } + + private static string IconName(string internalName) + { + if (internalName.EndsWith("Part")) return "part"; + if (internalName.EndsWith("Asm")) return "asm"; + if (internalName.EndsWith("Drw")) return "drw"; + if (internalName.EndsWith("Export")) return "export"; + if (internalName.EndsWith("Help")) return "help"; + if (internalName.EndsWith("Circlip")) return "circlip"; + if (internalName.EndsWith("Keyway")) return "keyway"; + if (internalName.EndsWith("Oring")) return "oring"; + if (internalName.EndsWith("BRing")) return "bearing"; + return "help"; + } + + private static ToolIcon IconEnum(string name) + { + switch (name) + { + case "part": return ToolIcon.Part; + case "asm": return ToolIcon.Asm; + case "drw": return ToolIcon.Drw; + case "export": return ToolIcon.Export; + case "help": return ToolIcon.Help; + case "circlip": return ToolIcon.Circlip; + case "keyway": return ToolIcon.Keyway; + case "oring": return ToolIcon.Oring; + case "bearing": return ToolIcon.Bearing; + default: return ToolIcon.Help; + } + } + + public void ExecuteCommand(int commandID) + { + // OnExecute 事件为主要路径, 此方法保留兼容(无操作) + } + + public object Automation { get { return null; } } + + public void Deactivate() + { + try + { + Inventor.UserInterfaceManager uim = mApp.UserInterfaceManager; + string[] ribNames = { "Part", "Assembly", "Drawing", "Presentation", "ZeroDoc" }; + foreach (string n in ribNames) + { + try + { + Inventor.Ribbon ribbon = uim.Ribbons[n]; + Inventor.RibbonTab tab = null; + try { tab = ribbon.RibbonTabs[TabId]; } catch { } + if (tab != null) { try { tab.Delete(); } catch { } } + } + catch { } + } + } + catch { } + } + } +} diff --git a/AddIn/src/InventorLink/InventorHost.cs b/AddIn/src/InventorLink/InventorHost.cs new file mode 100644 index 0000000..a375f18 --- /dev/null +++ b/AddIn/src/InventorLink/InventorHost.cs @@ -0,0 +1,458 @@ +using System; +using System.Collections.Generic; +using Inventor; +using DesignExpress.Core; + +namespace DesignExpress.InventorLink +{ + // Inventor 适配器: 实现 ICadHost, 把 Inventor 文档状态拍成 Core 快照 + public class InventorHost : ICadHost + { + private Application mApp; + + public InventorHost(Application app) + { + mApp = app; + } + + public string AppName { get { return "Inventor"; } } + + public object GetActiveDocument() + { + try { return mApp.ActiveDocument; } catch { return null; } + } + + public string GetDocType(object doc) + { + try + { + dynamic d = doc; + DocumentTypeEnum t = (DocumentTypeEnum)d.DocumentType; + if (t == DocumentTypeEnum.kPartDocumentObject) return "Part"; + if (t == DocumentTypeEnum.kAssemblyDocumentObject) return "Assembly"; + if (t == DocumentTypeEnum.kDrawingDocumentObject) return "Drawing"; + } + catch { } + return ""; + } + + public void SetStatusText(string text) + { + try { mApp.StatusBarText = text; } catch { } + } + + public double PickCylinderDiameterMM(out string faceName) + { + faceName = "手动输入"; + try + { + object pick = mApp.CommandManager.Pick(SelectionFilterEnum.kPartFaceCylindricalFilter, + "请选择轴承/配合圆柱面(轴或孔), 取消则手动输入直径"); + if (pick != null) + { + Face f = pick as Face; + if (f != null) + { + object geom = f.Geometry; + if (geom is Cylinder) + { + double d = ((Cylinder)geom).Radius * 20; + try { faceName = Convert.ToString(((dynamic)f).Name); } catch { } + return d; + } + } + } + } + catch { } + return 0; + } + + public DocSnapshot Capture(object doc) + { + DocSnapshot snap = new DocSnapshot(); + dynamic d = doc; + snap.Kind = GetDocType(doc); + try { snap.Dirty = (bool)d.Dirty; } catch { } + try { snap.DisplayName = Convert.ToString(d.DisplayName); } catch { } + try + { + string full = Convert.ToString(d.FullDocumentName); + int p = full.LastIndexOf('\\'); + if (p > 0) snap.Folder = full.Substring(0, p + 1); + } + catch { } + + snap.Props = ReadProps(doc); + + if (snap.Kind == "Part" || snap.Kind == "Assembly") + { + snap.MaterialName = ReadMaterial(d); + snap.Model = ReadModelInfo(d); + snap.Asm = new AssemblyInfo(); + } + if (snap.Kind == "Assembly") + { + ReadAssemblyInfo(d, snap); + } + if (snap.Kind == "Drawing") + { + ReadDrawingInfo(d, snap); + } + ReadOpenDocs(snap); + return snap; + } + + private Dictionary ReadProps(object doc) + { + Dictionary props = new Dictionary(); + try + { + dynamic d = doc; + foreach (PropertySet ps in d.PropertySets) + { + foreach (Property p in ps) + { + try + { + string key = Convert.ToString(p.Name); + string val = Convert.ToString(p.Value); + if (!string.IsNullOrEmpty(key) && !props.ContainsKey(key)) + props[key] = val ?? ""; + } + catch { } + } + } + } + catch { } + return props; + } + + private string ReadMaterial(dynamic d) + { + try { return Convert.ToString(d.ComponentDefinition.Material.Name); } catch { return ""; } + } + + private ModelInfo ReadModelInfo(dynamic d) + { + ModelInfo m = new ModelInfo(); + try + { + dynamic ma = d.ComponentDefinition.ModelAnnotations; + try { m.DimCount = (int)ma.ModelDimensions.Count; } catch { } + try { m.FcfCount = (int)ma.ModelFeatureControlFrames.Count; } catch { } + try { m.TexCount = (int)ma.ModelSurfaceTextureSymbols.Count; } catch { } + try { m.DatumCount = (int)ma.ModelDatumIdentifiers.Count; } catch { } + try { m.ThreadNoteCount = (int)ma.ModelHoleThreadNotes.Count; } catch { } + if (m.DimCount > 0) + { + try + { + int tol = 0; + for (int i = 1; i <= m.DimCount; i++) + { + try + { + dynamic dd = ma.ModelDimensions[i]; + dynamic tolObj = dd.Definition.Tolerance; + if (tolObj != null && tolObj.ToleranceType != ToleranceTypeEnum.kDefaultTolerance) + tol++; + } + catch { m.TolReadable = false; } + } + m.TolCount = tol; + } + catch { m.TolReadable = false; } + } + if (m.TexCount > 0) + { + try { m.FirstTexText = Convert.ToString(ma.ModelSurfaceTextureSymbols[1].Text); } catch { } + } + } + catch { } + try + { + dynamic tf = d.ComponentDefinition.Features.ThreadFeatures; + m.ThreadFeatureCount = (int)tf.Count; + if (m.ThreadFeatureCount > 0) + { + try { m.ThreadDesignation = Convert.ToString(tf[1].Designation); } catch { } + } + } + catch { } + try + { + dynamic ff = d.ComponentDefinition.Features.FilletFeatures; + int n = (int)ff.Count; + double minR = -1; + bool found = false; + for (int i = 1; i <= n; i++) + { + try + { + double r = (double)ff[i].Radius; + if (r > 0) + { + if (!found || r < minR) minR = r; + found = true; + } + } + catch { } + } + m.MinFilletReadable = found; + m.MinFilletRadiusMM = found ? minR * 10 : -1; + } + catch { } + return m; + } + + private void ReadAssemblyInfo(dynamic d, DocSnapshot snap) + { + AssemblyInfo asm = snap.Asm; + try + { + dynamic occs = d.ComponentDefinition.Occurrences; + try { asm.TopLevelCount = (int)occs.Count; } catch { } + CollectOccurrenceNames(occs, asm.OccurrenceNames); + } + catch { } + try + { + dynamic acd = d.ComponentDefinition; + ObjectCollection set1 = mApp.TransientObjects.CreateObjectCollection(); + dynamic occs = acd.Occurrences; + int cnt = (int)occs.Count; + for (int i = 1; i <= cnt; i++) + set1.Add(occs[i]); + dynamic res = acd.AnalyzeInterference(set1, null); + int n = (int)res.Count; + if (n > 0) asm.InterferenceState = 1; + for (int j = 1; j <= n && j <= 50; j++) + { + InterferencePair pair = new InterferencePair(); + try + { + dynamic ir = res[j]; + try { pair.Name1 = Convert.ToString(ir.OccurrenceOne.Name); } catch { } + try { pair.Name2 = Convert.ToString(ir.OccurrenceTwo.Name); } catch { } + try { pair.VolumeCM3 = (double)ir.Volume; } catch { } + asm.Interferences.Add(pair); + } + catch { } + } + } + catch (Exception ex) + { + asm.InterferenceState = 2; + asm.InterferenceError = ex.Message; + } + } + + private void CollectOccurrenceNames(dynamic occs, List names) + { + try + { + int cnt = (int)occs.Count; + for (int i = 1; i <= cnt; i++) + { + dynamic occ = occs[i]; + if (occ == null) continue; + try { names.Add(Convert.ToString(occ.Name)); } catch { } + try + { + dynamic sub = occ.SubOccurrences; + if (sub != null) CollectOccurrenceNames(sub, names); + } + catch { } + } + } + catch { } + } + + private void ReadDrawingInfo(dynamic d, DocSnapshot snap) + { + try + { + dynamic dd = d; + int nSheets = (int)dd.Sheets.Count; + for (int i = 1; i <= nSheets; i++) + { + SheetInfo sh = new SheetInfo(); + try + { + dynamic sheet = dd.Sheets[i]; + sh.Name = Convert.ToString(sheet.Name); + try { sh.SheetWidth = (double)sheet.Width; } catch { } + try { sh.SheetHeight = (double)sheet.Height; } catch { } + + try + { + dynamic bd = sheet.Border; + if (bd != null) + { + sh.HasBorder = true; + try + { + dynamic box = bd.RangeBox; + RectD r = new RectD(); + r.L = (double)box.MinPoint.X; r.T = (double)box.MinPoint.Y; + r.R = (double)box.MaxPoint.X; r.B = (double)box.MaxPoint.Y; + sh.BorderRect = r; + } + catch { } + } + } + catch { } + try + { + dynamic tb = sheet.TitleBlock; + if (tb != null) + { + sh.HasTitleBlock = true; + try { sh.TitleBlockName = Convert.ToString(tb.Name); } catch { } + } + } + catch { } + + try + { + dynamic views = sheet.DrawingViews; + sh.ViewCount = (int)views.Count; + for (int j = 1; j <= sh.ViewCount; j++) + { + dynamic vw = views[j]; + try + { + if (!(bool)vw.IsUpdateComplete) sh.UpdateNeeded++; + } + catch { } + try + { + if ((DrawingViewTypeEnum)vw.ViewType == DrawingViewTypeEnum.kSectionDrawingViewType) + sh.SectionCount++; + } + catch { } + try + { + dynamic pos = vw.Position; + double w = (double)vw.Width, h = (double)vw.Height; + RectD r = new RectD(); + r.L = (double)pos.X - w / 2; r.R = (double)pos.X + w / 2; + r.T = (double)pos.Y - h / 2; r.B = (double)pos.Y + h / 2; + sh.ViewRects.Add(r); + } + catch { } + } + } + catch { } + + try + { + dynamic dims = sheet.DrawingDimensions; + sh.DimCount = (int)dims.Count; + for (int j = 1; j <= sh.DimCount; j++) + { + dynamic dm = dims[j]; + try + { + if (dm.Tolerance.ToleranceType != ToleranceTypeEnum.kDefaultTolerance) + sh.DimTolCount++; + } + catch { } + try + { + if (Convert.ToString(dm.Text.Text).Contains("?")) sh.DimQuestionCount++; + } + catch { } + } + } + catch { } + + try { sh.FcfCount = (int)sheet.FeatureControlFrames.Count; } catch { } + try { sh.TexCount = (int)sheet.SurfaceTextureSymbols.Count; } catch { } + try { sh.CenterlineCount = (int)sheet.Centerlines.Count; } catch { } + try { sh.BalloonCount = (int)sheet.Balloons.Count; } catch { } + + try + { + dynamic notes = sheet.DrawingNotes; + int nn = (int)notes.Count; + for (int j = 1; j <= nn; j++) + { + try { sh.NotesTexts.Add(Convert.ToString(notes[j].Text)); } catch { } + } + } + catch { } + + try + { + for (int j = 1; j <= sh.FcfCount; j++) + { + try + { + dynamic ann = sheet.FeatureControlFrames[j]; + dynamic p1 = mApp.TransientGeometry.CreatePoint2d(); + dynamic p2 = mApp.TransientGeometry.CreatePoint2d(); + ann.GetBounds(p1, p2); + RectD r = new RectD(); + r.L = (double)p1.X; r.T = (double)p1.Y; r.R = (double)p2.X; r.B = (double)p2.Y; + sh.FcfRects.Add(r); + } + catch { } + } + for (int j = 1; j <= sh.TexCount; j++) + { + try + { + dynamic ann = sheet.SurfaceTextureSymbols[j]; + dynamic p1 = mApp.TransientGeometry.CreatePoint2d(); + dynamic p2 = mApp.TransientGeometry.CreatePoint2d(); + ann.GetBounds(p1, p2); + RectD r = new RectD(); + r.L = (double)p1.X; r.T = (double)p1.Y; r.R = (double)p2.X; r.B = (double)p2.Y; + sh.TexRects.Add(r); + } + catch { } + } + } + catch { } + + snap.Sheets.Add(sh); + } + catch { } + } + } + catch { } + } + + private void ReadOpenDocs(DocSnapshot snap) + { + try + { + dynamic docs = mApp.Documents; + int n = (int)docs.Count; + for (int i = 1; i <= n; i++) + { + dynamic dd = docs[i]; + if (dd == null) continue; + try + { + DocumentTypeEnum t = (DocumentTypeEnum)dd.DocumentType; + if (t != DocumentTypeEnum.kPartDocumentObject) continue; + } + catch { continue; } + DocInfo info = new DocInfo(); + try { info.Name = Convert.ToString(dd.DisplayName); } catch { } + info.Props = ReadProps(dd); + info.Material = AppState.GetProp(info.Props, "Material", "材料", "材质"); + if (info.Material == "") + { + try { info.Material = Convert.ToString(dd.ComponentDefinition.Material.Name); } catch { } + } + try { info.TexCount = (int)dd.ComponentDefinition.ModelAnnotations.ModelSurfaceTextureSymbols.Count; } catch { } + snap.OpenDocs.Add(info); + } + } + catch { } + } + } +} diff --git a/AddIn/src/SolidWorksLink/SolidWorksHost.cs b/AddIn/src/SolidWorksLink/SolidWorksHost.cs new file mode 100644 index 0000000..0bb8b82 --- /dev/null +++ b/AddIn/src/SolidWorksLink/SolidWorksHost.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using SolidWorks.Interop.sldworks; +using DesignExpress.Core; + +namespace DesignExpress.SolidWorksLink +{ + // SolidWorks 适配器: 实现 ICadHost, 把 SW 文档状态拍成 Core 快照 + public class SolidWorksHost : ICadHost + { + private ISldWorks mSwApp; + + public SolidWorksHost(ISldWorks swApp) + { + mSwApp = swApp; + } + + public string AppName { get { return "SolidWorks"; } } + + public object GetActiveDocument() + { + try { return mSwApp.IActiveDoc2; } catch { return null; } + } + + public string GetDocType(object doc) + { + try + { + // 注意: 不能用 dynamic 调 GetType()(会解析成 Object.GetType), 必须编译时调用 + ModelDoc2 m = doc as ModelDoc2; + if (m != null) + { + int t = m.GetType(); + if (t == 1) return "Part"; // swDocPART + if (t == 2) return "Assembly"; // swDocASSEMBLY + if (t == 3) return "Drawing"; // swDocDRAWING + } + } + catch { } + return ""; + } + + public void SetStatusText(string text) + { + try { ((dynamic)mSwApp).SetStatusBarText(text); } catch { } + } + + public double PickCylinderDiameterMM(out string faceName) + { + faceName = "手动输入"; + try + { + dynamic doc = mSwApp.IActiveDoc2; + if (doc == null) return 0; + dynamic selMgr = doc.ISelectionManager; + if (selMgr == null) return 0; + dynamic face = selMgr.GetSelectedObject6(1, -1); + if (face == null) return 0; + double[] origin = new double[3]; + double[] axis = new double[3]; + double radius = 0; + face.IGetCylinderParams(origin, axis, out radius); + if (radius > 0) return radius * 2 * 1000; // 米 -> 毫米 + } + catch { } + return 0; + } + + public DocSnapshot Capture(object doc) + { + DocSnapshot snap = new DocSnapshot(); + dynamic d = doc; + snap.Kind = GetDocType(doc); + try { snap.Dirty = (bool)d.GetSaveFlag(); } catch { } + try { snap.DisplayName = Convert.ToString(d.GetTitle()); } catch { } + try + { + string full = Convert.ToString(d.GetPathName()); + int p = full.LastIndexOf('\\'); + if (p > 0) snap.Folder = full.Substring(0, p + 1); + } + catch { } + + snap.Props = ReadProps(d); + snap.MaterialName = ReadMaterial(d); + + if (snap.Kind == "Part" || snap.Kind == "Assembly") + { + snap.Model = ReadModelInfo(d); + snap.Asm = new AssemblyInfo(); + } + if (snap.Kind == "Assembly") + { + ReadAssemblyInfo(d, snap); + } + if (snap.Kind == "Drawing") + { + ReadDrawingInfo(d, snap); + } + ReadOpenDocs(snap); + return snap; + } + + private Dictionary ReadProps(dynamic d) + { + Dictionary props = new Dictionary(); + try + { + dynamic ext = d.Extension; + dynamic cpm = ext.CustomPropertyManager(""); + if (cpm != null) + { + object names = cpm.GetNames(); + if (names is object[]) + { + foreach (object n in (object[])names) + { + try + { + string name = Convert.ToString(n); + string val = "", resolved = ""; + bool wasResolved = false, link = false; + cpm.Get6(name, false, out val, out resolved, out wasResolved, out link); + if (name != "" && !props.ContainsKey(name)) + props[name] = val ?? ""; + } + catch { } + } + } + } + } + catch { } + return props; + } + + private string ReadMaterial(dynamic d) + { + try + { + string mat = ""; + string config = ""; + try { config = Convert.ToString(d.ConfigurationManager.ActiveConfiguration.Name); } catch { } + if (d.GetMaterialPropertyName2(config, out mat)) + return mat ?? ""; + } + catch { } + return ""; + } + + private ModelInfo ReadModelInfo(dynamic d) + { + ModelInfo m = new ModelInfo(); + // 最小圆角(遍历特征中的 Fillet) + try + { + dynamic fm = d.FeatureManager; + dynamic feat = fm.GetFirstFeature(); + double minR = -1; + bool found = false; + while (feat != null) + { + try + { + string tn = Convert.ToString(feat.GetTypeName2()); + if (tn == "Fillet") + { + dynamic fd = feat.GetDefinition(); + double r = (double)fd.Radius; + if (r > 0) + { + if (!found || r < minR) minR = r; + found = true; + } + } + } + catch { } + feat = feat.GetNextFeature(); + } + m.MinFilletReadable = found; + m.MinFilletRadiusMM = found ? minR * 1000 : -1; + } + catch { } + return m; + } + + private void ReadAssemblyInfo(dynamic d, DocSnapshot snap) + { + AssemblyInfo asm = snap.Asm; + try + { + dynamic comps = d.GetComponents(false); + if (comps is object[]) + { + asm.TopLevelCount = ((object[])comps).Length; + CollectComponentNames(comps, asm.OccurrenceNames); + } + } + catch { } + try + { + object comps = d.GetComponents(false); + object pComp = null, pFace = null; + d.ToolsCheckInterference2(0, comps, true, out pComp, out pFace); + if (pComp is object[]) + { + int n = ((object[])pComp).Length; + if (n > 0) + { + asm.InterferenceState = 1; + for (int i = 0; i < n && i < 50; i++) + { + InterferencePair pair = new InterferencePair(); + try + { + dynamic c1 = ((object[])pComp)[i]; + dynamic c2 = null; + if (pFace is object[] && i < ((object[])pFace).Length) + c2 = ((object[])pFace)[i]; + try { pair.Name1 = Convert.ToString(c1.Name2); } catch { } + try { pair.Name2 = Convert.ToString(c2.Name2); } catch { } + asm.Interferences.Add(pair); + } + catch { } + } + } + } + } + catch (Exception ex) + { + asm.InterferenceState = 2; + asm.InterferenceError = ex.Message; + } + } + + private void CollectComponentNames(object comps, List names) + { + try + { + foreach (object c in (object[])comps) + { + try + { + dynamic comp = c; + names.Add(Convert.ToString(comp.Name2)); + object children = comp.GetChildren(); + if (children is object[] && ((object[])children).Length > 0) + CollectComponentNames(children, names); + } + catch { } + } + } + catch { } + } + + private void ReadDrawingInfo(dynamic d, DocSnapshot snap) + { + try + { + object sheetNamesObj = d.GetSheetNames(); + string[] sheetNames = null; + if (sheetNamesObj is string[]) sheetNames = (string[])sheetNamesObj; + if (sheetNamesObj is object[]) sheetNames = Array.ConvertAll((object[])sheetNamesObj, o => Convert.ToString(o)); + if (sheetNames == null || sheetNames.Length == 0) return; + foreach (string sn in sheetNames) + { + SheetInfo sh = new SheetInfo(); + sh.Name = sn; + try + { + d.ActivateSheet(sn); + dynamic views = d.GetViews(); + if (views is object[]) + { + sh.ViewCount = ((object[])views).Length; + foreach (object v in (object[])views) + { + try + { + dynamic view = v; + object notes = view.GetNotes(); + if (notes is object[]) + { + foreach (object n in (object[])notes) + { + try { sh.NotesTexts.Add(Convert.ToString(((dynamic)n).GetText())); } catch { } + } + } + } + catch { } + } + } + } + catch { } + snap.Sheets.Add(sh); + } + } + catch { } + } + + private void ReadOpenDocs(DocSnapshot snap) + { + try + { + object docs = mSwApp.GetDocuments(); + if (!(docs is object[])) return; + foreach (object d in (object[])docs) + { + DocInfo info = new DocInfo(); + dynamic dd = d; + try { info.Name = Convert.ToString(dd.GetTitle()); } catch { } + try + { + int t = (int)dd.GetType(); + if (t != 1) continue; + } + catch { continue; } + info.Props = ReadProps(dd); + info.Material = AppState.GetProp(info.Props, "Material", "材料", "材质"); + if (info.Material == "") info.Material = ReadMaterial(dd); + snap.OpenDocs.Add(info); + } + } + catch { } + } + } +} diff --git a/AddIn/src/SolidWorksLink/SwAddinServer.cs b/AddIn/src/SolidWorksLink/SwAddinServer.cs new file mode 100644 index 0000000..b30008b --- /dev/null +++ b/AddIn/src/SolidWorksLink/SwAddinServer.cs @@ -0,0 +1,283 @@ +using System; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using SolidWorks.Interop.sldworks; +using SolidWorks.Interop.swpublished; +using DesignExpress.Core; + +[assembly: ComVisible(true)] + +namespace DesignExpress.SolidWorksLink +{ + // SolidWorks 加载项外壳: 实现 ISwAddin, 延迟创建 CommandManager 选项卡, 回调全部转到 Core + [Guid("4A7E023E-EDD8-4642-B337-425E02394C81")] + [ComVisible(true)] + [ClassInterface(ClassInterfaceType.AutoDual)] + [ProgId("DesignExpress.SolidWorksLink.SwAddin")] + public class SwAddin : ISwAddin + { + private const string AddinClsid = "{4A7E023E-EDD8-4642-B337-425E02394C81}"; + private const string Title = "Design Express Res"; + private const int GroupId = 23130; + private ISldWorks mSwApp; + private int mCookie; + private DSldWorksEvents_Event mSwEvents; + private ICommandGroup mCmdGroup; + private int[] mCmdIndexes; + private readonly object mTabLock = new object(); + + static SwAddin() + { + // COM 激活的程序集依赖按宿主进程目录探测, 手动解析同目录的 Core.dll + AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs args) + { + try + { + string name = new AssemblyName(args.Name).Name; + if (name == "DesignExpress.Core") + { + string dir = Path.GetDirectoryName(typeof(SwAddin).Assembly.Location); + string path = Path.Combine(dir, "DesignExpress.Core.dll"); + if (File.Exists(path)) return Assembly.LoadFrom(path); + } + } + catch { } + return null; + }; + } + + public bool ConnectToSW(object ThisSW, int cookie) + { + try + { + mSwApp = (ISldWorks)ThisSW; + mCookie = cookie; + AppState.Host = new SolidWorksHost(mSwApp); + // 关键: SW 启动早期命令系统未就绪, 命令组/选项卡延迟到文档激活事件创建 + try + { + mSwEvents = (DSldWorksEvents_Event)mSwApp; + mSwEvents.ActiveDocChangeNotify += OnActiveDocChange; + } + catch { } + EnsureAll(); + return true; + } + catch { return false; } + } + + private int OnActiveDocChange() + { + EnsureAll(); + return 0; + } + + private void EnsureAll() + { + lock (mTabLock) + { + try + { + if (mCmdGroup == null) + { + SetupCommandGroup(); + Log("SetupCommandGroup: " + (mCmdGroup != null ? "OK" : "NULL")); + } + if (mCmdGroup != null) + { + // 每次文档激活都幂等确保选项卡存在(文档切换时 SW 可能清除 addin 选项卡) + AddCommandManagerTabs(); + } + MakeTabVisible(); + } + catch (Exception ex) { Log("EnsureAll 异常: " + ex.Message); } + } + } + + public bool DisconnectFromSW() + { + Log("DisconnectFromSW 被调用!"); + try + { + ICommandManager cmdMgr = mSwApp.GetCommandManager(mCookie); + cmdMgr.RemoveCommandGroup2(GroupId, true); + } + catch { } + return true; + } + + // 启用回调: 返回 1 = 命令始终可用 + public int EnableMethod() { return 1; } + + // 创建命令组+命令项(菜单可用), 选项卡延迟创建 + private void SetupCommandGroup() + { + try + { + ICommandManager cmdMgr = mSwApp.GetCommandManager(mCookie); + mSwApp.SetAddinCallbackInfo(0, this, mCookie); + + string iconList = ""; + try { iconList = SwIcons.EnsureIcons(); } catch { } + + ICommandGroup grp = cmdMgr.CreateCommandGroup(GroupId, Title, Title, "", -1); + if (grp == null) return; + + if (iconList != "") + { + try { grp.LargeIconList = iconList; } catch { } + try { grp.SmallIconList = iconList; } catch { } + } + + int[] idx = new int[9]; + idx[0] = grp.AddCommandItem2("零件检查", -1, "对当前零件进行三维标注、公差配合、GMP 检查", "零件检查", 0, "PartCheck", "EnableMethod", 0, 1); + idx[1] = grp.AddCommandItem2("装配检查", -1, "对当前装配进行 GMP、密封、干涉、外购件检查", "装配检查", 1, "AssemblyCheck", "EnableMethod", 1, 1); + idx[2] = grp.AddCommandItem2("工程图检查", -1, "对当前工程图进行标注与 spec 技术要求检查", "工程图检查", 2, "DrawingCheck", "EnableMethod", 2, 1); + idx[3] = grp.AddCommandItem2("导出报告", -1, "导出检查报告(Excel/CSV)", "导出报告", 3, "ExportReport", "EnableMethod", 3, 1); + idx[4] = grp.AddCommandItem2("使用说明", -1, "查看检查项清单与使用说明", "使用说明", 4, "Help", "EnableMethod", 4, 1); + idx[5] = grp.AddCommandItem2("卡簧槽查询", -1, "轴用/孔用弹性挡圈槽尺寸查询 (GB/T 894)", "卡簧槽查询", 5, "CirclipQuery", "EnableMethod", 5, 1); + idx[6] = grp.AddCommandItem2("键槽查询", -1, "平键键槽尺寸查询 (GB/T 1095)", "键槽查询", 6, "KeywayQuery", "EnableMethod", 6, 1); + idx[7] = grp.AddCommandItem2("O型密封圈查询", -1, "O 型密封圈设计与沟槽尺寸查询 (GB/T 3452.1)", "O型密封圈查询", 7, "OringQuery", "EnableMethod", 7, 1); + idx[8] = grp.AddCommandItem2("轴承挡圈查询", -1, "轴承挡圈与挡边设计查询", "轴承挡圈查询", 8, "BearingRingQuery", "EnableMethod", 8, 1); + + try { grp.HasToolbar = true; } catch { } + try { grp.HasMenu = true; } catch { } + grp.Activate(); + + mCmdGroup = grp; + mCmdIndexes = idx; + } + catch { } + } + + // 文档激活后创建 CommandManager 选项卡(SW 命令系统就绪后) + private void AddCommandManagerTabs() + { + try + { + ICommandManager cmdMgr = mSwApp.GetCommandManager(mCookie); + foreach (int docType in new int[] { 1, 2, 3 }) + { + try + { + ICommandTab cmdTab = cmdMgr.GetCommandTab(docType, Title); + if (cmdTab == null) + { + cmdTab = cmdMgr.AddCommandTab(docType, Title); + } + if (cmdTab != null) + { + // 幂等: 已有命令则不重复添加, 避免按钮重复 + int boxCount = cmdTab.GetCommandTabBoxCount(); + bool hasCommands = false; + if (boxCount > 0) + { + object boxes = cmdTab.CommandTabBoxes(); + if (boxes is object[]) + { + foreach (object b in (object[])boxes) + { + try + { + dynamic box = b; + object ids = null, tts = null; + if (box.GetCommands(out ids, out tts) > 0) hasCommands = true; + } + catch { } + } + } + } + if (hasCommands) + { + Log("docType=" + docType + " 已有命令, 跳过"); + } + else + { + ICommandTabBox cmdBox = cmdTab.AddCommandTabBox(); + int[] cmdIDs = new int[mCmdIndexes.Length]; + int[] textType = new int[mCmdIndexes.Length]; + for (int i = 0; i < mCmdIndexes.Length; i++) + { + cmdIDs[i] = mCmdGroup.CommandID[mCmdIndexes[i]]; + textType[i] = 2; // swCommandTabButton_TextBelow + } + cmdBox.AddCommands(cmdIDs, textType); + Log("docType=" + docType + " AddCommands 9 个"); + } + } + } + catch { } + } + } + catch { } + } + + // 文档扩展: 设置选项卡可见并激活(仅运行时创建时可能需强制显示) + private void MakeTabVisible() + { + try + { + ModelDoc2 doc = mSwApp.IActiveDoc2; + if (doc == null) { Log("MakeTabVisible: 无文档"); return; } + ModelDocExtension ext = doc.Extension; + if (ext == null) return; + object tabs = ext.GetCommandTabs(); + if (tabs is string[]) + { + string[] names = (string[])tabs; + Log("GetCommandTabs 共 " + names.Length + " 个"); + for (int i = 0; i < names.Length; i++) + { + Log(" Tab[" + i + "]=" + names[i]); + if (names[i] == Title) + { + ext.set_CommandTabVisible(i, true); + try + { + ICommandManager cmdMgr = mSwApp.GetCommandManager(mCookie); + dynamic ourTab = cmdMgr.GetCommandTab(1, Title); + if (ourTab != null) { ourTab.Active = true; Log("tab.Active=true"); } + else { Log("GetCommandTab 返回 null"); } + } + catch (Exception ex) { Log("tab.Active 失败: " + ex.Message); } + try { ext.ActiveCommandTab = Title; Log("ActiveCommandTab 已设"); } catch (Exception ex) { Log("ActiveCommandTab 失败: " + ex.Message); } + return; + } + } + Log("未找到 " + Title); + } + } + catch (Exception ex) { Log("MakeTabVisible 异常: " + ex.Message); } + } + + private static void Log(string msg) + { + try + { + System.IO.File.AppendAllText( + System.IO.Path.Combine(System.IO.Path.GetTempPath(), "DesignExpress_SW.log"), + DateTime.Now.ToString("HH:mm:ss.fff") + " " + msg + System.Environment.NewLine); + } + catch { } + } + + // ============ 命令回调(公开方法, SW 通过 IDispatch 调用) ============ + + public void PartCheck() { Safe(Checks.PartCheck); } + public void AssemblyCheck() { Safe(Checks.AssemblyCheck); } + public void DrawingCheck() { Safe(Checks.DrawingCheck); } + public void ExportReport() { Safe(Checks.ExportReport); } + public void Help() { Safe(Checks.Help); } + public void CirclipQuery() { Safe(Checks.CirclipQuery); } + public void KeywayQuery() { Safe(Checks.KeywayQuery); } + public void OringQuery() { Safe(Checks.OringQuery); } + public void BearingRingQuery() { Safe(Checks.BearingRingQuery); } + + private static void Safe(Action action) + { + try { action(); } + catch (Exception ex) { System.Windows.Forms.MessageBox.Show("执行出错: " + ex.Message); } + } + } +} diff --git a/AddIn/src/SolidWorksLink/SwIcons.cs b/AddIn/src/SolidWorksLink/SwIcons.cs new file mode 100644 index 0000000..0407184 --- /dev/null +++ b/AddIn/src/SolidWorksLink/SwIcons.cs @@ -0,0 +1,34 @@ +using System; +using System.IO; +using DesignExpress.Core; + +namespace DesignExpress.SolidWorksLink +{ + // SolidWorks 命令图标条: 复用 Core.ToolIcons(仓鼠品牌+功能符号), 生成 9 格 BMP + public static class SwIcons + { + public static string EnsureIcons() + { + try + { + string dir = Path.GetDirectoryName(typeof(SwIcons).Assembly.Location); + string file = Path.Combine(dir, "DesignExpress_icons.bmp"); + if (File.Exists(file)) return file; + ToolIcon[] icons = new ToolIcon[] + { + ToolIcon.Part, // 零件检查 + ToolIcon.Asm, // 装配检查 + ToolIcon.Drw, // 工程图检查 + ToolIcon.Export, // 导出报告 + ToolIcon.Help, // 使用说明 + ToolIcon.Circlip, // 卡簧槽查询 + ToolIcon.Keyway, // 键槽查询 + ToolIcon.Oring, // O型密封圈查询 + ToolIcon.Bearing // 轴承挡圈查询 + }; + return ToolIcons.CreateStripBmp(icons, 32, file); + } + catch { return ""; } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..7e16c96 --- /dev/null +++ b/README.md @@ -0,0 +1,113 @@ +# Design Express Res — CAD 设计标注检查与设计数据查询工具 + +基于 **Core + Link 架构**(类似 COMSOL LiveLink):独立核心库与 CAD 完全解耦,Inventor / SolidWorks 各自通过 Link 适配层调用 Core 功能。 + +## 架构 + +``` +┌────────────────────────────────────────────┐ +│ DesignExpress.Core.dll (独立核心, 不依赖CAD) │ +│ - KB 知识库 (GB/T 275/1800/1801/894/1095) │ +│ - CheckEngine 检查引擎 (P/A/D 三阶段) │ +│ - DesignQuery 设计数据查询 (键槽/卡簧槽已实装)│ +│ - UI: MainForm/QueryForm/FitForm │ +│ - Report 报告 (Excel/CSV) │ +│ - ICadHost 抽象接口 + DocSnapshot 快照 │ +│ - ToolIcons 共享图标(仓鼠品牌+功能符号) │ +└────────────────────────────────────────────┘ + ▲ ▲ + InventorLink SolidWorksLink + (已交付) (已交付) +``` + +- **ICadHost 接口**:`GetActiveDocument / GetDocType / Capture / PickCylinderDiameterMM / SetStatusText` +- **DocSnapshot 快照**:Link 层把 CAD 文档状态拍成纯数据,Core 只处理快照 +- 新增 CAD 支持 = 实现一个 ICadHost + 该 CAD 的选项卡壳 + +## 功能 + +| 系统 | 内容 | +|------|------| +| 设计检查 | 零件(标注/公差配合GB/T 275/形位/粗糙度/螺纹/物料接触GMP/表面处理/强度指引)、装配(GMP材料/密封/清洁/静态干涉/外购件/电气西门子/动力)、工程图(图框/视图/尺寸公差/22项spec关键词检查) | +| 设计数据查询 | 键槽(GB/T 1095)、卡簧槽(GB/T 894.1)已实装; O型密封圈(GB/T 3452.1)、轴承挡圈规划中 | + +## 部署 + +### Inventor +1. 复制 `AddIn/deploy` 文件夹到目标机器 +2. 右键 `install.ps1` → **使用 PowerShell 运行**(无需管理员) +3. 重启 Inventor,功能区出现 **[Design Express Res]** 选项卡 + +### SolidWorks +1. 复制 `AddIn/deploy` 文件夹 +2. 运行 `install_solidworks.ps1`(用户级注册) +3. **首次需管理员执行两条 reg add**(脚本末尾有提示): + - `HKLM\SOFTWARE\SolidWorks\AddIns\{4A7E023E-...}` 键(default=1, Title) +4. 启动 SolidWorks → 工具→插件 勾选 Design Express Res +5. 打开零件 → 出现选项卡 → **正常退出并重启一次**(持久化选项卡定义) + +## 文件清单 + +| 路径 | 说明 | +|------|------| +| `AddIn/deploy/DesignExpress.Core.dll` | 独立核心(成品) | +| `AddIn/deploy/DesignExpress.InventorLink.dll` | Inventor 适配层(成品) | +| `AddIn/deploy/DesignExpress.SolidWorksLink.dll` | SolidWorks 适配层(成品) | +| `AddIn/deploy/install.ps1` | Inventor 一键部署脚本 | +| `AddIn/deploy/install_solidworks.ps1` | SolidWorks 部署脚本 | +| `AddIn/src/Core/*.cs` | 核心源码(ICadHost/Snapshots/KB/CheckEngine/DesignQuery/UI/ToolIcons) | +| `AddIn/src/InventorLink/*.cs` | Inventor 适配源码 | +| `AddIn/src/SolidWorksLink/*.cs` | SolidWorks 适配源码 | + +## 关键技术点 + +### Inventor +- **按钮点击**:`ButtonDefinition.OnExecute` 事件(不依赖 ExecuteCommand 路由) +- **加载规则**:`AddInLoadRules` 二进制文件控制加载,install.ps1 自动修复(DWORD=0 允许加载) + +### SolidWorks +- **延迟创建**:命令组/选项卡在**文档激活事件**(`ActiveDocChangeNotify`)中创建——SW 启动早期命令系统未就绪,ConnectToSW 时创建会静默失败 +- **EnableMethod 回调**:命令必须提供启用方法(返回 1),否则 SW 禁用命令导致按钮不可见 +- **tab.Active = true**:选项卡可见性 + 激活必须直接设置,否则不显示在选项卡栏 +- **持久化**:SW 正常退出时把选项卡定义写入注册表,重启后读取显示 +- **GetDocType 注意**:不能 `dynamic.GetType()`(解析成 Object.GetType),必须编译时调用 + +### 通用 +- **窗口**:1/4 屏幕面积,TableLayoutPanel 百分比布局,无固定尺寸不遮罩 +- **报告**:Excel COM 动态调用,未装 Excel 回退 CSV +- **图标**:ToolIcons 共享生成(功能符号集中式),SolidWorks 用 9 格 BMP 图标条;Inventor 因 AddButtonDefinition 对 IPictureDisp 兼容性问题暂用文字按钮(可后续手动补图标) + +## 建议的 iProperties 自定义属性 + +| 属性名 | 取值示例 | 用途 | +|--------|----------|------| +| 物料接触 | 直接接触 / 间接接触 / 非接触 | 触发 GMP 专项检查 | +| 表面处理 | 镀镍+磨削 / 抛光 / 发黑 | 表面处理检查 | +| 材料 | 316L / 304 / PTFE | GMP 材料判定 | + +## 编译 + +```powershell +# Core (无 CAD 依赖) +csc /target:library /platform:x64 /codepage:65001 /out:DesignExpress.Core.dll ^ + /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:Microsoft.CSharp.dll /r:System.Core.dll ^ + src\Core\*.cs + +# Inventor Link +csc /target:library /platform:x64 /codepage:65001 /out:DesignExpress.InventorLink.dll ^ + /r:DesignExpress.Core.dll /r:"\Bin\Public Assemblies\Autodesk.Inventor.Interop.dll" ^ + /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:Microsoft.CSharp.dll /r:System.Core.dll ^ + src\InventorLink\*.cs + +# SolidWorks Link +csc /target:library /platform:x64 /codepage:65001 /out:DesignExpress.SolidWorksLink.dll ^ + /r:DesignExpress.Core.dll /r:"\SolidWorks.Interop.sldworks.dll" ^ + /r:"\SolidWorks.Interop.swpublished.dll" /r:"\SolidWorks.Interop.swconst.dll" ^ + /r:System.Windows.Forms.dll /r:System.Drawing.dll /r:Microsoft.CSharp.dll /r:System.Core.dll ^ + src\SolidWorksLink\*.cs +``` + +## 版本历史 + +- **1.3.0** (2026-08-21): SolidWorks Link 交付; 功能图标(集中式功能符号); 生产版代码清理 +- **1.3.1** (2026-08-22): 清理调试代码与临时文件; Inventor 图标方案(IPictureDisp)因 API 兼容性暂缓, 保留文字按钮