Files
cad-agent/CODEBUDDY.md
T
gjm 6143bb9ab2 feat(review): PDF/图片离线源适配(C1)—— 无需 CAD 的整条审查链路
支持直接审查 PDF 与位图图纸,全链路离线(不连 CAD、不连 8080 端口)。
渲染在内存内完成,复用既有墨迹热力图 + 自适应瓦片规划。

- 新增 dwg_review/sources.py
  - detect_source / plan_file / collect 统一入口
  - PdfReader:pypdfium2 惰性导入,页序即图框序,处理 /Rotate 与 crop 边距
  - ImageReader:世界单位 = 源像素,snap_box 整数对齐保证窗口与像素严格一致
  - ink_grid:纯 PIL 灰度阈值 → 占用网格(不引入 numpy)
- ReviewContext.create(connect_cad=False):离线时 client/tools 均为 None
- 全链路 ctx.tools is None 守卫:review 只读子循环 / inspect_region / annotator /
  checks 的窗口类检查 / 绘图区最大化
- checklist.build_system_prompt(allow_tools=False, offline=True):离线提示词
- __main__:位置参数 source,按扩展名自动判定源类型并决定是否连 CAD
- config 新增 8 个常量(PDF_MAX_PAGES / 文本最小字数 / 各源缩放上限 / 墨迹阈值等)
- tiling.content_predicate 与 heatmap.cell_box 提升为公开函数

验证:
- pytest 457 passed
- verify_pdf_source.py:PDF 23/23、图片 9/9(不需 CAD),含实测瓦片字高像素与
  旋转映射自检(文本区墨迹 0.311 vs 忽略 /Rotate 反向对照 0.145)
- 真实 LLM 端到端:20 页 HVAC 图纸前 3 页跑通,结论具体可核验且未编造 handle

已知局限:页边线在墨迹网格里会把整页连成单簇,瓦片常退化为整页象限(CAD 侧
靠 drop_oversized 丢图框块,离线侧暂无等价手段),留待后续打磨。
2026-10-16 23:07:23 +08:00

12 KiB
Raw Blame History

CODEBUDDY.md

This file provides guidance to CodeBuddy Code when working with code in this repository.

项目概述

CADProject 是一个将 AutoCAD / 中望 CAD (ZWCAD) 变为 MCP (Model Context Protocol) 服务端的插件工程。CAD 进程内运行一个 TCP JSON-RPC 服务,向大模型暴露 tools/list 与 tools/call,让 LLM 能驱动 CAD 执行绘图、缩放、截图等操作。仓库同时附带一组 Python 客户端脚本用于联调。

构建

构建系统为 CMake + vcpkg + Visual Studio 生成器,通过 CMake Presets 区分 CAD 版本。产物输出到 bin/<Config>-<TAG>/ 与 lib/<Config>-<TAG>/。

# 配置 + 构建单个版本(以 R243 为例)
cmake --preset R243
cmake --build --preset R243 --config Release

# 或使用根目录脚本批量处理(内部即上面两条命令的组合)
./preset.bat      # 仅 configure: R190 / R230 / R220
./build.bat       # build --config release: R230 / R220
  • 前置环境:需要 VCPKG_ROOT 环境变量;第三方 CAD SDK 位于仓库同级的 ../envi-code/Arx(AutoCAD)与 ../envi-code/Zrx(ZWCAD),路径在 cmakepresets.json 的 CAD_SDK_ROOT 中配置。
  • 可用 preset:R180–R260(AutoCAD,ARX 18–26)、Z2023–Z2026(ZWCAD,ZRX)。每个 preset 绑定对应的 MSVC toolset(v90–v143)。preset 名称同时决定输出目录标签 GENERATE_DIR_TAG(如 R243、Z2024)。
  • 必需 cache 变量(缺失会 FATAL_ERROR):CAD_SDK_ROOT、DEP_LIB_ROOT、DEP_INC_ROOT、CAD_SDK_VERSION,AutoCAD 版还需 CAD_SDK_SUBVERSION。
  • vcpkg 依赖见 vcpkg.json:nlohmann-json、curl、ghc-filesystem;triplet 为 x64-windows-static-md(静态库 + 动态 CRT,定义于 cmake/x64-windows-static-md.cmake)。

