commit ba67ff7da743ff28f6f5d22a833873c515146c69 Author: liuyu65 Date: Sat Aug 22 17:20:02 2026 +0800 Design Express Res initial commit diff --git a/AddIn/deploy/DesignExpress.Core.dll b/AddIn/deploy/DesignExpress.Core.dll new file mode 100644 index 0000000..1fe1559 Binary files /dev/null and b/AddIn/deploy/DesignExpress.Core.dll differ diff --git a/AddIn/deploy/DesignExpress.InventorLink.dll b/AddIn/deploy/DesignExpress.InventorLink.dll new file mode 100644 index 0000000..222a875 Binary files /dev/null and b/AddIn/deploy/DesignExpress.InventorLink.dll differ diff --git a/AddIn/deploy/DesignExpress.SolidWorksLink.dll b/AddIn/deploy/DesignExpress.SolidWorksLink.dll new file mode 100644 index 0000000..dc1e60d Binary files /dev/null and b/AddIn/deploy/DesignExpress.SolidWorksLink.dll differ diff --git a/AddIn/deploy/install.ps1 b/AddIn/deploy/install.ps1 new file mode 100644 index 0000000..edbd6ee --- /dev/null +++ b/AddIn/deploy/install.ps1 @@ -0,0 +1,83 @@ +# Design Express Res 部署脚本: 复制 DLL + 注册每用户 COM + 生成 .addin (无需管理员权限) +# 架构: DesignExpress.Core.dll(独立核心) + DesignExpress.InventorLink.dll(Inventor 适配) +$ErrorActionPreference = "Stop" + +$invVer = "2022" +$addinDir = Join-Path $env:APPDATA "Autodesk\Inventor $invVer\Addins" +$clsid = "{D422E0BF-02D0-4DAE-958A-7BBA80B718E9}" +$progId = "DesignExpress.InventorLink.AddInServer" +$asmName = "DesignExpress.InventorLink, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" + +$srcCore = Join-Path $PSScriptRoot "DesignExpress.Core.dll" +$srcLink = Join-Path $PSScriptRoot "DesignExpress.InventorLink.dll" +if (-not (Test-Path $srcCore)) { Write-Host "未找到 DesignExpress.Core.dll"; exit 1 } +if (-not (Test-Path $srcLink)) { Write-Host "未找到 DesignExpress.InventorLink.dll"; exit 1 } + +New-Item -ItemType Directory -Force -Path $addinDir | Out-Null +Copy-Item -LiteralPath $srcCore -Destination (Join-Path $addinDir "DesignExpress.Core.dll") -Force +$dstLink = Join-Path $addinDir "DesignExpress.InventorLink.dll" +Copy-Item -LiteralPath $srcLink -Destination $dstLink -Force +Write-Host "DLL 已复制: $addinDir" + +# 每用户 COM 注册 (HKCU\Software\Classes) +$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 +Write-Host "COM 注册完成 (HKCU)" + +# 生成 .addin +$addinXml = @" + + $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 兼容性暂缓, 保留文字按钮