"""绘图区最大化验证:`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())