测试

分两层,互不替代:

快层 —— pytest 离线单元测试(tests/,无需 CAD、不联网、秒级):

pip install -r requirements-dev.txt   # 仅 pytest
python -m pytest                      # 期望全绿

全部使用假 LLM + 假 MCP 基座(tests/fakes.py),锁定 dwg_review 流水线既有行为。 配置见 pytest.ini(testpaths = tests)与根 conftest.py(把仓库根加入 sys.path, 并提供 RENDER_WAIT=0 / REPORT_DIR=tmp 两个 fixture)。注意:dwg_review.config 的常量在导入期定值,测试改 os.environ 无效,须 monkeypatch.setattr(config, ...)。 tests/test_sources_pdf.py 里的 _build_pdf() 手工构造最小 PDF,PDF 路径无需样本文件即可回归。

慢层 —— 端到端冒烟脚本(仓库根 verify_*.py):

需要已启动已加载插件的 CAD、8080 端口在监听(verify_pdf_source.py 除外):

python verify_mcp_frame.py           # 基座最小集:分帧 / id / isError 映射
python verify_mcp_tcp_hardening.py   # TCP 收发路径加固(超长帧 / 粘包 / 半关闭)
python verify_mcp_fault_injection.py # 工具异常保护(类型错误不再崩 CAD)
python verify_stdio_bridge.py        # stdio ↔ TCP 桥(协议协商 / 转发 / 通知)
python verify_annotate.py            # 批注回写链路
python verify_drawing_area.py        # 绘图区最大化:基线 → max → restore 全回位(实测 3.68x)
python verify_review_maximized.py    # 审查全链路:取证+审查两阶段均在最大化态、结束精确还原;记录 LLM 取证工具调用
python verify_collisions.py          # find_collisions:全模型/逐图框粗筛、容器过滤、阈值/分页/细筛;--inject 注入干涉正例后清理

这些脚本通过 TCP 连接 127.0.0.1:8080 收发 JSON-RPC。

离线源验收(PDF / 图片,不需要 CAD):

python verify_pdf_source.py "E:/path/图纸.pdf"            # 离线:页序 / evidence 同形 / 瓦片字高实测 / 旋转映射自检
python verify_pdf_source.py "E:/path/scan.png" --dump out/c1
python verify_pdf_source.py "E:/path/图纸.pdf" --llm      # 追加真实 python -m dwg_review 端到端

架构

两个构建目标

目标 产物 作用
src/cad_mcp_frame cad_mcp_frame.arx(ZWCAD 下为 .zrx) MCP 基座。随 CAD 加载,起 TCP 服务、解析工具配置、路由执行。
src/cad_mcp_plugins cad_mcp_plugins.dll 示例 C++ 工具插件,通过 ABI 被基座动态加载。

基座运行模型(src/cad_mcp_frame/)

  1. CAD 加载 .arx → acrxEntryPoint(kInitAppMsg) → mcp::init_mcp_server()(cad_mcp_frame.cpp:41)。
  2. init_mcp_server()(mcp_server.cpp:225)在主 UI 线程创建仅消息窗口(HWND_MESSAGE,McpWndProc),并启动后台 TcpServerLoop 线程监听 127.0.0.1:8080。
  3. 跨线程安全是核心设计:TCP 线程只负责 recv,把请求封装成 TaskData(含 socket)后 PostMessage(WM_MCP_EXECUTE_TASK) 投递给消息窗口。真正的工具执行 ExecuteTaskInMainThread() 运行在 CAD 主线程,从而安全调用 ARX API。执行结果再通过该 socket 发回。
  4. 配置加载:mcp_ConfigParser::LoadAndRegister() 读取模块所在目录下的 mcp_config.json(getModelDirPath(),即 .arx 同级目录),按 tools[].backend.type 注册工具到全局 mcp_Tools 注册表(unordered_map<name, shared_ptr<mcp_Tool>>)。
  5. 卸载:close_mcp_server() 停线程、销毁消息窗口、清空工具表。

