Files
cad-agent/verify_grounding.py
T
gjm f8bd12dcec fix(mcp): 截图去除 CAD 界面元素(视口控件/ViewCube/滚动条)
- get_viewport_screenshot 改用 acedGetAcadDwgView() 只截图形区(排除滚动条/状态栏)
- 截图期间临时置 NAVVCUBEDISPLAY=0、VPCONTROL=0 隐藏 ViewCube 与视口控件,
  RAII 守卫自动恢复原值,不影响用户 CAD 界面设置;配合 RedrawWindow 同步重绘
- 新增 verify_grounding.py:grounding 实测探针(截整体图 + VLM 定位放大区域)
2026-10-15 21:19:59 +08:00

132 lines
4.2 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.
"""实测 deepseek-flash 的 grounding:能否给出可用的「放大区域」归一化坐标。
流程:截一张真实图框整体图 → 问 VLM「哪些区域需要放大」→ 解析它给的归一化坐标
→ 映射成 CAD 世界坐标(WCS)→ 按坐标放大截图,供目视验证 grounding 是否可用。
用法:
python verify_grounding.py [--frames 1] [--out reports/_probe]
前置:CAD 在线(127.0.0.1:8080 监听)、DEEPSEEK_API_KEY 可用。
"""
import argparse
import base64
import json
import time
from pathlib import Path
from dwg_review import config
from dwg_review.findings import extract_json_block
from dwg_review.llm_client import LlmClient, downsample_image_b64
from dwg_review.mcp_client import result_images
from dwg_review.phases import planning
from dwg_review.context import ReviewContext
_PROMPT = (
"这是某工程图纸中一个图框的截图。请判断:图中哪些区域**细节不清、需要放大**才能看清?\n"
"用**归一化坐标**给出想放大的区域:x0,y0,x1,y1 取值 0~1,"
"图片左上角为 (0,0)、右下角为 (1,1)。\n"
"最多给 3 个区域,按重要性排序。\n"
'只输出 JSON,不要解释:{"regions":[{"bbox":[x0,y0,x1,y1],"reason":"..."}]}'
)
def _save(b64, path):
path.write_bytes(base64.b64decode(b64))
return path
def _norm_to_wcs(win, x0, y0, x1, y1):
"""归一化坐标(左上原点)→ WCS window。"""
dx = win["max_x"] - win["min_x"]
dy = win["max_y"] - win["min_y"]
return {
"min_x": win["min_x"] + x0 * dx,
"max_x": win["min_x"] + x1 * dx,
"min_y": win["max_y"] - y1 * dy, # 图片 y 向下,WCS y 向上
"max_y": win["max_y"] - y0 * dy,
}
def main(argv=None):
ap = argparse.ArgumentParser()
ap.add_argument("--frames", type=int, default=1)
ap.add_argument("--out", default="reports/_probe")
args = ap.parse_args(argv)
ctx = ReviewContext.create()
plan = planning.plan(ctx)
frames = plan.get("frames", [])[: args.frames]
if not frames:
print("未识别到图框")
return 1
out_dir = Path(config.PROJECT_ROOT) / args.out
out_dir.mkdir(parents=True, exist_ok=True)
frame = frames[0]
win = {
k: float(v)
for k, v in zip(("min_x", "min_y", "max_x", "max_y"), frame["bbox"])
}
print("图框 window:", win)
ctx.tools.call("zoom_to_window", dict(win, margin=0.02))
time.sleep(config.RENDER_WAIT)
shot = ctx.tools.call("get_viewport_screenshot", {})
imgs = result_images(shot)
if not imgs:
print("无截图")
return 1
b64, mime = imgs[0]
nb, nm = downsample_image_b64(b64, mime)
print("整体图:", _save(b64, out_dir / "overall.png"))
llm = LlmClient()
print("模型:", llm.model)
messages = [
{"role": "system", "content": "你是资深工程图纸审图助手。"},
{
"role": "user",
"content": [
{"type": "text", "text": _PROMPT},
LlmClient.image_content(nb, nm),
],
},
]
resp = llm.chat(messages)
text = resp.choices[0].message.content or ""
print("=== VLM 原始响应 ===")
print(text)
block = extract_json_block(text)
if not block:
print("[X] 未解析出 JSON")
return 1
data = json.loads(block)
regions = data.get("regions") or []
print(f"=== 解析到 {len(regions)} 个区域 ===")
for i, r in enumerate(regions, 1):
bbox = r.get("bbox")
if not (isinstance(bbox, list) and len(bbox) == 4):
print(f" 区域 {i}: bbox 非法 {bbox}")
continue
x0, y0, x1, y1 = (float(v) for v in bbox)
sub = _norm_to_wcs(win, x0, y0, x1, y1)
print(f" 区域 {i}: bbox={bbox} reason={r.get('reason')!r}")
print(f" -> WCS {sub}")
ctx.tools.call("zoom_to_window", dict(sub, margin=0.05))
time.sleep(config.RENDER_WAIT)
s2 = ctx.tools.call("get_viewport_screenshot", {})
im2 = result_images(s2)
if im2:
print(" -> 已保存", _save(im2[0][0], out_dir / f"region_{i}.png"))
print("\n截图目录:", out_dir)
return 0
if __name__ == "__main__":
raise SystemExit(main())