dsh-engineering-ui 0.0.0-stage → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +772 -3
  3. package/engineering/plugins/dsh-engineering-ui/cordis.patch.yml +5 -0
  4. package/engineering/plugins/dsh-engineering-ui/icon.svg +14 -0
  5. package/engineering/plugins/dsh-engineering-ui/lib/client.js +8259 -0
  6. package/engineering/plugins/dsh-engineering-ui/lib/defense-sign.js +1552 -0
  7. package/engineering/plugins/dsh-engineering-ui/lib/index.js +5633 -0
  8. package/engineering/plugins/dsh-engineering-ui/lib/job-wait-guard.js +236 -0
  9. package/engineering/plugins/dsh-engineering-ui/lib/mem-guard.js +331 -0
  10. package/engineering/plugins/dsh-engineering-ui/lib/webots.js +114 -0
  11. package/engineering/plugins/dsh-engineering-ui/locale/en.json +6 -0
  12. package/engineering/plugins/dsh-engineering-ui/locale/zh.json +6 -0
  13. package/engineering/plugins/dsh-engineering-ui/preset-engineering.patch.yml +601 -0
  14. package/engineering/skills/assembly-orchestration/SKILL.md +651 -0
  15. package/engineering/skills/cad-workflow/SKILL.md +132 -0
  16. package/engineering/skills/mode-selection/SKILL.md +660 -0
  17. package/engineering/skills/physics-in-loop/SKILL.md +238 -0
  18. package/engineering/skills/physics-in-loop/references/feadocs.md +142 -0
  19. package/engineering/skills/solidworks-bridge/SKILL.md +445 -0
  20. package/engineering/skills/sw-design/SKILL.md +112 -0
  21. package/engineering/skills/sw-design/references/autocad-workflow.md +81 -0
  22. package/engineering/skills/sw-design/references/gbt-drafting.md +71 -0
  23. package/engineering/skills/sw-to-cad/SKILL.md +121 -0
  24. package/engineering/skills/webots-simulation/SKILL.md +58 -0
  25. package/engineering/tools/LICENSE.Auto-SolidWorks +21 -0
  26. package/engineering/tools/_plugin_paths.py +93 -0
  27. package/engineering/tools/_root.py +330 -0
  28. package/engineering/tools/_store.py +446 -0
  29. package/engineering/tools/ac_bridge.py +459 -0
  30. package/engineering/tools/ac_rules.json +21 -0
  31. package/engineering/tools/ac_validate.py +699 -0
  32. package/engineering/tools/ask_user.py +887 -0
  33. package/engineering/tools/build_arm1.py +46 -0
  34. package/engineering/tools/build_assembly.py +94 -0
  35. package/engineering/tools/build_base.py +57 -0
  36. package/engineering/tools/build_housing.py +70 -0
  37. package/engineering/tools/build_structural.py +43 -0
  38. package/engineering/tools/build_transmission.py +69 -0
  39. package/engineering/tools/cadx.lsp +128 -0
  40. package/engineering/tools/choice_contract.py +61 -0
  41. package/engineering/tools/conn_state.py +1671 -0
  42. package/engineering/tools/defense_gate.py +3374 -0
  43. package/engineering/tools/drawing_input.py +314 -0
  44. package/engineering/tools/env_check.py +467 -0
  45. package/engineering/tools/examples/build_axis.py +142 -0
  46. package/engineering/tools/examples/build_cup.py +56 -0
  47. package/engineering/tools/examples/build_plate.py +58 -0
  48. package/engineering/tools/examples/build_sphere.py +30 -0
  49. package/engineering/tools/examples/build_transition_duct.py +381 -0
  50. package/engineering/tools/gate_integrity.py +401 -0
  51. package/engineering/tools/mode2_flow.py +3589 -0
  52. package/engineering/tools/mode_checker.py +77 -0
  53. package/engineering/tools/mode_gate.py +8013 -0
  54. package/engineering/tools/part_registry.py +4266 -0
  55. package/engineering/tools/physics/__init__.py +50 -0
  56. package/engineering/tools/physics/_compat.py +12 -0
  57. package/engineering/tools/physics/design_state.py +142 -0
  58. package/engineering/tools/physics/domain_validator.py +751 -0
  59. package/engineering/tools/physics/fatigue.py +489 -0
  60. package/engineering/tools/physics/fea_solver.py +1163 -0
  61. package/engineering/tools/physics/feapy_solver.py +314 -0
  62. package/engineering/tools/physics/gb_materials.json +1228 -0
  63. package/engineering/tools/physics/geometry_gate.py +337 -0
  64. package/engineering/tools/physics/load_case.py +951 -0
  65. package/engineering/tools/physics/material_db.py +874 -0
  66. package/engineering/tools/physics/mesh_adapter.py +321 -0
  67. package/engineering/tools/physics/refine_rules.py +272 -0
  68. package/engineering/tools/physics/rules/gb_base_rules.json +377 -0
  69. package/engineering/tools/physics/rules/gb_drafting_rules.json +57 -0
  70. package/engineering/tools/physics/rules/gb_electrical_rules.json +25 -0
  71. package/engineering/tools/physics/rules/gb_fasteners_rules.json +73 -0
  72. package/engineering/tools/physics/rules/gb_housing_rules.json +41 -0
  73. package/engineering/tools/physics/rules/gb_manufacturing_rules.json +25 -0
  74. package/engineering/tools/physics/rules/gb_material_verify_rules.json +72 -0
  75. package/engineering/tools/physics/rules/gb_materials_rules.json +377 -0
  76. package/engineering/tools/physics/rules/gb_safety_rules.json +89 -0
  77. package/engineering/tools/physics/rules/gb_springs_rules.json +41 -0
  78. package/engineering/tools/physics/rules/gb_standards_registry.json +1646 -0
  79. package/engineering/tools/physics/rules/gb_structural_rules.json +201 -0
  80. package/engineering/tools/physics/rules/gb_surface_rules.json +25 -0
  81. package/engineering/tools/physics/rules/gb_transmission_rules.json +233 -0
  82. package/engineering/tools/physics/rules/housing_rules.json +48 -0
  83. package/engineering/tools/physics/rules/manifest.json +54 -0
  84. package/engineering/tools/physics/rules/mold_rules.json +48 -0
  85. package/engineering/tools/physics/rules/structural_rules.json +56 -0
  86. package/engineering/tools/physics/rules/transmission_rules.json +48 -0
  87. package/engineering/tools/physics/simulation_report.py +829 -0
  88. package/engineering/tools/physics_bridge.py +2685 -0
  89. package/engineering/tools/profile-manifest.ps1 +72 -0
  90. package/engineering/tools/run_regression.py +109 -0
  91. package/engineering/tools/settings.py +419 -0
  92. package/engineering/tools/solidworks-modeling.md +519 -0
  93. package/engineering/tools/sw_bridge.py +8215 -0
  94. package/engineering/tools/sw_guard.py +1066 -0
  95. package/engineering/tools/sw_surface.py +87 -0
  96. package/engineering/tools/swapi.py +14766 -0
  97. package/engineering/tools/verify_sync.py +218 -0
  98. package/engineering/tools/visual_check.py +2589 -0
  99. package/engineering/tools/webots_env.py +121 -0
  100. package/engineering/tools/webots_mcp/__init__.py +3 -0
  101. package/engineering/tools/webots_mcp/__main__.py +3 -0
  102. package/engineering/tools/webots_mcp/assembly.py +391 -0
  103. package/engineering/tools/webots_mcp/bootstrap.py +55 -0
  104. package/engineering/tools/webots_mcp/controller_worker.py +124 -0
  105. package/engineering/tools/webots_mcp/evidence.py +288 -0
  106. package/engineering/tools/webots_mcp/fake_worker.py +69 -0
  107. package/engineering/tools/webots_mcp/fault_resources.py +121 -0
  108. package/engineering/tools/webots_mcp/host.py +45 -0
  109. package/engineering/tools/webots_mcp/ledger.py +73 -0
  110. package/engineering/tools/webots_mcp/owned_job.py +70 -0
  111. package/engineering/tools/webots_mcp/physical_gate.py +187 -0
  112. package/engineering/tools/webots_mcp/physics.py +152 -0
  113. package/engineering/tools/webots_mcp/pipeline.py +108 -0
  114. package/engineering/tools/webots_mcp/recipes.py +129 -0
  115. package/engineering/tools/webots_mcp/repair.py +132 -0
  116. package/engineering/tools/webots_mcp/repair_trial.py +375 -0
  117. package/engineering/tools/webots_mcp/runtime.py +311 -0
  118. package/engineering/tools/webots_mcp/server.py +217 -0
  119. package/engineering/tools/webots_mcp/sw_export.py +509 -0
  120. package/engineering/tools/webots_mcp/transport.py +156 -0
  121. package/engineering/tools/workflow_gate.py +6134 -0
  122. package/package.json +64 -4