支持的 JSON-RPC 方法:tools/list(返回所有工具描述)、tools/call(路由到 mcp_Tools::Call)。非法 JSON 返回 -32700,未知方法返回 -32601。

三种工具后端(backend.type)

  • lisp_inline:把 script_template 中的 {占位符} 替换为调用参数,sendStringToExecute 到命令行(异步)。布尔值转 T/nil。见 mcp_tool.cpp:43。
  • lisp_file:先 (load "文件" nil) 再执行 call_template,file_name 相对于配置中的 lisp_directory 解析。见 mcp_tool.cpp:61。
  • c++_dll:LoadLibrary 第三方 DLL。同一 DLL 全进程只加载一次(句柄由 mcp_configParser.cpp 的模块缓存 g_loadedDllModules 持有,mcp_Tools::clear() 时统一 FreeLibrary),仅首次加载时校验版本兼容性:acrxGetApiVersion 返回值编码为 (major<<16)|minor,默认只比大版本(同一大版本内二进制兼容,如 R24.0/R24.1/R24.2 可共用同一 ARX);唯一例外 R24.3(换了 MSVC toolset,与 R24.0~R24.2 不兼容),对 major==24 再按 minor>=3 分组。基座侧版本用编译期常量 ARX/SUB_ARX。注意运行时的 acrxGetApiVersion(rxapi.lib 的 acrxGetApiVersionImpl)是有状态实现,返回值会随调用次数变化,不能用作基座侧版本。取 factory_function 与 Destroy_<factory> 创建/销毁工具,用 mcp_Tool_DllProxy 托管工具对象生命周期(RAII,不再 FreeLibrary 模块)。跨 DLL 通过 SetConfigAbiSafe / ExecuteAbiSafe 传 C 字符串,JSON 序列化全部在 DLL 内部完成。见 mcp_configParser.cpp、mcp_tool.cpp。

插件 ABI(inc/mcp_plugin_api.h)

第三方工具只需继承 mcp_Tool,实现纯虚 Execute(args),并用 EXPORT_MCP_TOOL(ToolClass, FactoryName) 宏导出工厂 + 销毁函数。mcp_Tool_Utility 提供 make_text_result / make_image_result / make_mixed_result / make_error 等返回体构造器。mcp_plugins.cpp 中的 mcp_Tool_ViewportScreenshot(GDI+ 抓屏 → PNG → Base64 → 多模态返回)是完整参考实现。

Python 侧:图纸审查 agent(dwg_review/)

CLI:python -m dwg_review [source] [options]。来源决定运行模式:

source 模式 取证方式
省略 / .dwg / .dxf CAD 在线 连 8080,逐图框调 MCP 工具 + 截屏
.pdf / 常见图片格式 完全离线(不连 CAD) sources.py 内存渲染 + 墨迹瓦片

