From 40aff77dd10103179693c70c113a908a13088283 Mon Sep 17 00:00:00 2001 From: LittleGuo Date: Tue, 13 Oct 2026 23:55:40 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(plugins):=20=E6=88=AA=E5=9B=BE?= =?UTF-8?q?=E5=89=8D=E6=A3=80=E6=B5=8B=20CAD=20=E7=AA=97=E5=8F=A3=E9=81=AE?= =?UTF-8?q?=E6=8C=A1=E5=B9=B6=E8=87=AA=E5=8A=A8=E7=BD=AE=E5=89=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit get_viewport_screenshot 使用 GDI 抓屏(GetDC+BitBlt 读屏幕内容), 若 CAD 窗口被其它窗口遮挡会截到遮挡窗口。现增加: - IsWindowOccluded:客户区中心点经 WindowFromPoint 取顶层窗口, 根窗口不同即判定被遮挡。 - ForceWindowForeground:AttachThreadInput + SetWindowPos/BringWindowToTop/ SetForegroundWindow 强制置前,RedrawWindow(RDW_UPDATENOW) 同步重绘。 - Execute 中先检测遮挡/前台状态,必要时置前并 Sleep(120) 等 DWM 合成, 返回文本按「已置前」「仍被遮挡」给出提示。 --- src/cad_mcp_plugins/mcp_plugins.cpp | 94 +++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/src/cad_mcp_plugins/mcp_plugins.cpp b/src/cad_mcp_plugins/mcp_plugins.cpp index 8425c96..e4e602b 100644 --- a/src/cad_mcp_plugins/mcp_plugins.cpp +++ b/src/cad_mcp_plugins/mcp_plugins.cpp @@ -66,6 +66,69 @@ namespace free(pImageCodecInfo); return -1; } + + // 判断窗口客户区中心点是否被其它窗口遮挡。 + // 原理:取客户区中心换算到屏幕坐标,用 WindowFromPoint 取该点最顶层窗口, + // 若其根窗口与目标窗口的根窗口不同,即视为被遮挡。 + bool IsWindowOccluded(HWND hwnd) + { + if (!hwnd || !::IsWindow(hwnd)) + return false; + + RECT rc; + if (!::GetClientRect(hwnd, &rc)) + return false; + + POINT pt; + pt.x = (rc.left + rc.right) / 2; + pt.y = (rc.top + rc.bottom) / 2; + ::ClientToScreen(hwnd, &pt); + + HWND hAtPoint = ::WindowFromPoint(pt); + if (!hAtPoint) + return false; + + return ::GetAncestor(hAtPoint, GA_ROOT) != ::GetAncestor(hwnd, GA_ROOT); + } + + // 把窗口强制置到前台并重绘。 + // SetForegroundWindow 在调用进程不是前台进程时会被系统忽略,故先用 + // AttachThreadInput 把当前线程与前台线程的输入队列挂接,再设置前台窗口。 + bool ForceWindowForeground(HWND hwnd) + { + if (!hwnd || !::IsWindow(hwnd)) + return false; + + if (::IsIconic(hwnd)) + ::ShowWindow(hwnd, SW_RESTORE); + if (!::IsWindowVisible(hwnd)) + ::ShowWindow(hwnd, SW_SHOW); + + if (::GetForegroundWindow() == hwnd) + return true; + + HWND hFore = ::GetForegroundWindow(); + DWORD foreThread = hFore ? ::GetWindowThreadProcessId(hFore, NULL) : 0; + DWORD thisThread = ::GetCurrentThreadId(); + + bool attached = false; + if (foreThread != 0 && foreThread != thisThread) + attached = (::AttachThreadInput(foreThread, thisThread, TRUE) != 0); + + ::SetWindowPos(hwnd, HWND_TOP, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW); + ::BringWindowToTop(hwnd); + ::SetForegroundWindow(hwnd); + + if (attached) + ::AttachThreadInput(foreThread, thisThread, FALSE); + + // 同步重绘,避免截到旧画面(RDW_UPDATENOW 会立即处理 WM_PAINT) + ::RedrawWindow(hwnd, NULL, NULL, + RDW_INVALIDATE | RDW_UPDATENOW | RDW_ALLCHILDREN); + + return ::GetForegroundWindow() == hwnd; + } } class mcp_Tool_ViewportScreenshot: public mcp_Tool @@ -78,7 +141,18 @@ public: if (!hWnd) return mcp_Tool_Utility::make_error(-32000, L"无法获取CAD绘图区窗口句柄"); - // 2. 初始化 GDI+ + // 2. 确保窗口在前台:GDI 抓屏读的是屏幕内容,被其它窗口遮挡就会截到别的窗口。 + // 先尝试把 CAD 主窗口置前并重绘,再记录是否仍被遮挡。 + bool wasOccluded = IsWindowOccluded(hWnd); + if (wasOccluded || ::GetForegroundWindow() != ::GetAncestor(hWnd, GA_ROOT)) + { + HWND hMain = adsw_acadMainWnd(); + ForceWindowForeground(hMain ? hMain : hWnd); + ::Sleep(120); // 留一点时间给 DWM 合成,避免截到旧帧 + } + bool stillOccluded = IsWindowOccluded(hWnd); + + // 3. 初始化 GDI+ Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); @@ -86,7 +160,7 @@ public: std::string base64Result; bool success = false; - // 3. 开始 Windows GDI 抓屏逻辑 (使用花括号限定生命周期) + // 4. 开始 Windows GDI 抓屏逻辑 (使用花括号限定生命周期) { RECT rc; GetClientRect(hWnd, &rc); @@ -139,13 +213,23 @@ public: ReleaseDC(hWnd, hdcScreen); } - // 4. 关闭 GDI+ + // 5. 关闭 GDI+ Gdiplus::GdiplusShutdown(gdiplusToken); - // 5. 按照 MCP 多模态协议返回数据 + // 6. 按照 MCP 多模态协议返回数据 if (success) { - return mcp_Tool_Utility::make_mixed_result(L"已成功截取 AutoCAD 当前视口画面。", base64Result, "image/png"); + std::wstring msg = L"已成功截取 AutoCAD 当前视口画面。"; + if (stillOccluded) + { + // 窗口被遮挡时 GDI 抓屏会读到遮挡窗口的内容,截图不可靠,显式提示。 + msg += L"(警告:CAD 绘图区可能仍被其它窗口遮挡,截图内容可能不准确)"; + } + else if (wasOccluded) + { + msg += L"(已将 CAD 窗口置前,避免截到遮挡窗口)"; + } + return mcp_Tool_Utility::make_mixed_result(msg, base64Result, "image/png"); } else { -- 2.54.0 From bcc4f72e91d718fde48e0aaa7508950892d0847e Mon Sep 17 00:00:00 2001 From: LittleGuo Date: Tue, 13 Oct 2026 23:56:10 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs(memory):=20=E8=AE=B0=E5=BD=95=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=20DLL=20=E9=87=8D=E9=93=BE=E6=8E=A5=E9=9C=80=E5=85=88?= =?UTF-8?q?=E5=85=B3=E9=97=AD=20CAD=EF=BC=88=E6=97=A0=E7=83=AD=E9=87=8D?= =?UTF-8?q?=E8=BD=BD=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ai.memory/project_review_agent_impl.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ai.memory/project_review_agent_impl.md b/ai.memory/project_review_agent_impl.md index fe29557..d466d38 100644 --- a/ai.memory/project_review_agent_impl.md +++ b/ai.memory/project_review_agent_impl.md @@ -58,6 +58,11 @@ type: project 这是 AutoCAD 官方 RX 自动加载机制,比 `acaddoc.lsp` + LISP 更直接。 - AutoCAD 2020 安装于 `C:/Program Files/Autodesk/AutoCAD 2020/`;用户配置目录 `C:/Users/gjm/AppData/Roaming/Autodesk/AutoCAD 2020/R23.1/chs/`(`acaddoc.lsp` 已用于 PDF2CAD,勿覆盖)。 +- **改 `cad_mcp_plugins` 后重链接必须先关 CAD**:插件 DLL 被 CAD 进程持有(`LNK1168: cannot open + ...dll for writing`)。模板 `cad-mcp-frame.json` 里的 `"hot_reload": true` **只是占位、代码未实现**, + `mcp_Tools::clear()`(唯一 `FreeLibrary` 处)仅在 ARX 卸载时调用 → **没有热重载**。 + 流程:关 CAD → `cmake --build --preset R230 --config Debug` → 重启 `acad.exe`(`acad.rx` 自动加载) + → 轮询 `127.0.0.1:8080` 监听。编译阶段可先行(语法错误会被提前暴露,只有链接会因占用失败)。 ## 验证结论(2026-10-13 实测通过) -- 2.54.0