Files
cad-agent/verify_drawing_area.py
T
gjm 1e5bff268a feat(review): 截屏前最大化绘图区 + 空瓦片过滤覆盖回退路径
新增 MCP 工具 set_drawing_area(mode="max"|"restore"):最大化主窗口 + CLEANSCREENON
(隐藏功能区/命令行/面板)。实测绘图区 2110x462 -> 3310x1084(面积 3.68x),
restore 精确回位(窗口矩形/最大化态/绘图区尺寸全部还原)。

坑:CLEANSCREENOFF 是异步命令,AutoCAD 会按「进入干净屏幕时的布局」重设窗口几何,
把同步的 SetWindowPlacement 覆盖掉(表现为窗口停在最大化)。解法是插件内
SetTimer 延迟 700ms 再压回;实测窗口在 0.5~1.0s 之间收敛,故调用方等待取 1.5s。

取证层:
- 新增 maximized_drawing_area(ctx) 上下文管理器,__main__ 用它包住整个取证循环
  (多图框只进出一次);受 DRAWING_AREA_MAXIMIZE 控制,工具缺失/调用失败静默降级,
  restore 失败不掩盖业务异常。
- 内容过滤统一到规划与回退两条路径,共用同一谓词;过滤后为空则退回原集合。
- 判定改用「实体 bbox」而非「占用格」:格子量化会把跨界内容算进相邻格,反而更粗
  (实测实体 bbox 正好压象限边界时 4 个象限全判有内容)。heatmap.occupied_boxes 随之删除。

实测(真实图纸 A0/A1/A2 三图框):截图张数 12/8/4 -> 8/5/4;pytest 296 passed。
新增 verify_drawing_area.py 作为该工具的验收脚本。
2026-10-15 23:44:32 +08:00

141 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""绘图区最大化验证:`set_drawing_area(max)` 到底把绘图区放大了多少,`restore` 能否保真还原。
不消耗 VLM token(只调截图工具 + 解码 PNG 量尺寸)。
用法:
python verify_drawing_area.py [--wait 3.0] [--restore-wait 1.5]
前置:CAD 在线(127.0.0.1:8080 监听)且已加载最新插件。不需要 DEEPSEEK_API_KEY。
判定:
1. max 后绘图区面积应显著大于基线(实测约 3.7x);
2. restore 后窗口矩形 / 最大化状态 / 绘图区尺寸应回到基线(容差 ±2px)。
注意:脚本会改动 CAD 窗口位置与干净屏幕状态,结束时一定还原(finally)。
"""
import argparse
import ctypes
import sys
import time
from ctypes import wintypes
from dwg_review.llm_client import image_size_b64
from dwg_review.mcp_client import McpClient, result_images, result_json
_TOL = 2 # 还原容差(像素)
# --------------------------------------------------------------------------
# Win32:定位 CAD 主窗口并读取其状态
# --------------------------------------------------------------------------
def _user32():
u = ctypes.windll.user32
u.GetWindowTextW.argtypes = [wintypes.HWND, wintypes.LPWSTR, ctypes.c_int]
u.IsWindowVisible.argtypes = [wintypes.HWND]
u.GetWindowRect.argtypes = [wintypes.HWND, ctypes.POINTER(wintypes.RECT)]
u.IsZoomed.argtypes = [wintypes.HWND]
return u
def find_main_hwnd(u):
"""枚举可见顶层窗口,取标题含 AutoCAD 且带 [图纸名] 的那个。"""
found = []
proc_type = ctypes.WINFUNCTYPE(wintypes.BOOL, wintypes.HWND, wintypes.LPARAM)
def cb(hwnd, _):
if not u.IsWindowVisible(hwnd):
return True
n = u.GetWindowTextLengthW(hwnd)
if n == 0:
return True
buf = ctypes.create_unicode_buffer(n + 1)
u.GetWindowTextW(hwnd, buf, n + 1)
if "AutoCAD" in buf.value and "[" in buf.value:
found.append(hwnd)
return False
return True
u.EnumWindows(proc_type(cb), 0)
return found[0] if found else None
def win_state(u, hwnd):
rc = wintypes.RECT()
u.GetWindowRect(hwnd, ctypes.byref(rc))
return (rc.left, rc.top, rc.right, rc.bottom), bool(u.IsZoomed(hwnd))
# --------------------------------------------------------------------------
# 测量
# --------------------------------------------------------------------------
def shot_size(client):
"""截一张整图,返回 (宽, 高)。"""
resp = client.call_tool("get_viewport_screenshot", {})
imgs = result_images(resp)
if not imgs:
return None
return image_size_b64(imgs[0][0])
def _area(sz):
return (sz[0] * sz[1]) if sz else 0
def main(argv=None):
ap = argparse.ArgumentParser()
ap.add_argument("--wait", type=float, default=3.0, help="max 后等待渲染秒数")
ap.add_argument("--restore-wait", type=float, default=1.5,
help="restore 后等待秒数(须 > 插件内 700ms 延迟还原)")
args = ap.parse_args(argv)
u = _user32()
hwnd = find_main_hwnd(u)
if not hwnd:
print("找不到 AutoCAD 主窗口,请先启动 CAD", file=sys.stderr)
return 1
print(f"CAD 主窗口: {hwnd:#x}")
client = McpClient()
rc0, zoom0 = win_state(u, hwnd)
base = shot_size(client)
print(f"[基线] 窗口矩形={list(rc0)} 最大化={zoom0} 绘图区={base}")
ok = True
try:
info = result_json(client.call_tool("set_drawing_area", {"mode": "max"}), {})
time.sleep(args.wait)
big = shot_size(client)
rc1, zoom1 = win_state(u, hwnd)
ratio = _area(big) / _area(base) if _area(base) else 0.0
print(f"[max] 窗口矩形={list(rc1)} 最大化={zoom1} 绘图区={big} "
f"面积={ratio:.2f}x 工具返回={info.get('main_rect')}")
if ratio < 1.5:
print(" !! 绘图区放大不足 1.5x,请检查窗口是否被其它窗口遮挡")
ok = False
finally:
client.call_tool("set_drawing_area", {"mode": "restore"})
time.sleep(args.restore_wait)
rc2, zoom2 = win_state(u, hwnd)
back = shot_size(client)
print(f"[restore] 窗口矩形={list(rc2)} 最大化={zoom2} 绘图区={back}")
# ---- 还原保真度判定 ----
if zoom2 != zoom0:
print(f" !! 最大化状态未还原: {zoom0} -> {zoom2}")
ok = False
if any(abs(a - b) > _TOL for a, b in zip(rc0, rc2)):
print(f" !! 窗口矩形未还原(容差 {_TOL}px)")
ok = False
if base and back and any(abs(a - b) > _TOL for a, b in zip(base, back)):
print(f" !! 绘图区尺寸未还原: {base} -> {back}")
ok = False
print("\n结果:", "PASS" if ok else "FAIL")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())