四阶段流水线:phases/planning(识别图框 / 分页)→ phases/evidence(取证)→ phases/review(LLM 审查 + 受控只读取证子循环)→ phases/report(汇总落盘)。

  • 统一证据模型 evidence_model.py:source_type + images:[{b64,mime,label,window}]; window 是该图实际可见的世界范围(供 inspect_region 把截图归一化坐标映射回 WCS)。
  • 多源分发 seam:phases/evidence.collect(ctx, index, unit) 按 source_type_of(unit) 分发;非 CAD 源走 sources.collect,产出与 CAD 同形的 evidence(结构化键给 evidence_model.empty_envelope(),PDF 另有 best-effort 文本层放进 texts 信封)。
  • 纯函数复用:tiling.py(比例换算 / 瓦片切分)、heatmap.py(占用网格 / 聚类) 与 CAD 无关,CAD 用实体 bbox 驱动、PDF/图片用像素墨迹驱动,链路完全一致。
  • 离线守卫:connect_cad=False 时 ctx.tools/ctx.client 均为 None;所有访问点 (maximized_drawing_area / _review_tools / _run_readonly_tool / _reset_viewport / run_deterministic_checks / annotator.annotate&clear)都先判 None。 checklist.build_system_prompt(offline=True) 换成纯视觉提示词(无工具、禁止输出 handle)。
  • 世界坐标系(都 y 向上):CAD = 图形单位;PDF = 页内点(已应用 /Rotate); 图片 = 源像素(sources.snap_box 对齐整数,保证「窗口 ↔ 像素」精确对应)。
  • 运行时依赖见 requirements.txt(openai / Pillow / pypdfium2)。

关键头文件

  • inc/cadsdk.h:ARX 开发总伞头文件,一次性引入几乎所有 ObjectARX/ZWCAD 头文件,处理 ZWCAD 与 AutoCAD 的宏/库差异。业务 .cpp 通常只需 #include "cadsdk.h"。
  • src/cad_mcp_frame/StdAfx.h:预编译头(MFC + Windows),是 cad_mcp_frame 的 PCH 入口。

配置与产物布局

  • 运行时配置:mcp_config.json,必须与 .arx 同目录。示例见 bin/Debug-R230/mcp_config.json(当前注册 28 个工具,含 zoom_window_normalized、zoom_extents、get_viewport_screenshot、set_drawing_area、find_collisions 等);src/cad_mcp_frame/cad-mcp-frame.json 是模板(文件名带连字符,非运行时文件)。
  • global_settings.plugin_directory 指向 C++ DLL 目录,lisp_directory 指向 LISP 目录(默认都相对模块目录)。示例 LISP 见 bin/Debug-R230/mcp_plugins/mcp_table_array.lsp。
  • cmake/functions.cmake 提供工程级封装:target_link_objectarx(自动按 ZWCAD 与否链接 ThirdParty::ZRX/ThirdParty::ARX、注入 ARX/SUB_ARX 宏)、target_set_name_and_suffix(.arx 在 ZWCAD 下自动变 .zrx)、add_def_file_name、set_debug_post_build 等。新增目标应复用这些函数而非手写链接。
  • cmake/ThirdPartyDeps.cmake 注册所有 ThirdParty::* INTERFACE 目标(ARX/ZRX/cURL/SQLite3/BitAnswer/modernGlue/CadOcr)。

约定与注意点

  • 不要改动线程模型:任何涉及 ARX API 的工具逻辑必须在主线程(ExecuteTaskInMainThread 派发链)中执行,不能在 TcpServerLoop 线程里直接调用。
  • C++ 标准随 SDK 版本浮动:cmakelists.txt 按 CAD_SDK_VERSION 自动设定 C++98/03/14/20,跨版本改动需兼容旧标准(低版本 SDK 最高只到 C++14)。
  • 新增 CAD 版本支持时,需在 cmakepresets.json 同步添加 configurePresets 与 buildPresets,并在 cmake/ThirdPartyDeps.cmake 中确认对应库列表(如 CAD_SDK_VERSION GREATER 23 时追加 acpal.lib/acgeoment.lib)。
  • Python 侧不得把 CAD 能力下沉到 MCP:PDF / 图片的解析与渲染属于 agent 侧感知层(dwg_review/sources.py),不塞进 MCP、不编 DLL;只有「CAD 进程内的正交感知能力」才考虑新增 MCP 工具。
  • bin/、lib/、out/ 均为构建产物,已在 .gitignore 中忽略;源码在 src/、inc/、cmake/。