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.
- package/LICENSE +21 -0
- package/README.md +772 -3
- package/engineering/plugins/dsh-engineering-ui/cordis.patch.yml +5 -0
- package/engineering/plugins/dsh-engineering-ui/icon.svg +14 -0
- package/engineering/plugins/dsh-engineering-ui/lib/client.js +8259 -0
- package/engineering/plugins/dsh-engineering-ui/lib/defense-sign.js +1552 -0
- package/engineering/plugins/dsh-engineering-ui/lib/index.js +5633 -0
- package/engineering/plugins/dsh-engineering-ui/lib/job-wait-guard.js +236 -0
- package/engineering/plugins/dsh-engineering-ui/lib/mem-guard.js +331 -0
- package/engineering/plugins/dsh-engineering-ui/lib/webots.js +114 -0
- package/engineering/plugins/dsh-engineering-ui/locale/en.json +6 -0
- package/engineering/plugins/dsh-engineering-ui/locale/zh.json +6 -0
- package/engineering/plugins/dsh-engineering-ui/preset-engineering.patch.yml +601 -0
- package/engineering/skills/assembly-orchestration/SKILL.md +651 -0
- package/engineering/skills/cad-workflow/SKILL.md +132 -0
- package/engineering/skills/mode-selection/SKILL.md +660 -0
- package/engineering/skills/physics-in-loop/SKILL.md +238 -0
- package/engineering/skills/physics-in-loop/references/feadocs.md +142 -0
- package/engineering/skills/solidworks-bridge/SKILL.md +445 -0
- package/engineering/skills/sw-design/SKILL.md +112 -0
- package/engineering/skills/sw-design/references/autocad-workflow.md +81 -0
- package/engineering/skills/sw-design/references/gbt-drafting.md +71 -0
- package/engineering/skills/sw-to-cad/SKILL.md +121 -0
- package/engineering/skills/webots-simulation/SKILL.md +58 -0
- package/engineering/tools/LICENSE.Auto-SolidWorks +21 -0
- package/engineering/tools/_plugin_paths.py +93 -0
- package/engineering/tools/_root.py +330 -0
- package/engineering/tools/_store.py +446 -0
- package/engineering/tools/ac_bridge.py +459 -0
- package/engineering/tools/ac_rules.json +21 -0
- package/engineering/tools/ac_validate.py +699 -0
- package/engineering/tools/ask_user.py +887 -0
- package/engineering/tools/build_arm1.py +46 -0
- package/engineering/tools/build_assembly.py +94 -0
- package/engineering/tools/build_base.py +57 -0
- package/engineering/tools/build_housing.py +70 -0
- package/engineering/tools/build_structural.py +43 -0
- package/engineering/tools/build_transmission.py +69 -0
- package/engineering/tools/cadx.lsp +128 -0
- package/engineering/tools/choice_contract.py +61 -0
- package/engineering/tools/conn_state.py +1671 -0
- package/engineering/tools/defense_gate.py +3374 -0
- package/engineering/tools/drawing_input.py +314 -0
- package/engineering/tools/env_check.py +467 -0
- package/engineering/tools/examples/build_axis.py +142 -0
- package/engineering/tools/examples/build_cup.py +56 -0
- package/engineering/tools/examples/build_plate.py +58 -0
- package/engineering/tools/examples/build_sphere.py +30 -0
- package/engineering/tools/examples/build_transition_duct.py +381 -0
- package/engineering/tools/gate_integrity.py +401 -0
- package/engineering/tools/mode2_flow.py +3589 -0
- package/engineering/tools/mode_checker.py +77 -0
- package/engineering/tools/mode_gate.py +8013 -0
- package/engineering/tools/part_registry.py +4266 -0
- package/engineering/tools/physics/__init__.py +50 -0
- package/engineering/tools/physics/_compat.py +12 -0
- package/engineering/tools/physics/design_state.py +142 -0
- package/engineering/tools/physics/domain_validator.py +751 -0
- package/engineering/tools/physics/fatigue.py +489 -0
- package/engineering/tools/physics/fea_solver.py +1163 -0
- package/engineering/tools/physics/feapy_solver.py +314 -0
- package/engineering/tools/physics/gb_materials.json +1228 -0
- package/engineering/tools/physics/geometry_gate.py +337 -0
- package/engineering/tools/physics/load_case.py +951 -0
- package/engineering/tools/physics/material_db.py +874 -0
- package/engineering/tools/physics/mesh_adapter.py +321 -0
- package/engineering/tools/physics/refine_rules.py +272 -0
- package/engineering/tools/physics/rules/gb_base_rules.json +377 -0
- package/engineering/tools/physics/rules/gb_drafting_rules.json +57 -0
- package/engineering/tools/physics/rules/gb_electrical_rules.json +25 -0
- package/engineering/tools/physics/rules/gb_fasteners_rules.json +73 -0
- package/engineering/tools/physics/rules/gb_housing_rules.json +41 -0
- package/engineering/tools/physics/rules/gb_manufacturing_rules.json +25 -0
- package/engineering/tools/physics/rules/gb_material_verify_rules.json +72 -0
- package/engineering/tools/physics/rules/gb_materials_rules.json +377 -0
- package/engineering/tools/physics/rules/gb_safety_rules.json +89 -0
- package/engineering/tools/physics/rules/gb_springs_rules.json +41 -0
- package/engineering/tools/physics/rules/gb_standards_registry.json +1646 -0
- package/engineering/tools/physics/rules/gb_structural_rules.json +201 -0
- package/engineering/tools/physics/rules/gb_surface_rules.json +25 -0
- package/engineering/tools/physics/rules/gb_transmission_rules.json +233 -0
- package/engineering/tools/physics/rules/housing_rules.json +48 -0
- package/engineering/tools/physics/rules/manifest.json +54 -0
- package/engineering/tools/physics/rules/mold_rules.json +48 -0
- package/engineering/tools/physics/rules/structural_rules.json +56 -0
- package/engineering/tools/physics/rules/transmission_rules.json +48 -0
- package/engineering/tools/physics/simulation_report.py +829 -0
- package/engineering/tools/physics_bridge.py +2685 -0
- package/engineering/tools/profile-manifest.ps1 +72 -0
- package/engineering/tools/run_regression.py +109 -0
- package/engineering/tools/settings.py +419 -0
- package/engineering/tools/solidworks-modeling.md +519 -0
- package/engineering/tools/sw_bridge.py +8215 -0
- package/engineering/tools/sw_guard.py +1066 -0
- package/engineering/tools/sw_surface.py +87 -0
- package/engineering/tools/swapi.py +14766 -0
- package/engineering/tools/verify_sync.py +218 -0
- package/engineering/tools/visual_check.py +2589 -0
- package/engineering/tools/webots_env.py +121 -0
- package/engineering/tools/webots_mcp/__init__.py +3 -0
- package/engineering/tools/webots_mcp/__main__.py +3 -0
- package/engineering/tools/webots_mcp/assembly.py +391 -0
- package/engineering/tools/webots_mcp/bootstrap.py +55 -0
- package/engineering/tools/webots_mcp/controller_worker.py +124 -0
- package/engineering/tools/webots_mcp/evidence.py +288 -0
- package/engineering/tools/webots_mcp/fake_worker.py +69 -0
- package/engineering/tools/webots_mcp/fault_resources.py +121 -0
- package/engineering/tools/webots_mcp/host.py +45 -0
- package/engineering/tools/webots_mcp/ledger.py +73 -0
- package/engineering/tools/webots_mcp/owned_job.py +70 -0
- package/engineering/tools/webots_mcp/physical_gate.py +187 -0
- package/engineering/tools/webots_mcp/physics.py +152 -0
- package/engineering/tools/webots_mcp/pipeline.py +108 -0
- package/engineering/tools/webots_mcp/recipes.py +129 -0
- package/engineering/tools/webots_mcp/repair.py +132 -0
- package/engineering/tools/webots_mcp/repair_trial.py +375 -0
- package/engineering/tools/webots_mcp/runtime.py +311 -0
- package/engineering/tools/webots_mcp/server.py +217 -0
- package/engineering/tools/webots_mcp/sw_export.py +509 -0
- package/engineering/tools/webots_mcp/transport.py +156 -0
- package/engineering/tools/workflow_gate.py +6134 -0
- package/package.json +64 -4
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
ac_bridge.py — AutoCAD COM 桥接(DSH 工程模式通用版)
|
|
4
|
+
======================================================
|
|
5
|
+
从 CADX 提取的 AutoCAD COM 桥接模块,适配 DSH 工程模式使用。
|
|
6
|
+
|
|
7
|
+
功能:
|
|
8
|
+
- 连接运行中的 AutoCAD(COM 自动化)
|
|
9
|
+
- 导出 DXF 文件(供 AI 读取验证)
|
|
10
|
+
- 读取图纸实体(图层/线条/圆/文字/标注)
|
|
11
|
+
- 高亮验证问题(在 AutoCAD 中标记)
|
|
12
|
+
|
|
13
|
+
依赖:
|
|
14
|
+
- pywin32 (win32com, pythoncom)
|
|
15
|
+
- 与 sw_bridge.py 使用相同的连接模式(dynamic dispatch)
|
|
16
|
+
|
|
17
|
+
用法:
|
|
18
|
+
import ac_bridge
|
|
19
|
+
ac = ac_bridge.connect() # 连接 AutoCAD
|
|
20
|
+
dxf_path = ac.export_dxf() # 导出 DXF
|
|
21
|
+
entities = ac.get_entities() # 读取所有实体
|
|
22
|
+
issues = ac.validate() # 运行几何验证
|
|
23
|
+
|
|
24
|
+
【重要约束】
|
|
25
|
+
- COM 操作使用 dynamic dispatch(不依赖类型库注册)
|
|
26
|
+
- 坐标单位默认为 mm(与 swapi 一致)
|
|
27
|
+
- AutoCAD busy 时使用 ESC 取消挂起命令后重试
|
|
28
|
+
"""
|
|
29
|
+
import os
|
|
30
|
+
import sys
|
|
31
|
+
import time
|
|
32
|
+
import json
|
|
33
|
+
import subprocess
|
|
34
|
+
|
|
35
|
+
import pythoncom
|
|
36
|
+
import win32com.client
|
|
37
|
+
|
|
38
|
+
# Fix encoding on Windows terminals
|
|
39
|
+
if sys.platform == 'win32':
|
|
40
|
+
try:
|
|
41
|
+
sys.stdout.reconfigure(encoding='utf-8')
|
|
42
|
+
sys.stderr.reconfigure(encoding='utf-8')
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# ==================== 常量 ====================
|
|
48
|
+
_VALIDATION_LAYER = "DSH_CADX_VALIDATION"
|
|
49
|
+
_COLOR_CRITICAL = 1 # 红色
|
|
50
|
+
_COLOR_WARNING = 4 # 黄色
|
|
51
|
+
_COLOR_INFO = 3 # 绿色
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ==================== 连接 ====================
|
|
55
|
+
|
|
56
|
+
def _acad_process_running():
|
|
57
|
+
"""进程层探测 AutoCAD 是否已在运行(【连接自检修复】)。
|
|
58
|
+
|
|
59
|
+
为什么需要它:`Dispatch('AutoCAD.Application')` 在 AutoCAD 已安装但
|
|
60
|
+
未运行时【会触发 COM 激活 → 尝试启动 AutoCAD】,可能挂起数分钟,
|
|
61
|
+
甚至在安装/许可异常时把 CAD 弄崩(实测事故)。
|
|
62
|
+
先做一次纯进程探测(tasklist,与 sw_bridge 的进程探测手段一致),
|
|
63
|
+
没有 acad.exe 就一个 COM 调用都不发 ——
|
|
64
|
+
保证本模块【永不启动、也永不搞崩】用户的 AutoCAD。
|
|
65
|
+
|
|
66
|
+
Returns: True/False;探测手段不可用时返回 True(保守放行,
|
|
67
|
+
让 COM 层自己去报错,避免因探测失败而误判"CAD 没开")。
|
|
68
|
+
"""
|
|
69
|
+
try:
|
|
70
|
+
out = subprocess.run(
|
|
71
|
+
["tasklist", "/FI", "IMAGENAME eq acad.exe", "/FO", "CSV", "/NH"],
|
|
72
|
+
capture_output=True, text=True, timeout=15)
|
|
73
|
+
_txt = (out.stdout or "")
|
|
74
|
+
if "no tasks are running" in _txt.lower() or "没有运行的任务" in _txt:
|
|
75
|
+
return False
|
|
76
|
+
# tasklist 命中时输出 CSV 行,其中含 acad.exe
|
|
77
|
+
return "acad.exe" in _txt.lower()
|
|
78
|
+
except Exception:
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def connect(timeout=30):
|
|
83
|
+
"""连接运行中的 AutoCAD 实例。
|
|
84
|
+
|
|
85
|
+
返回 AutoCADBridge 对象;失败返回 None。
|
|
86
|
+
"""
|
|
87
|
+
pythoncom.CoInitialize()
|
|
88
|
+
bridge = AutoCADBridge()
|
|
89
|
+
if bridge._do_connect(timeout=timeout):
|
|
90
|
+
return bridge
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class AutoCADBridge:
|
|
95
|
+
"""AutoCAD COM 桥接。所有 COM 操作在调用线程上执行(单线程模式)。
|
|
96
|
+
|
|
97
|
+
与 sw_bridge.py / swapi.py 保持一致:使用 dynamic dispatch,
|
|
98
|
+
不依赖类型库注册。
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self):
|
|
102
|
+
self.acad = None
|
|
103
|
+
self.doc = None
|
|
104
|
+
self._connected = False
|
|
105
|
+
self._drawing_name = None
|
|
106
|
+
|
|
107
|
+
def _do_connect(self, timeout=30):
|
|
108
|
+
"""尝试连接 AutoCAD(带超时和重试)。
|
|
109
|
+
|
|
110
|
+
── 【连接自检修复·安全前置检查】 AutoCAD 未运行 → 直接失败,不激活 COM ──
|
|
111
|
+
原缺陷(实测事故):直接 `Dispatch('AutoCAD.Application')` 在
|
|
112
|
+
"AutoCAD 已安装但【未运行】"的机器上会触发 COM 激活 ——
|
|
113
|
+
Windows 会尝试【启动 AutoCAD】(含许可检查/启动画面)。
|
|
114
|
+
后果:
|
|
115
|
+
· 命令挂起数分钟(远超 30s 超时,因为激活本身阻塞在循环里);
|
|
116
|
+
· 若 AutoCAD 安装已损坏/许可异常,这次激活会把 CAD 【弄崩】;
|
|
117
|
+
· 用户并未要求启动 CAD,却被动收获一个崩掉的 CAD 进程。
|
|
118
|
+
修复:Dispatch 之前【先进程层探测】。没有 acad.exe 就直接返回 False,
|
|
119
|
+
一个 COM 调用都不发 —— 保证本模块【永远不会】启动或搞崩 AutoCAD
|
|
120
|
+
(这也与文档承诺一致:只挂接已运行实例,不自行启动)。
|
|
121
|
+
"""
|
|
122
|
+
# ── 安全前置检查:AutoCAD 没在跑就立刻放弃(绝不激活 COM)────────
|
|
123
|
+
if not _acad_process_running():
|
|
124
|
+
print("[ac_bridge] AutoCAD 未在运行 —— 不尝试激活 COM"
|
|
125
|
+
"(避免启动/搞崩 AutoCAD)。请手动启动 AutoCAD 后重试。")
|
|
126
|
+
self.doc = None
|
|
127
|
+
self.acad = None
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
deadline = time.time() + timeout
|
|
131
|
+
esc_sent = False
|
|
132
|
+
# 连续【非忙】失败计数:AutoCAD 在跑但 COM 始终连不上时快速失败,
|
|
133
|
+
# 不再把 30s 超时白白耗光(原来每轮 sleep 1s,最多空转 30 次)。
|
|
134
|
+
hard_fail = 0
|
|
135
|
+
while time.time() < deadline:
|
|
136
|
+
try:
|
|
137
|
+
pythoncom.CoInitialize()
|
|
138
|
+
self.acad = win32com.client.dynamic.Dispatch('AutoCAD.Application')
|
|
139
|
+
self.acad.Visible = True
|
|
140
|
+
self.doc = self.acad.ActiveDocument
|
|
141
|
+
# 触发一次属性读取以验证连接
|
|
142
|
+
_ = self.doc.Name
|
|
143
|
+
self._drawing_name = self.doc.Name
|
|
144
|
+
self._connected = True
|
|
145
|
+
print(f"[ac_bridge] Connected to AutoCAD — document: {self._drawing_name}")
|
|
146
|
+
return True
|
|
147
|
+
except pythoncom.com_error as e:
|
|
148
|
+
hr = getattr(e, 'hresult', None)
|
|
149
|
+
# RPC_E_CALL_REJECTED (-2147418111) = AutoCAD busy
|
|
150
|
+
if hr == -2147418111 and not esc_sent:
|
|
151
|
+
print("[ac_bridge] AutoCAD busy, sending ESC to cancel pending command...")
|
|
152
|
+
self._cancel_pending_command()
|
|
153
|
+
esc_sent = True
|
|
154
|
+
time.sleep(1.0)
|
|
155
|
+
elif hr == -2147418111:
|
|
156
|
+
delay = 0.5 * (2 ** min(int((deadline - time.time()) * 2), 4))
|
|
157
|
+
print(f"[ac_bridge] AutoCAD still busy, retrying in {delay:.1f}s")
|
|
158
|
+
time.sleep(delay)
|
|
159
|
+
pythoncom.PumpWaitingMessages()
|
|
160
|
+
else:
|
|
161
|
+
# 非"忙"类错误(服务器运行失败/被拒/许可等)→ 快速失败。
|
|
162
|
+
hard_fail += 1
|
|
163
|
+
print(f"[ac_bridge] COM error (attempt {hard_fail}): {e}")
|
|
164
|
+
self.doc = None
|
|
165
|
+
self.acad = None
|
|
166
|
+
if hard_fail >= 2:
|
|
167
|
+
print("[ac_bridge] 连续 COM 失败,放弃重试"
|
|
168
|
+
"(AutoCAD 可能正在启动或状态异常)。")
|
|
169
|
+
return False
|
|
170
|
+
time.sleep(1.0)
|
|
171
|
+
except Exception as e:
|
|
172
|
+
hard_fail += 1
|
|
173
|
+
print(f"[ac_bridge] Connection failed (attempt {hard_fail}): {e}")
|
|
174
|
+
self.doc = None
|
|
175
|
+
self.acad = None
|
|
176
|
+
if hard_fail >= 2:
|
|
177
|
+
return False
|
|
178
|
+
time.sleep(1.0)
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def connected(self):
|
|
183
|
+
return self._connected
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def drawing_name(self):
|
|
187
|
+
return self._drawing_name or ""
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def model_space(self):
|
|
191
|
+
if not self.doc:
|
|
192
|
+
raise RuntimeError("No active AutoCAD document")
|
|
193
|
+
return self.doc.ModelSpace
|
|
194
|
+
|
|
195
|
+
# ------------------------------------------------------------------
|
|
196
|
+
# 辅助方法
|
|
197
|
+
# ------------------------------------------------------------------
|
|
198
|
+
|
|
199
|
+
def _cancel_pending_command(self):
|
|
200
|
+
"""发送 ESC 取消 AutoCAD 挂起的命令。"""
|
|
201
|
+
try:
|
|
202
|
+
hwnd = self.acad.HWND
|
|
203
|
+
import ctypes
|
|
204
|
+
from ctypes import wintypes
|
|
205
|
+
user32 = ctypes.windll.user32
|
|
206
|
+
user32.SetForegroundWindow(hwnd)
|
|
207
|
+
time.sleep(0.3)
|
|
208
|
+
shell = win32com.client.dynamic.Dispatch("WScript.Shell")
|
|
209
|
+
for _ in range(5):
|
|
210
|
+
shell.SendKeys("{ESC}")
|
|
211
|
+
time.sleep(0.15)
|
|
212
|
+
time.sleep(1.0)
|
|
213
|
+
except Exception as e:
|
|
214
|
+
print(f"[ac_bridge] _cancel_pending_command: {e}")
|
|
215
|
+
|
|
216
|
+
def _point(self, x, y, z=0.0):
|
|
217
|
+
"""创建 COM 兼容的 3D 点数组。"""
|
|
218
|
+
return win32com.client.VARIANT(
|
|
219
|
+
pythoncom.VT_ARRAY | pythoncom.VT_R8, (float(x), float(y), float(z))
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# ------------------------------------------------------------------
|
|
223
|
+
# DXF 导出
|
|
224
|
+
# ------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
def export_dxf(self, output_path=None):
|
|
227
|
+
"""将当前图纸导出为 DXF 文件。
|
|
228
|
+
|
|
229
|
+
Args:
|
|
230
|
+
output_path: 输出路径(可选),默认保存到临时目录
|
|
231
|
+
Returns:
|
|
232
|
+
DXF 文件路径
|
|
233
|
+
|
|
234
|
+
Bug-27 修复: AutoCAD 2020 SaveAs 参数兼容性处理。
|
|
235
|
+
- AutoCAD 2020+: doc.SaveAs(filename, file_format)
|
|
236
|
+
- 旧版本: doc.SaveAs(filename)
|
|
237
|
+
自动尝试两种格式,失败时抛出明确错误信息。
|
|
238
|
+
"""
|
|
239
|
+
if not self.doc:
|
|
240
|
+
raise RuntimeError("No active AutoCAD document")
|
|
241
|
+
if output_path is None:
|
|
242
|
+
base = os.path.splitext(self.doc.Name)[0]
|
|
243
|
+
out_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "output")
|
|
244
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
245
|
+
output_path = os.path.join(out_dir, f"{base}_export.dxf")
|
|
246
|
+
abs_path = os.path.abspath(output_path)
|
|
247
|
+
|
|
248
|
+
# Bug-27: 尝试多种 SaveAs 调用方式以兼容不同 AutoCAD 版本
|
|
249
|
+
saved = False
|
|
250
|
+
errors = []
|
|
251
|
+
for try_name, try_args in [
|
|
252
|
+
("SaveAs(filename, format)", (abs_path, 18)), # AutoCAD 2018+
|
|
253
|
+
("SaveAs(filename)", (abs_path,)), # 兼容旧版本
|
|
254
|
+
("SaveAs2(format, filename)", (18, abs_path)), # 部分版本反序
|
|
255
|
+
]:
|
|
256
|
+
try:
|
|
257
|
+
getattr(self.doc, "SaveAs")(*try_args)
|
|
258
|
+
if os.path.exists(abs_path):
|
|
259
|
+
saved = True
|
|
260
|
+
break
|
|
261
|
+
errors.append(f"{try_name}: file not created")
|
|
262
|
+
except Exception as e:
|
|
263
|
+
errors.append(f"{try_name}: {e}")
|
|
264
|
+
|
|
265
|
+
if not saved:
|
|
266
|
+
raise RuntimeError(
|
|
267
|
+
f"DXF 导出失败({abs_path}),所有方法均失败:\n" +
|
|
268
|
+
"\n".join(f" - {e}" for e in errors) +
|
|
269
|
+
"\n请确认: 1) AutoCAD 已安装并打开图纸 2) 输出路径有写入权限"
|
|
270
|
+
)
|
|
271
|
+
return abs_path
|
|
272
|
+
|
|
273
|
+
# ------------------------------------------------------------------
|
|
274
|
+
# 实体读取
|
|
275
|
+
# ------------------------------------------------------------------
|
|
276
|
+
|
|
277
|
+
def get_entities(self):
|
|
278
|
+
"""读取 ModelSpace 中所有实体,返回列表。
|
|
279
|
+
|
|
280
|
+
每个实体包含:type, layer, handle, 几何数据(坐标/半径/面积等)。
|
|
281
|
+
"""
|
|
282
|
+
if not self.doc:
|
|
283
|
+
raise RuntimeError("No active AutoCAD document")
|
|
284
|
+
entities = []
|
|
285
|
+
ms = self.model_space
|
|
286
|
+
for i in range(ms.Count):
|
|
287
|
+
ent = ms.Item(i)
|
|
288
|
+
info = {
|
|
289
|
+
"index": i,
|
|
290
|
+
"type": ent.EntityName,
|
|
291
|
+
"layer": ent.Layer,
|
|
292
|
+
"handle": ent.Handle,
|
|
293
|
+
"color": ent.Color,
|
|
294
|
+
}
|
|
295
|
+
# LINE
|
|
296
|
+
try:
|
|
297
|
+
info["start"] = (ent.StartPoint[0], ent.StartPoint[1])
|
|
298
|
+
info["end"] = (ent.EndPoint[0], ent.EndPoint[1])
|
|
299
|
+
info["length"] = ent.Length
|
|
300
|
+
except Exception:
|
|
301
|
+
pass
|
|
302
|
+
# CIRCLE / ARC
|
|
303
|
+
try:
|
|
304
|
+
info["center"] = (ent.Center[0], ent.Center[1])
|
|
305
|
+
info["radius"] = ent.Radius
|
|
306
|
+
except Exception:
|
|
307
|
+
pass
|
|
308
|
+
# LWPOLYLINE / POLYLINE
|
|
309
|
+
try:
|
|
310
|
+
coords = list(ent.Coordinates)
|
|
311
|
+
points = [(coords[j], coords[j + 1]) for j in range(0, len(coords), 2)]
|
|
312
|
+
info["points"] = points
|
|
313
|
+
info["closed"] = bool(ent.Closed)
|
|
314
|
+
info["area"] = ent.Area
|
|
315
|
+
except Exception:
|
|
316
|
+
pass
|
|
317
|
+
# TEXT / MTEXT
|
|
318
|
+
try:
|
|
319
|
+
info["text"] = ent.TextString
|
|
320
|
+
except Exception:
|
|
321
|
+
pass
|
|
322
|
+
# DIMENSION
|
|
323
|
+
try:
|
|
324
|
+
info["measurement"] = ent.Measurement
|
|
325
|
+
except Exception:
|
|
326
|
+
pass
|
|
327
|
+
entities.append(info)
|
|
328
|
+
return entities
|
|
329
|
+
|
|
330
|
+
def get_entities_on_layer(self, layer_name):
|
|
331
|
+
"""获取指定图层上的所有实体。"""
|
|
332
|
+
return [e for e in self.get_entities() if e.get("layer") == layer_name]
|
|
333
|
+
|
|
334
|
+
def get_all_layers(self):
|
|
335
|
+
"""返回所有图层名称列表。"""
|
|
336
|
+
if not self.doc:
|
|
337
|
+
raise RuntimeError("No active AutoCAD document")
|
|
338
|
+
return [self.doc.Layers.Item(i).Name for i in range(self.doc.Layers.Count)]
|
|
339
|
+
|
|
340
|
+
# ------------------------------------------------------------------
|
|
341
|
+
# 图层管理
|
|
342
|
+
# ------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
def ensure_layer(self, name, color=7):
|
|
345
|
+
"""确保图层存在(不存在则创建),并设置颜色。"""
|
|
346
|
+
if not self.doc:
|
|
347
|
+
raise RuntimeError("No active AutoCAD document")
|
|
348
|
+
try:
|
|
349
|
+
self.doc.Layers.Add(name)
|
|
350
|
+
except pythoncom.com_error:
|
|
351
|
+
pass # 已存在
|
|
352
|
+
layer = self.doc.Layers.Item(name)
|
|
353
|
+
layer.Color = color
|
|
354
|
+
|
|
355
|
+
# ------------------------------------------------------------------
|
|
356
|
+
# 问题高亮
|
|
357
|
+
# ------------------------------------------------------------------
|
|
358
|
+
|
|
359
|
+
def highlight_issue(self, x, y, message, severity="CRITICAL"):
|
|
360
|
+
"""在指定坐标处绘制标记(圆圈 + 文字标注)。
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
x, y: 坐标(mm)
|
|
364
|
+
message: 问题描述
|
|
365
|
+
severity: CRITICAL / WARNING / INFO
|
|
366
|
+
"""
|
|
367
|
+
color_map = {
|
|
368
|
+
"CRITICAL": _COLOR_CRITICAL,
|
|
369
|
+
"WARNING": _COLOR_WARNING,
|
|
370
|
+
"INFO": _COLOR_INFO,
|
|
371
|
+
}
|
|
372
|
+
color = color_map.get(severity, _COLOR_CRITICAL)
|
|
373
|
+
self.ensure_layer(_VALIDATION_LAYER, color)
|
|
374
|
+
try:
|
|
375
|
+
center = self._point(x, y)
|
|
376
|
+
circle = self.model_space.AddCircle(center, 500.0)
|
|
377
|
+
circle.Layer = _VALIDATION_LAYER
|
|
378
|
+
circle.Color = color
|
|
379
|
+
# 文字标注(略高于标记)
|
|
380
|
+
text_pt = self._point(x, y + 600)
|
|
381
|
+
mtext = self.model_space.AddMText(text_pt, 5000.0, f"[{severity}] {message}")
|
|
382
|
+
mtext.Layer = _VALIDATION_LAYER
|
|
383
|
+
mtext.Color = color
|
|
384
|
+
mtext.Height = 200.0
|
|
385
|
+
except Exception as e:
|
|
386
|
+
print(f"[ac_bridge] highlight_issue failed: {e}")
|
|
387
|
+
|
|
388
|
+
def clear_validation_layer(self):
|
|
389
|
+
"""清除验证图层上的所有标记。"""
|
|
390
|
+
try:
|
|
391
|
+
self.send_command(f'-LAYDEL\nN\n{_VALIDATION_LAYER}\n\nY\n')
|
|
392
|
+
time.sleep(0.5)
|
|
393
|
+
self.ensure_layer(_VALIDATION_LAYER, _COLOR_CRITICAL)
|
|
394
|
+
except Exception as e:
|
|
395
|
+
print(f"[ac_bridge] clear_validation_layer: {e}")
|
|
396
|
+
|
|
397
|
+
# ------------------------------------------------------------------
|
|
398
|
+
# 绘图命令(供设计生成使用)
|
|
399
|
+
# ------------------------------------------------------------------
|
|
400
|
+
|
|
401
|
+
def send_command(self, command):
|
|
402
|
+
"""向 AutoCAD 发送命令字符串。"""
|
|
403
|
+
if not self.doc:
|
|
404
|
+
raise RuntimeError("No active AutoCAD document")
|
|
405
|
+
self.doc.SendCommand(command + "\n")
|
|
406
|
+
|
|
407
|
+
def draw_line(self, x1, y1, x2, y2, layer="0"):
|
|
408
|
+
"""画直线。"""
|
|
409
|
+
p1 = self._point(x1, y1)
|
|
410
|
+
p2 = self._point(x2, y2)
|
|
411
|
+
line = self.model_space.AddLine(p1, p2)
|
|
412
|
+
line.Layer = layer
|
|
413
|
+
return line
|
|
414
|
+
|
|
415
|
+
def draw_rectangle(self, x, y, width, height, layer="0"):
|
|
416
|
+
"""画矩形(闭合多段线)。"""
|
|
417
|
+
points = win32com.client.VARIANT(
|
|
418
|
+
pythoncom.VT_ARRAY | pythoncom.VT_R8,
|
|
419
|
+
[x, y, x + width, y, x + width, y + height, x, y + height],
|
|
420
|
+
)
|
|
421
|
+
pline = self.model_space.AddLightWeightPolyline(points)
|
|
422
|
+
pline.Closed = True
|
|
423
|
+
pline.Layer = layer
|
|
424
|
+
return pline
|
|
425
|
+
|
|
426
|
+
def zoom_extents(self):
|
|
427
|
+
"""缩放至全部实体。"""
|
|
428
|
+
self.send_command("ZOOM\nE\n")
|
|
429
|
+
|
|
430
|
+
def regen(self):
|
|
431
|
+
"""重生成图纸。"""
|
|
432
|
+
self.send_command("REGEN\n")
|
|
433
|
+
|
|
434
|
+
# ------------------------------------------------------------------
|
|
435
|
+
# 状态查询
|
|
436
|
+
# ------------------------------------------------------------------
|
|
437
|
+
|
|
438
|
+
def status(self):
|
|
439
|
+
"""返回连接状态摘要。"""
|
|
440
|
+
return {
|
|
441
|
+
"connected": self._connected,
|
|
442
|
+
"drawing": self._drawing_name,
|
|
443
|
+
"layer_count": len(self.get_all_layers()) if self._connected else 0,
|
|
444
|
+
"entity_count": len(self.get_entities()) if self._connected else 0,
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
def close(self):
|
|
448
|
+
"""断开连接(不清闭 AutoCAD 本身)。"""
|
|
449
|
+
self._connected = False
|
|
450
|
+
self._drawing_name = None
|
|
451
|
+
self.doc = None
|
|
452
|
+
self.acad = None
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# ==================== 便捷函数 ====================
|
|
456
|
+
|
|
457
|
+
def get_ac():
|
|
458
|
+
"""获取 AutoCAD 桥接实例(连接失败时返回 None)。"""
|
|
459
|
+
return connect()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "机械制图验证规则(DSH 工程模式)",
|
|
3
|
+
"description": "适用于 GB/T 机械制图的几何/结构验证规则,从 CADX residential/general 适配而来。",
|
|
4
|
+
"min_room_area_sqm": 6.0,
|
|
5
|
+
"min_wall_thickness_mm": 100,
|
|
6
|
+
"max_wall_thickness_mm": 500,
|
|
7
|
+
"min_door_width_mm": 700,
|
|
8
|
+
"max_door_width_mm": 2000,
|
|
9
|
+
"min_window_area_sqm": 0.5,
|
|
10
|
+
"ventilation_pct_of_floor": 10,
|
|
11
|
+
"max_stair_riser_mm": 160,
|
|
12
|
+
"min_stair_tread_mm": 250,
|
|
13
|
+
"min_ceiling_height_m": 2.7,
|
|
14
|
+
"required_layers": ["OUTLINE", "THIN", "CENTER", "HIDDEN", "DIM", "TEXT", "HATCH"],
|
|
15
|
+
"layer_name_pattern": "^[A-Z][A-Z0-9_-]*$",
|
|
16
|
+
"min_room_width_m": 2.4,
|
|
17
|
+
"min_room_length_m": 3.0,
|
|
18
|
+
"max_room_aspect_ratio": 3.0,
|
|
19
|
+
"unit_scale": 1.0,
|
|
20
|
+
"gap_threshold_mm": 10.0
|
|
21
|
+
}
|