@@ -0,0 +1,467 @@
1
+ # -*- coding: utf-8 -*-
2
+ r"""env_check.py —— 工程模式【本机环境自检】(设置页「工程环境检查」的后端)
3
+
4
+ ═══════════════════════════════════════════════════════════════════════════
5
+ 【为什么需要它】
6
+ 用户在学校微机房实测到的现象,本质是同一件事:
7
+ 工程模式默认"这台电脑什么都有",一旦缺 Python 3.8+(64 位)、缺 pywin32、
8
+ 缺 numpy,或者 SW/CAD 装在了别的盘 —— 表现就变成"某些设置静默失效",
9
+ 而界面不会告诉用户少了什么。
10
+
11
+ 用户的要求(原话):
12
+ 「在设置 → 工程模式中,连接区上面再加一个『检查电脑是否有对应的环境』,
13
+ 然后写一段提示词自己发给新的对话,新的 Agent 对话就自己去查看看少啥,
14
+ 自己安装。」
15
+
16
+ 【本模块提供什么】
17
+ 1) probe() —— 一次性把依赖查清楚,返回结构化结论
18
+ (ok / warn / missing,每一项都带"怎么修")
19
+ 2) install_prompt() —— 生成一段**可直接发给新对话**的提示词:
20
+ 把缺失项、期望版本、可用命令、验收标准全部写进去,
21
+ 新 Agent 照着做就行,不需要用户自己翻译报错。
22
+ 3) 交付目录 / 软件路径等"本机设置"的读写入口(转发到 settings.py)。
23
+
24
+ 【为什么不用 import 探测就下结论】
25
+ 微机房最容易骗人的一件事:import win32com 在宿主进程里可能成功
26
+ (宿主用的是随包 Python),但真正跑 sw_bridge.py 的那个解释器根本没有。
27
+ 所以这里对 Python 的检查一律【用目标解释器起子进程跑一段探针】,
28
+ 而不是在当前进程里 try/import。
29
+ ═══════════════════════════════════════════════════════════════════════════
30
+ """
31
+ from __future__ import annotations
32
+
33
+ import json
34
+ import os
35
+ import re
36
+ import subprocess
37
+ import sys
38
+
39
+ HERE = os.path.dirname(os.path.abspath(__file__))
40
+
41
+ # ── 依赖清单(单一事实源:UI 文案、提示词、自检都读它)────────────────────
42
+ DEPS = [
43
+ {"id": "python", "name": "Python 解释器(≥ 3.8,64 位)",
44
+ "why": "按 README 的最低要求检查;工具使用 annotations、dataclasses、"
45
+ "subprocess.run(capture_output=True) 等较新语法与标准库接口。"},
46
+ {"id": "pywin32", "name": "pywin32(SolidWorks / AutoCAD 的 COM 自动化)",
47
+ "why": "没有它就完全无法驱动 SolidWorks 与 AutoCAD。",
48
+ "kind": "module", "module": "win32com.client", "pip": "pywin32"},
49
+ {"id": "numpy", "name": "numpy(有限元求解 Level 1)",
50
+ "why": "物理在环的纯 Python FEA(CST/CTE 单元)依赖它;"
51
+ "缺了只能退回 Level 0 解析解,薄壁件判定会变粗。",
52
+ "kind": "module", "module": "numpy", "pip": "numpy"},
53
+ {"id": "ezdxf", "name": "ezdxf(DXF 解析 / CADX 几何验证)",
54
+ "why": "cad-validate 的 7 项检查需要读 DXF;缺了 CADX 验证直接不可用。",
55
+ "kind": "module", "module": "ezdxf", "pip": "ezdxf"},
56
+ {"id": "shapely", "name": "shapely(几何布尔运算)",
57
+ "why": "DXF 重叠/相交检查依赖它;缺了该项检查会被跳过。",
58
+ "kind": "module", "module": "shapely", "pip": "shapely"},
59
+ {"id": "PIL", "name": "Pillow + mss(截图 / 看图矫正)",
60
+ "why": "「建模后必须看图」这条门禁靠截图证据;缺了截图链路会降级。",
61
+ "kind": "module", "module": "PIL", "pip": "Pillow mss"},
62
+ ]
63
+
64
+ PROBE_CODE = r'''
65
+ import json, sys
66
+ out = {"ok": True, "version": "%d.%d.%d" % sys.version_info[:3],
67
+ "executable": sys.executable, "prefix": sys.prefix,
68
+ "bits": 64 if sys.maxsize > 2**32 else 32,
69
+ "modules": {}, "error": None}
70
+ for mod, shown in (("win32com.client", "pywin32"), ("numpy", "numpy"),
71
+ ("ezdxf", "ezdxf"), ("shapely", "shapely"),
72
+ ("PIL", "PIL"), ("mss", "mss")):
73
+ try:
74
+ __import__(mod)
75
+ out["modules"][shown] = {"ok": True, "version": None}
76
+ except Exception as e:
77
+ out["modules"][shown] = {"ok": False, "error": repr(e)[:200]}
78
+ if sys.version_info < (3, 8):
79
+ out["ok"] = False
80
+ out["error"] = "Python 版本过低(需要 3.8+,当前 %d.%d)" % sys.version_info[:2]
81
+ elif out["bits"] != 64:
82
+ out["ok"] = False
83
+ out["error"] = "需要 64 位 Python,当前为 32 位"
84
+ print(json.dumps(out))
85
+ '''
86
+
87
+
88
+ # ══════════════════════════════════════════════════════════════════════════
89
+ # 解释器发现
90
+ # ══════════════════════════════════════════════════════════════════════════
91
+
92
+ def _candidates():
93
+ """候选解释器(去重保序)。
94
+
95
+ 优先使用本机设置、环境变量和当前解释器;其后先试 py -3,
96
+ 避免只认 PATH 里排在前面的旧版本。
97
+ """
98
+ out = []
99
+ try:
100
+ import settings as _settings
101
+ _p = _settings.python_exe()
102
+ if _p:
103
+ out.append(_p)
104
+ except Exception:
105
+ pass
106
+ v = (os.environ.get("DSH_PYTHON") or "").strip().strip('"')
107
+ if v:
108
+ out.append(v)
109
+ out.extend([sys.executable, "py", "python", "python3"])
110
+ seen, uniq = set(), []
111
+ for c in out:
112
+ if not c:
113
+ continue
114
+ key = str(c).lower()
115
+ if key in seen:
116
+ continue
117
+ seen.add(key)
118
+ uniq.append(c)
119
+ return uniq
120
+
121
+
122
+ def _run_probe(exe):
123
+ """用某个解释器跑探针脚本,返回 (ok, data, raw_error)。"""
124
+ args = [exe]
125
+ if os.path.basename(exe).lower() in ("py", "py.exe"):
126
+ args.append("-3")
127
+ try:
128
+ r = subprocess.run(args + ["-c", PROBE_CODE],
129
+ capture_output=True, text=True, timeout=25)
130
+ except Exception as e:
131
+ return False, None, "无法执行 %r: %r" % (exe, e)
132
+ stdout = (r.stdout or "").strip()
133
+ stderr = (r.stderr or "").strip()
134
+ if not stdout:
135
+ # 典型旧版 Python:语法错误直接死在 stderr,stdout 空 ——
136
+ # 这正是微机房"插件解析失败"的现场。
137
+ return False, None, (stderr or
138
+ "无输出(可能是 Python 版本过低,语法错误只打到 stderr)")[-600:]
139
+ try:
140
+ data = json.loads(stdout.splitlines()[-1])
141
+ except Exception:
142
+ return False, None, "探针输出不是 JSON: %s" % stdout[-300:]
143
+ return bool(data.get("ok")), data, (data.get("error") or stderr[-600:] or None)
144
+
145
+
146
+ def probe_python():
147
+ """挑一个能满足最低要求的解释器,并返回它的模块状况。"""
148
+ tried = []
149
+ for exe in _candidates():
150
+ ok, data, err = _run_probe(exe)
151
+ tried.append({"exe": exe, "version": (data or {}).get("version"),
152
+ "ok": bool(ok), "error": err})
153
+ if ok and data:
154
+ return {"ok": True, "chosen": data.get("executable"),
155
+ "version": data.get("version"),
156
+ "bits": data.get("bits"),
157
+ "executable": data.get("executable"),
158
+ "modules": data.get("modules") or {},
159
+ "tried": tried, "note": None}
160
+ return {"ok": False, "chosen": None, "version": None, "modules": {},
161
+ "tried": tried,
162
+ "error": "没有找到可用的 Python 3.8+(64 位)解释器",
163
+ "note": ("候选全部失败。PATH 中的 Python 可能版本过低或为 32 位。"
164
+ "解决:在设置页「工程环境检查」里指定 python.exe,"
165
+ "或安装 Python 3.11/3.12 并勾选 Add to PATH。")}
166
+
167
+
168
+ # ══════════════════════════════════════════════════════════════════════════
169
+ # 软件路径 / 设置
170
+ # ══════════════════════════════════════════════════════════════════════════
171
+
172
+ def probe_sw_path():
173
+ mine = None
174
+ try:
175
+ import settings as _settings
176
+ mine = _settings.sw_exe()
177
+ except Exception:
178
+ pass
179
+ try:
180
+ import swapi
181
+ found = swapi.find_sldworks_exe()
182
+ except Exception as e:
183
+ return {"ok": bool(mine), "path": mine,
184
+ "error": "探测 swapi 失败: %r" % (e,)}
185
+ return {"ok": bool(found), "path": found or mine,
186
+ "from_settings": bool(mine),
187
+ "error": None if found else
188
+ "未找到 SLDWORKS.exe(注册表 / 常见安装目录 / 环境变量都没命中)"}
189
+
190
+
191
+ def probe_cad_path():
192
+ try:
193
+ import conn_state
194
+ p = conn_state._find_acad_exe()
195
+ except Exception as e:
196
+ return {"ok": False, "path": None, "error": "探测失败: %r" % (e,)}
197
+ return {"ok": bool(p), "path": p,
198
+ "error": None if p else "未找到 acad.exe(可选组件:缺了仍可出 SLDDRW)"}
199
+
200
+
201
+ def probe_settings():
202
+ try:
203
+ import settings as _settings
204
+ root, src = _settings.delivery_root()
205
+ rep = _settings.report()
206
+ writable = False
207
+ try:
208
+ os.makedirs(root, exist_ok=True)
209
+ probe_file = os.path.join(root, ".dsh_write_probe")
210
+ with open(probe_file, "w", encoding="utf-8") as f:
211
+ f.write("ok")
212
+ os.remove(probe_file)
213
+ writable = True
214
+ except Exception:
215
+ writable = False
216
+ return {"ok": bool(writable), "delivery_root": root,
217
+ "delivery_root_source": src, "writable": writable,
218
+ "file": rep.get("file"),
219
+ "ignored": [r for r in rep.get("paths", [])
220
+ if r.get("usable") is False],
221
+ "error": None if writable else ("交付目录不可写:%s" % root)}
222
+ except Exception as e:
223
+ return {"ok": False, "error": "读取设置失败: %r" % (e,)}
224
+
225
+
226
+ def probe_engine():
227
+ """工程模式自身文件是否完整(换机 / 半拷贝时很容易缺)。"""
228
+ need = ["sw_bridge.py", "swapi.py", "mode_gate.py", "workflow_gate.py",
229
+ "defense_gate.py", "physics_bridge.py", "conn_state.py", "settings.py"]
230
+ missing = [n for n in need if not os.path.isfile(os.path.join(HERE, n))]
231
+ return {"ok": not missing, "tools_dir": HERE, "missing": missing,
232
+ "error": ("缺少核心脚本:%s" % ", ".join(missing)) if missing else None}
233
+
234
+
235
+ # ══════════════════════════════════════════════════════════════════════════
236
+ # 汇总
237
+ # ══════════════════════════════════════════════════════════════════════════
238
+
239
+ def probe(full=True):
240
+ """完整自检。返回 {ok, status, items[], summary, counts}。
241
+
242
+ status: 'ok'(全绿)/ 'warn'(只缺可选项)/ 'bad'(缺必需项)
243
+ """
244
+ items = []
245
+
246
+ def add(item_id, name, state, detail, fix=None, optional=False):
247
+ items.append({"id": item_id, "name": name, "state": state,
248
+ "optional": bool(optional),
249
+ "detail": detail or "", "fix": fix or ""})
250
+
251
+ eng = probe_engine()
252
+ add("engine", "工程模式核心脚本", "ok" if eng["ok"] else "missing",
253
+ eng["tools_dir"] if eng["ok"] else eng.get("error"),
254
+ "重新运行 install-0.2.0.ps1 或 sync-to-dsh.ps1,把 tools/ 同步到安装副本")
255
+
256
+ py = probe_python()
257
+ if py["ok"]:
258
+ add("python", "Python 解释器", "ok",
259
+ "%s(%s,%s 位)" % (py["version"], py["chosen"], py["bits"]))
260
+ else:
261
+ add("python", "Python 解释器", "missing", py.get("error") or "未找到",
262
+ py.get("note") or "安装 Python 3.11/3.12 并加入 PATH,"
263
+ "或在设置里指定 python.exe")
264
+
265
+ mods = py.get("modules") or {}
266
+ for dep in DEPS:
267
+ if dep.get("kind") != "module":
268
+ continue
269
+ m = mods.get(dep["id"])
270
+ if m is None:
271
+ add(dep["id"], dep["name"], "unknown",
272
+ "无法确认(Python 解释器不可用)", "先解决 Python 解释器问题")
273
+ elif m.get("ok"):
274
+ add(dep["id"], dep["name"], "ok", "已安装")
275
+ else:
276
+ add(dep["id"], dep["name"], "missing", (m.get("error") or "导入失败"),
277
+ '-m pip install %s' % (dep.get("pip") or dep["id"]))
278
+
279
+ sw = probe_sw_path()
280
+ add("solidworks", "SolidWorks 安装路径",
281
+ "ok" if sw["ok"] else "missing",
282
+ sw.get("path") or sw.get("error"),
283
+ "在连接区点「SOLIDWORKS连接」重新探测;"
284
+ "或在「工程环境检查」里手动指定 SLDWORKS.exe")
285
+ cad = probe_cad_path()
286
+ add("autocad", "AutoCAD 安装路径",
287
+ "ok" if cad["ok"] else "warn",
288
+ cad.get("path") or cad.get("error"),
289
+ "可选:在连接区点「AutoCAD 连接」探测;缺少时仍可输出 SLDDRW / PDF",
290
+ optional=True)
291
+
292
+ st = probe_settings()
293
+ add("delivery_root", "交付目录",
294
+ "ok" if st.get("ok") else "missing",
295
+ "%s(来源:%s)" % (st.get("delivery_root"),
296
+ st.get("delivery_root_source")),
297
+ "在「工程环境检查」里把交付目录改到一个可写的位置(例如 D:/dsh-out)")
298
+ ignored = st.get("ignored") or []
299
+ if ignored:
300
+ add("stale_paths", "跨机器残留设置", "warn",
301
+ ";".join("%s=%s(%s)" % (r["key"], r["value"], r.get("note") or "")
302
+ for r in ignored),
303
+ "这些设置来自另一台电脑,已自动忽略;可点「清空本机路径设置」重来")
304
+
305
+ state = "ok"
306
+ if any(i["state"] == "missing" and not i["optional"] for i in items):
307
+ state = "bad"
308
+ elif any(i["state"] in ("missing", "warn", "unknown") for i in items):
309
+ state = "warn"
310
+
311
+ return {
312
+ "ok": state != "bad",
313
+ "status": state,
314
+ "items": items,
315
+ "missing": [i for i in items
316
+ if i["state"] in ("missing", "unknown") and not i["optional"]],
317
+ "summary": ("环境完整,可以直接建模" if state == "ok" else
318
+ ("缺少可选项,部分功能会降级" if state == "warn" else
319
+ "缺少必需项,工程模式无法正常建模")),
320
+ "counts": {"ok": len([i for i in items if i["state"] == "ok"]),
321
+ "warn": len([i for i in items
322
+ if i["state"] in ("warn", "unknown")]),
323
+ "missing": len([i for i in items if i["state"] == "missing"])},
324
+ "python": {"chosen": py.get("chosen"), "version": py.get("version"),
325
+ "tried": py.get("tried")},
326
+ "full": bool(full),
327
+ }
328
+
329
+
330
+ # ══════════════════════════════════════════════════════════════════════════
331
+ # 提示词生成(用户要的「写一段提示词自己发给新的对话」)
332
+ # ══════════════════════════════════════════════════════════════════════════
333
+
334
+ _PROMPT_HEAD = """【工程模式 · 本机环境修复任务】
335
+
336
+ 你正在一台可能缺少依赖的电脑上,为 DSH「工程模式」预设做环境修复。
337
+ 请按下面清单逐项处理,不要跳过、不要假设"应该已经装了"。
338
+
339
+ ## 一、当前自检结论(由 tools/env_check.py 实测生成)
340
+ 状态:{status_cn}
341
+ {summary}
342
+
343
+ ## 二、必须处理的问题
344
+
345
+ """
346
+
347
+ _PROMPT_TAIL = """
348
+ ## 三、执行规则(请严格遵守)
349
+
350
+ 1. **先确认解释器,再装包。** 需要 Python 3.8+(64 位)。
351
+ 安装依赖必须使用自检选中的实际解释器,避免装到另一个 Python。装包前先跑:
352
+ ```powershell
353
+ & "{python}" -c "import sys;print(sys.version);print(sys.executable);print(64 if sys.maxsize > 2**32 else 32)"
354
+ ```
355
+ **所有 pip 安装都必须使用同一个合格解释器**:
356
+ ```powershell
357
+ & "{python}" -m pip install --upgrade pip
358
+ & "{python}" -m pip install {pip_pkgs}
359
+ ```
360
+ 2. **装完必须回填。** 若 py -3 可用而 python 不可用,请把这件事告诉我,
361
+ 我会把它固定成本机设置(settings.py set python_exe <路径>),
362
+ 这样插件和门禁脚本用的是同一个解释器,不会再出现"装了但没生效"。
363
+ 3. **每装完一项就复验**,不要全装完再一次性验证:
364
+ ```powershell
365
+ & "{python}" "{tools}\\env_check.py" probe
366
+ ```
367
+ 4. **SolidWorks / AutoCAD 路径**:若自检说找不到,先点设置页连接区的「连接」;
368
+ 仍不行就在「工程环境检查」里手动指定可执行文件。**不要全盘扫描 C: 盘根目录**。
369
+ 5. **交付目录**:默认不再落在桌面(以前会凭空建一个 test 文件夹)。
370
+ 请在设置页「工程环境检查」里把交付目录设到一个明确、可写的位置。
371
+ 6. 全部处理完后,最后再跑一次完整自检,并把结论贴给我:
372
+ ```powershell
373
+ & "{python}" "{tools}\\env_check.py" probe
374
+ ```
375
+
376
+ ## 四、验收标准(做不到就是没修好)
377
+
378
+ - env_check.py probe 的 status 必须是 ok(或只剩 AutoCAD 这类可选项);
379
+ - sw_bridge.py doctor 不再报 Python 语法错误 / 缺 pywin32;
380
+ - 设置页「工程环境检查」里所有必需项都是绿色。
381
+ """
382
+
383
+
384
+ def _guess_python():
385
+ try:
386
+ py = probe_python()
387
+ return py.get("chosen") or py.get("executable")
388
+ except Exception:
389
+ return None
390
+
391
+
392
+ def install_prompt(result=None, tools_dir=None):
393
+ """生成可直接粘贴给新对话的修复提示词。"""
394
+ r = result or probe()
395
+ items = r.get("items") or []
396
+ tools = tools_dir or HERE
397
+ lines, pip_pkgs = [], []
398
+ idx = 0
399
+ for it in items:
400
+ if it["state"] not in ("missing", "warn", "unknown"):
401
+ continue
402
+ idx += 1
403
+ tag = "可选项" if it.get("optional") else "必需"
404
+ lines.append("### %d. %s(%s)" % (idx, it["name"], tag))
405
+ if it.get("detail"):
406
+ lines.append("- 现状:%s" % it["detail"])
407
+ if it.get("fix"):
408
+ lines.append("- 处理:%s" % it["fix"])
409
+ lines.append("")
410
+ m = re.search(r"-m pip install\s+(.+)$", it.get("fix") or "")
411
+ if m:
412
+ pip_pkgs.append(m.group(1).strip())
413
+ if not lines:
414
+ lines = ["(无 —— 当前环境已完整,无需处理任何项。)", ""]
415
+ status_cn = {"ok": "完整", "warn": "有可选项缺失",
416
+ "bad": "缺少必需项"}.get(r.get("status"), str(r.get("status")))
417
+ head = _PROMPT_HEAD.format(status_cn=status_cn,
418
+ summary=r.get("summary") or "")
419
+ tail = _PROMPT_TAIL.format(
420
+ pip_pkgs=" ".join(dict.fromkeys(pip_pkgs)) if pip_pkgs else "<包名>",
421
+ python=(_guess_python() or "python"),
422
+ tools=tools)
423
+ return head + "\n".join(lines) + tail
424
+
425
+
426
+ # ══════════════════════════════════════════════════════════════════════════
427
+ # CLI
428
+ # ══════════════════════════════════════════════════════════════════════════
429
+
430
+ def _print(obj):
431
+ print(json.dumps(obj, ensure_ascii=False, indent=2))
432
+
433
+
434
+ def _main(argv):
435
+ cmd = (argv[0] if argv else "probe").lower()
436
+ if cmd in ("probe", "check", "status"):
437
+ res = probe(full=True)
438
+ _print(res)
439
+ return 0 if res.get("ok") else 1
440
+ if cmd in ("prompt", "install-prompt"):
441
+ res = probe(full=True)
442
+ text = install_prompt(res) # 代码围栏已在模块常量里就位,无需二次替换
443
+ if "--json" in argv:
444
+ _print({"ok": True, "prompt": text, "status": res.get("status")})
445
+ else:
446
+ sys.stdout.write(text + "\n")
447
+ return 0
448
+ if cmd in ("set-delivery-root", "set_delivery_root") and len(argv) >= 2:
449
+ import settings as _settings
450
+ _print(_settings.set_path("delivery_root", argv[1]))
451
+ return 0
452
+ if cmd == "set-path" and len(argv) >= 3:
453
+ import settings as _settings
454
+ _print(_settings.set_path(argv[1], argv[2]))
455
+ return 0
456
+ if cmd == "settings":
457
+ import settings as _settings
458
+ _print(_settings.report())
459
+ return 0
460
+ _print({"ok": False,
461
+ "error": "用法: env_check.py probe | prompt [--json] | settings | "
462
+ "set-path <key> <path> | set-delivery-root <dir>"})
463
+ return 2
464
+
465
+
466
+ if __name__ == "__main__":
467
+ sys.exit(_main(sys.argv[1:]))
@@ -0,0 +1,142 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 示例2:五段阶梯轴(旋转成型 + 倒角 + 键槽)—— 教学重点
4
+ ========================================================
5
+ 设计需求(单位 mm,以左端面 x=0 为基准,轴沿 +X 方向):
6
+ 轴段1: Φ30 × 30 (x: 0 ~ 30)
7
+ 轴段2: Φ36 × 6 (x: 30 ~ 36)
8
+ 轴段3: Φ46.494 × 40 (x: 36 ~ 76),两端开倒角 C1×45°
9
+ 轴段4: Φ35 × 74 (x: 76 ~ 150)
10
+ 轴段5: Φ30 × 36 (x: 150 ~ 186)
11
+ 总长 = 186 mm
12
+
13
+ 附加特征:
14
+ A. 轴最左端 (x=0) 倒角 C1×45°
15
+ B. 轴最右端 (x=186) 倒角 C1×45°
16
+ C. 第四段右端 (x=150) 台阶倒角 C1×45°
17
+ D. 第四段右端往左 20mm 处 (中心 x=130) 开键槽:
18
+ 直槽口 总长 35、宽 10(半圆半径 5)
19
+ 深度: 键槽底面到过轴心的平行平面距离 12.5
20
+
21
+ 【通用版改动】选中键槽草图不再写死中文名"草图2",
22
+ 改用 swapi.select_sketch_by_index 按特征树序号选择(与语言无关)。
23
+
24
+ 执行: python sw_bridge.py run examples\build_axis.py
25
+ (或在 DSH 里说:"建模一个五段阶梯轴,Φ30×30 / Φ36×6 / Φ46.494×40 /
26
+ Φ35×74 / Φ30×36,总长186,第四段右侧开键槽宽10圆心距25、
27
+ 右侧圆心距右端面7.5,保存为 DSH_五段阶梯轴")
28
+ """
29
+ import os
30
+ import math
31
+
32
+ import win32com.client
33
+ import pythoncom
34
+
35
+ import swapi
36
+
37
+ # ---- 半径表(直径/2)----
38
+ R1 = 30 / 2.0 # 15
39
+ R2 = 36 / 2.0 # 18
40
+ R3 = 46.494 / 2.0 # 23.247
41
+ R4 = 35 / 2.0 # 17.5
42
+ R5 = 30 / 2.0 # 15
43
+
44
+ X1, X2, X3, X4, X5 = 30, 36, 76, 150, 186
45
+
46
+ # ---- 1. 新建零件 ----
47
+ m = swapi.new_part()
48
+ print("STEP1 new part:", m.title)
49
+
50
+ # ---- 2. 前视基准面:画旋转轮廓 + 中心线 ----
51
+ m.begin_sketch("Front Plane")
52
+ m.polyline([
53
+ (0, R1), (X1, R1), (X1, R2), (X2, R2), (X2, R3), (X3, R3),
54
+ (X3, R4), (X4, R4), (X4, R5), (X5, R5), (X5, 0), (0, 0),
55
+ ])
56
+ m.line(0, 0, 0, R1)
57
+ m.centerline(-20, 0, 206, 0)
58
+ m.end_sketch()
59
+
60
+ # ---- 3. 旋转 360° 成阶梯轴 ----
61
+ r = m.revolve(360)
62
+ print("STEP2 revolve ->", "OK" if r is not None else "FAIL")
63
+ if r is None:
64
+ raise RuntimeError("revolve failed")
65
+
66
+ # ---- 4. 轴段3 两端圆环棱边倒角 C1×45° ----
67
+ m.clear_selection()
68
+ c1 = m.chamfer(1, [(X2, R3, 0)], angle_deg=45)
69
+ print("STEP3 chamfer seg3 left C1 ->", "OK" if c1 is not None else "FAIL")
70
+ m.clear_selection()
71
+ c2 = m.chamfer(1, [(X3, R3, 0)], angle_deg=45)
72
+ print("STEP4 chamfer seg3 right C1 ->", "OK" if c2 is not None else "FAIL")
73
+
74
+ # ---- 5. 轴最左端 (x=0) 端面棱边倒角 C1 ----
75
+ m.clear_selection()
76
+ c3 = m.chamfer(1, [(0, R1, 0)], angle_deg=45)
77
+ print("STEP5 chamfer left end C1 ->", "OK" if c3 is not None else "FAIL")
78
+
79
+ # ---- 8. 第四段键槽 ----
80
+ # 用户要求(实测确认的 API 语义):
81
+ # "键槽长度25" = 圆心距(中心距) 25
82
+ # "右侧圆心距离右端面 7.5" → 右圆心 x = 150 - 7.5 = 142.5
83
+ # 左圆心 x = 142.5 - 25 = 117.5
84
+ # 宽 10 → 半径 5;底面距过轴心平面 12.5
85
+ # 全长 = 圆心距 + 2R = 25 + 10 = 35
86
+ #
87
+ # 实测结论(读草图段验证):
88
+ # CreateSketchSlot(center_line=1, FullLength=1, 宽, X1, X2) 中:
89
+ # X1 = 槽中心,X2 = 右圆弧**圆心**(不是最外端点!)
90
+ # 传 (中心=130, 右圆心=142.5) → 圆弧圆心落在 117.5 和 142.5
91
+ # → 圆心距 25 OK,右圆心距右端面 7.5 OK,全长 35 OK
92
+ # 注意: CenterCenter 模式(0) 的 X1 被解释为槽左端,会生成错误几何 FAIL
93
+ KEY_W = 10.0 # 宽度(半径 R=5)
94
+ KEY_R = KEY_W / 2.0 # 5
95
+ KEY_CC = 25.0 # 圆心距(用户要求"长度25")
96
+ KEY_GAP_RIGHT = 7.5 # 右侧圆心距第四段右端面 7.5(用户明确)
97
+ KEY_RIGHT_C = X4 - KEY_GAP_RIGHT # 右圆心 x = 150 - 7.5 = 142.5
98
+ KEY_CENTER = KEY_RIGHT_C - KEY_CC / 2.0 # 槽中心 x = 130
99
+ KEY_OFFSET = 12.5 # 键槽底面距过轴心平面的距离
100
+
101
+ m.clear_selection()
102
+ m.begin_sketch("Front Plane")
103
+ MM = swapi.MM
104
+ m.skm.CreateSketchSlot(
105
+ 1, # swSketchSlotCreationType_center_line
106
+ 1, # swSketchSlotLengthType_FullLength(实测:X2=右圆心)
107
+ KEY_W * MM,
108
+ KEY_CENTER * MM, 0, 0, # 槽中心 (x, y, z)
109
+ KEY_RIGHT_C * MM, 0, 0, # 右圆弧圆心(实测语义)
110
+ 0, 0, 0,
111
+ 1, False)
112
+ m.end_sketch()
113
+ # 选中键槽草图(第二个草图)—— 通用版:按序号选,不依赖中文/英文名
114
+ m.model.ClearSelection2(True)
115
+ sel = swapi.select_sketch_by_index(m.sw, m.model, 2)
116
+ print("STEP8 select keyway sketch ->", "OK" if sel else "FAIL")
117
+ if not sel:
118
+ raise RuntimeError("cannot select keyway sketch by index 2")
119
+ k = m.fm.FeatureCut3(
120
+ True, False, False, 1, 0, 0.0, 0.0, # 单向, 完全贯穿
121
+ False, False, False, False, 0, 0,
122
+ False, False, False, False, False, False, True,
123
+ False, False, False, # 自动选择
124
+ 3, KEY_OFFSET * MM, True) # 起始=等距12.5, 方向向外
125
+ print("STEP8 keyway: 圆心距%d 右圆心x=%.1f(距右端面%.1f) 中心x=%.1f ->"
126
+ % (KEY_CC, KEY_RIGHT_C, X4 - KEY_RIGHT_C, KEY_CENTER),
127
+ "OK" if k is not None else "FAIL")
128
+
129
+ # ---- 6. 轴最右端 (x=186) 端面棱边倒角 C1 ----
130
+ m.clear_selection()
131
+ c4 = m.chamfer(1, [(X5, R5, 0)], angle_deg=45)
132
+ print("STEP6 chamfer right end C1 ->", "OK" if c4 is not None else "FAIL")
133
+
134
+ # ---- 7. 第四段右端 (x=150) 台阶棱边倒角 C1 ----
135
+ m.clear_selection()
136
+ c5 = m.chamfer(1, [(X4, R4, 0)], angle_deg=45)
137
+ print("STEP7 chamfer seg4 right C1 ->", "OK" if c5 is not None else "FAIL")
138
+
139
+ # ---- 9. 保存 ----
140
+ out = os.path.join(os.path.dirname(os.path.abspath(__file__)),
141
+ "DSH_五段阶梯轴.sldprt")
142
+ print("STEP9 save:", m.save(out))
@@ -0,0 +1,56 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ 示例3:杯子(拉伸外壁 + 切除内腔 + 杯口倒角)
4
+ ==============================================
5
+ 设计(单位 mm):
6
+ 杯身外径 D80, 高 100
7
+ 内腔直径 D70, 深 95(杯底厚 5)
8
+ 杯口内外倒角 C2
9
+
10
+ 执行: python sw_bridge.py run examples\build_cup.py
11
+ (或在 DSH 里说:"建模一个马克杯,外径 80、高 100、壁厚 3、底部厚 4,
12
+ 保存为 DSH_杯子")
13
+
14
+ 本示例完全通用:不依赖任何本机路径,保存到脚本所在目录。
15
+ """
16
+ import os
17
+ import swapi
18
+
19
+ D_OUT = 80.0 # 杯身外径
20
+ H = 100.0 # 杯高
21
+ D_IN = 70.0 # 内腔直径
22
+ BOTTOM = 5.0 # 杯底厚
23
+ DEPTH = H - BOTTOM # 内腔深度 95
24
+
25
+ m = swapi.new_part()
26
+ print("STEP1 new part:", m.title)
27
+
28
+ # ---- 2. 杯身外圆柱 D80×100 ----
29
+ m.begin_sketch("Front Plane")
30
+ m.circle(0, 0, D_OUT / 2.0)
31
+ m.end_sketch()
32
+ m.extrude(H)
33
+ print("STEP2 cup body D%.0f x %.0f OK" % (D_OUT, H))
34
+
35
+ # ---- 3. 切除内腔 D70×95(顶面画圆,向下切 95,留底 5)----
36
+ m.begin_sketch_on_face(0, 0, H)
37
+ m.circle(0, 0, D_IN / 2.0)
38
+ m.end_sketch()
39
+ m.cut(depth=DEPTH)
40
+ print("STEP3 cavity D%.0f x %.0f OK" % (D_IN, DEPTH))
41
+
42
+ # ---- 4. 杯口外沿倒角 C2(顶面外圆棱边 z=H)----
43
+ # 顶面外棱边: 半径 40 的圆, 取 y 方向极值点
44
+ m.clear_selection()
45
+ c1 = m.chamfer(2, [(0, D_OUT / 2.0, H)], angle_deg=45)
46
+ print("STEP4 rim outer chamfer C2 ->", "OK" if c1 is not None else "FAIL")
47
+
48
+ # ---- 5. 杯口内沿倒角 C2(顶面内圆棱边 z=H)----
49
+ m.clear_selection()
50
+ c2 = m.chamfer(2, [(0, D_IN / 2.0, H)], angle_deg=45)
51
+ print("STEP5 rim inner chamfer C2 ->", "OK" if c2 is not None else "FAIL")
52
+
53
+ # ---- 6. 保存(按用户提供的名称命名;不校验)----
54
+ out = os.path.join(os.path.dirname(os.path.abspath(__file__)),
55
+ "DSH_杯子.sldprt")
56
+ print("STEP6 save:", m.save(out))