发现的缺口:maximized_drawing_area 原先只包住取证循环,审查阶段没被包住 → LLM 在受控子循环里 zoom + 截图拿到的是未最大化的 2110x462(正是上一轮刚解决的 低分辨率问题,在最需要放大看细节的审查阶段又出现了);且审查中的 zoom 不复位视口, 跑完把用户的 CAD 留在最后一次放大处。 改动: 1. __main__.run_review:把 maximized_drawing_area 从「只包取证」扩到「包取证 + 审查」 (两者都要截图);report/annotate 留在 with 之外。 2. phases/review.py:新增 _VIEW_TOOLS;review_frame 拆成外壳 + _review_frame, 外壳 try/finally 里仅当确实动过视口才复位到该图框整幅视图 (zoom_to_window(win, margin=0.02))。纯查结构化数据的路径不受影响。 3. checklist.build_system_prompt:把「可调用只读工具」改写成明确的放大取证协议—— 截图看不清时用 zoom_to_window(坐标由图框/实体 bbox 算)或 zoom_to_entity, 再 get_viewport_screenshot 复核;并约束不要为看图而反复放大。 次生问题(同轮修复):_MAX_OBS_IMAGES 原为硬编码 3,而模型一轮内会发 8 次截图 → 拍了 8 张只回灌 3 张。改为配置项 REVIEW_OBS_IMAGES(默认 6),并把预算耗尽的占位 文案改成明确告知模型「不要再请求截图,请给出结论」。 实测(真实图纸 1 图框,新增 verify_review_maximized.py):取证与审查两阶段窗口均为 [-4,-2,3444,1442](最大化态),结束精确还原 [68,0,2316,960],三项全 PASS; 子循环里 LLM 连发 8 组 zoom_to_window + get_viewport_screenshot,提示词生效。 pytest 304 passed。
14 KiB
name, description, type
| name | description | type |
|---|---|---|
| Agent 路线与多源架构(2026-10-15) | dwg_review agent 的总目标、多源图纸分层架构(视觉为最大公约数)、工具层与 agent 层的正交分工、多源证据层、截图去 UI、grounding 实测、热力图 + 自适应瓦片规划 + VLM 分辨率标定、绘图区最大化(set_drawing_area)与空瓦片过滤、审查阶段分辨率对齐 + 放大取证闭环、后续路线 | project |
Agent 路线与多源架构
总目标(用户 2026-10-15 明确)
实现 AI 自动识别 + 评审 DWG 图纸。分阶段:先 MCP(已完成),再 agent(进行中)。 最终要支持的图纸来源不限于自家自定义实体:
- 自家软件的自定义实体 DWG;
- 其他软件出的 DWG(各种自定义实体);
- 将来 PDF 图纸;
- 将来纯图片图纸。
架构分层(用户认可)
核心洞察:视觉模型(VLM)是"最大公约数"。DWG 自定义实体 / PDF / 图片本质是 "拿不到结构化数据时靠视觉理解"的同一件事——任何图纸只要能渲染成图,VLM 就能审。 所以结构化数据(ARX 读普通实体)是增强,视觉是兜底。
Agent 推理层(审查逻辑 / 清单 / 规则)
▲ 统一 evidence schema
感知适配层(能力从高到低降级)
CAD结构化(ARX) → CAD几何 → 离线解析(DWG/PDF) → 视觉(VLM)
- MCP 工具层只管 CAD(进程内能力);PDF / 图片在 agent 侧(PyMuPDF / Pillow), 不塞进 MCP、不编 DLL。
- 统一证据模型 + 多源适配 + 能力降级;agent 推理层不关心来源格式。
How to apply:新增"支持某格式"时,优先在 agent 侧感知层加适配器;只有"CAD 进程内的 正交感知能力"才考虑加 MCP 工具。
第一步已落地(2026-10-15,提交 1c9fc72)
取证层多源化 + 视觉多图:
- 新增
dwg_review/evidence_model.py:统一证据模型(source_type+images:[{b64,mime,label}]),访问器evidence_images()兼容旧image_b64/image_mime单图字段(历史 evidence.json 仍可读)。 phases/evidence.py:collect()多源分发 seam(当前仅 CAD)、capture_images()整体 + 等分象限多图(结束复位视口)、sub_windows()。- 默认
EVIDENCE_DETAIL_TILES=4/EVIDENCE_MAX_IMAGES=5(每图框 5 图,默认开启)。 - 实测:真实 Plant 3D 图纸上多图让审查更细(多报出线型误用
CENTER2、图签块属性为空、 按 handle 定位的图层混用等)。
截图去 CAD 界面元素(2026-10-15,提交 f8bd12d)
get_viewport_screenshot 原先截 adsw_acadDocWnd() 客户区,含视口控件、ViewCube、滚动条,
污染截图并误导 VLM(曾把视口标签当成"需放大的内容")。已修复:
- 改用
acedGetAcadDwgView()只截图形区(排除滚动条/状态栏)。 - 截图期间临时置
NAVVCUBEDISPLAY=0(关 ViewCube)与VPCONTROL=0(关视口控件), 用 RAII 守卫(TempSysVarOff)自动恢复原值,不影响用户 CAD 界面设置; 配合RedrawWindow(RDW_UPDATENOW)同步重绘。 - 实测:三项 UI 均消失,VLM 不再提界面元素。
残留(无解,已知):绘图区右上角仍有绘图窗口的最小化/最大化/关闭按钮——由 AutoCAD 画在图形区内的固有 UI,无系统变量可关。它很小且在角落,实测未影响 VLM。 (区别于应用窗口的按钮;用户最初要清的是 视口控件/ViewCube/滚动条,不含此项。)
grounding 实测(2026-10-15)
用 verify_grounding.py 拿真实截图问 deepseek-flash"哪里要放大":grounding 可用——
给出的归一化坐标区域级定位准确(放大后确实套住目标,如图签栏、图框角),
但语义描述略有偏差(把图签栏说成"状态栏")。结论:可支撑"按区域放大",但位置对、语义别全信。
后续路线(未做)
- VLM 主动放大(自适应取证):复用受控只读取证子循环,让 VLM 请求放大看不清处。
- 2026-10-16 部分落地:审查阶段已对齐分辨率 + 提示词已明确放大协议 + 视口复位,
实测 LLM 真的会连发
zoom_to_window+get_viewport_screenshot(见下节)。 仍缺:① 按区域放大时的 grounding→WCS 映射(目前靠 LLM 自己用 bbox 算窗口,够用); ② 把「看清没」的判定不交给 VLM(防过度自信)——现靠回合上限 + 截图预算兜底。 - 现有
zoom_to_window/zoom_to_entity/get_viewport_screenshot已在只读白名单, 无需新增工具。
- 2026-10-16 部分落地:审查阶段已对齐分辨率 + 提示词已明确放大协议 + 视口复位,
实测 LLM 真的会连发
- PDF / 图片源:在
phases/evidence.collect()加分支,产出同形 evidence (window=None、结构化空信封、images带 label);规划阶段抽象"审查单元" (CAD=图框 / PDF=页 / 图片=单);批注对非 CAD 源降级(annotator已天然跳过无 handle 的 issue)。 - 审图规范接入:清单升级为规范库。
热力图 → 自适应瓦片规划 → VLM 分辨率标定(2026-10-15,进行中)
动机:固定「整体 + 2x2 象限」截图对 A0/A1 大图幅读不清。用户拍板:驱动信号是实体分布 (热力图),不是文字(有的图没有文字),且实际工况下实体占据图框大部分,稀疏/稠密都要能处理。 用户方案:图框类型 → 纸张真实尺寸 + 比例 → CAD 世界尺寸 → 换算像素 → 知道 VLM 分辨率 → 算出一个图框要截几张、截哪里。
用户已拍板的关键决定:
- 热力图在 Python 侧(agent 层) 算(派生量,工具层只给原始事实)。密集图若受
query_entities的limit≤1000/ 帧上限拉不全 → 给现有query_entities加brief模式 (只回 handle/type/bbox、放开 limit),而不是新增"热力图工具"。 CollisionDetection.h/cpp(用户写的 BVH)本步不用,留作后续几何相交/干涉检查引擎。 其按质心中位分割 → 节点框可能重叠,不适合铺屏瓦片;瓦片切分用四叉树/网格(空间不相交)。- 标定判据:同时测「读出文字」与「分辨线细节」;标定字高 200 图形单位(1:100 = 纸面 2mm,国标最小)。
已落地(提交 9b8a232 热力图;cba05b0 瓦片规划 + 离线标定):
dwg_review/heatmap.py(纯函数):entity_boxes/build_grid(counts+coverage)/cluster_cells(4-连通,可选膨胀)/grid_to_windows/grid_stats/render_overlay。drop_oversized:丢掉 bbox 占满图框的实体(实测每框恰 1 个,即图框块),否则热力图全满。dwg_review/tiling.py(纯函数):frame_scale(sheet_scale>0用之;否则由 bbox/纸张反算; 否则 default)/world_per_px/is_text_type/plan_regions(文字簇优先、空白区不出图、max_tiles截断、窗口夹到图框)。phases/evidence.capture_images:先截整体图并测绘图区像素 → 规划成功用规划窗口, 否则退回sub_windows(行为零变化);任一信封total>returned(截断)→ 退回固定策略。verify_heatmap.py(不耗 token,出 PNG + bbox 缓存)、verify_vlm_resolution_offline.py、verify_text_resolution.py(CAD 实测)。
标定结论:
- 该模型无法可靠计数线条(6 条线在 2~10px 间距下答 4/10/4/5/4/4)→ 线条判据改用 「1 条 vs 2 条」二选一。
- 可读下限与字体强相关:离线合成(Consolas 抗锯齿填充字体)12px(白底 10px); 真实 CAD 单线体 .shx(描边字体)6px 就能读对(3/3,两轮)。
- 真实 CAD 线条:2/3/4px 失败,5/8/12px 成功 → R_line ≈ 5px。
- 标定必须用 ACI 7(白/黑自适应):实测红字(ACI 1)12px 完全读不出——否则把 "对比度不足"误当"分辨率不足"。
- 实测绘图区(窗口未最大化时)2110×462 → 有效 1280×280(长边 1280),视口宽高比 4.567。
用户拍板的默认值(2026-10-15):TEXT_READABLE_PX=8(折中,兼顾描边与 TrueType)、
LINE_READABLE_PX=6、EVIDENCE_MAX_IMAGES=12(原 5 会把规划结果截到只剩 4 张瓦片)。
瓦片粒度内容过滤:簇 bbox 是包围盒,L 形/斜向分布(如管道走向)含大片空白 →
plan_regions(keep=...) 剔除空瓦片(实测有效)。
已知局限:本图纸的文字在块 / 自定义实体内,get_text_content 检不到 → 该簇会按
线条判据出图(6px),块内文字可能读不清。要更保险可把 LINE_READABLE_PX 调到 8
(等价于全用文字判据,代价是同样预算下覆盖面积减半)。
绘图区最大化 + 内容过滤覆盖回退路径(2026-10-15,本轮)
用户诉求(原话):「截屏之前,要把 CAD 铺满屏幕,并且把命令行也最小化,反正一句话, 让绘图区最大化,然后再去截屏。是不是更好?还有,我发现你有几次截屏区里面没有实体,浪费了。 截屏之前判断一下这个区域有没有实体。」
A. 新增 MCP 工具 set_drawing_area(mode="max"|"restore")(src/cad_mcp_plugins/dwg_review_ui.cpp)
max:保存WINDOWPLACEMENT+CLEANSCREENSTATE(只存首次)→ShowWindow(SW_MAXIMIZE)→sendStringToExecute("_.CLEANSCREENON")(隐藏功能区/命令行/面板)。restore:CLEANSCREENOFF+ 还原窗口布局。- 关键坑:CLEANSCREENOFF 是异步命令,会按「进入干净屏幕时的布局」重设窗口几何,把同步执行的
SetWindowPlacement覆盖掉(表现为窗口停在最大化)。解法:restore 里用SetTimer(NULL, 0, 700, delayedRestoreProc)延迟 700ms 再压回布局。 实测窗口在 0.5~1.0s 之间收敛 → 调用方须等待 ≥1.5s。 - 实测收益(屏幕 3440×1440):绘图区 2110×462 → 3310×1084(面积 3.68×),
窗口矩形
[68,0,2316,960]→[-4,-2,3444,1442],还原精确回位。 - 验收脚本:
verify_drawing_area.py(基线 → max → restore,断言矩形/最大化态/绘图区尺寸全回位)。 - 工具注册:
mcp_config.json的tools[](现 27 个),工厂CreateSetDrawingAreaTool。set_drawing_area以set_开头 → 被cad_tools.is_write_tool判为写类(不重试), 且不在READ_ONLY_TOOLS里 → LLM 的受控子循环不能动用户界面。
B. 取证层接入(phases/evidence.py)
maximized_drawing_area(ctx)上下文管理器:with内max、退出finally里restore。 受config.DRAWING_AREA_MAXIMIZE控制;工具缺失 / 调用失败 → 静默降级; restore 失败不掩盖业务异常。__main__.run_review用它包住整个取证循环(多图框只进出一次)。- 内容过滤统一到两条路径:原先只有规划路径过滤,回退路径(
sub_windows象限)不过滤。 现在keep = _content_predicate(boxes)一次构造,规划与回退共用;_apply_keep在「过滤后为空」时退回原集合(宁可多拍不漏)。 - 改用「实体 bbox」而非「占用格」判定(上一版用
heatmap.occupied_boxes): 格子量化会把跨界内容算进相邻格,反而更粗(实测某实体 bbox 正好压象限边界时 4 个象限全判有内容)。 → 直接boxes_intersect(tile, entity_box),更精确也更简单;heatmap.occupied_boxes随之删除。 语义:接触即算有内容(保守)。
实测(真实图纸,3 个图框 A0/A1/A2):截图张数 12/8/4 → 8/5/4(收益同时来自
绘图区变大 → 单张覆盖更多世界尺寸,与空瓦片被剔除)。端到端 --plan-only 跑通,
运行中窗口峰值 [-4,-2,3444,1442](面积 2.31×),结束精确还原 [68,0,2316,960]。
新增配置:DRAWING_AREA_MAXIMIZE(True) / DRAWING_AREA_ENTER_WAIT(1.5) /
DRAWING_AREA_RESTORE_WAIT(1.5,必须 > 插件内 700ms 延迟还原)。
审查阶段分辨率对齐 + 放大取证闭环(2026-10-16,本轮)
发现的缺口:maximized_drawing_area 原先只包住取证循环,审查阶段没被包住 →
LLM 在受控子循环里 zoom + 截图拿到的是未最大化的 2110×462(正是上一轮刚花力气解决的
低分辨率问题,在最需要放大看细节的审查阶段又出现了);且审查中的 zoom 不复位视口,
跑完把用户的 CAD 留在最后一次放大处。
三项改动:
__main__.run_review:把with evidence_phase.maximized_drawing_area(ctx)从 「只包取证」扩展到「包取证 + 审查」(两者都要截图);report/annotate留在 with 之外。phases/review.py:新增_VIEW_TOOLS = {zoom_to_window, zoom_to_entity};review_frame拆成外壳 +_review_frame,外壳try/finally里仅当确实动过视口才_reset_viewport(复位到该图框整幅视图zoom_to_window(win, margin=0.02))。 —— 只在动过时复位,既省一次调用,也不影响"纯查结构化数据"的路径。checklist.build_system_prompt:把「可调用只读工具」改写成明确的放大取证协议: 截图看不清时用zoom_to_window(坐标由图框/实体 bbox 算)或zoom_to_entity, 再get_viewport_screenshot复核;并约束不要为看图而反复放大。
实测(真实图纸,1 图框,verify_review_maximized.py):
- 取证阶段采样
[-4,-2,3444,1442]、审查阶段采样同样是[-4,-2,3444,1442](最大化态), 结束精确还原[68,0,2316,960]→ 三项全 PASS。 - 子循环里 LLM 真的连发 8 组
zoom_to_window+get_viewport_screenshot(提示词生效,"看不清→放大重看"闭环跑通)。
同一轮发现的次生问题:_MAX_OBS_IMAGES 原为硬编码 3,而模型一轮内会发 8 次截图
→ 拍了 8 张只回灌 3 张(其余被"已省略"占位)。改为配置项 REVIEW_OBS_IMAGES
(默认 6,config.py),并把预算耗尽时的占位文案改成明确告知模型"不要再请求截图,
请给出结论"(原先只说"已省略")。实测模型截图次数不变(8),但能看到 6 张而非 3 张。