feat(plugins): 截图前检测 CAD 窗口遮挡并自动置前 #7
No files matched your search
@@ -58,6 +58,11 @@ type: project
|
|||||||
这是 AutoCAD 官方 RX 自动加载机制,比 `acaddoc.lsp` + LISP 更直接。
|
这是 AutoCAD 官方 RX 自动加载机制,比 `acaddoc.lsp` + LISP 更直接。
|
||||||
- AutoCAD 2020 安装于 `C:/Program Files/Autodesk/AutoCAD 2020/`;用户配置目录
|
- AutoCAD 2020 安装于 `C:/Program Files/Autodesk/AutoCAD 2020/`;用户配置目录
|
||||||
`C:/Users/gjm/AppData/Roaming/Autodesk/AutoCAD 2020/R23.1/chs/`(`acaddoc.lsp` 已用于 PDF2CAD,勿覆盖)。
|
`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 实测通过)
|
## 验证结论(2026-10-13 实测通过)
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,69 @@ namespace
|
|||||||
free(pImageCodecInfo);
|
free(pImageCodecInfo);
|
||||||
return -1;
|
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
|
class mcp_Tool_ViewportScreenshot: public mcp_Tool
|
||||||
@@ -78,7 +141,18 @@ public:
|
|||||||
if (!hWnd)
|
if (!hWnd)
|
||||||
return mcp_Tool_Utility::make_error(-32000, L"无法获取CAD绘图区窗口句柄");
|
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;
|
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
|
||||||
ULONG_PTR gdiplusToken;
|
ULONG_PTR gdiplusToken;
|
||||||
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
|
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
|
||||||
@@ -86,7 +160,7 @@ public:
|
|||||||
std::string base64Result;
|
std::string base64Result;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
|
|
||||||
// 3. 开始 Windows GDI 抓屏逻辑 (使用花括号限定生命周期)
|
// 4. 开始 Windows GDI 抓屏逻辑 (使用花括号限定生命周期)
|
||||||
{
|
{
|
||||||
RECT rc;
|
RECT rc;
|
||||||
GetClientRect(hWnd, &rc);
|
GetClientRect(hWnd, &rc);
|
||||||
@@ -139,13 +213,23 @@ public:
|
|||||||
ReleaseDC(hWnd, hdcScreen);
|
ReleaseDC(hWnd, hdcScreen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 关闭 GDI+
|
// 5. 关闭 GDI+
|
||||||
Gdiplus::GdiplusShutdown(gdiplusToken);
|
Gdiplus::GdiplusShutdown(gdiplusToken);
|
||||||
|
|
||||||
// 5. 按照 MCP 多模态协议返回数据
|
// 6. 按照 MCP 多模态协议返回数据
|
||||||
if (success)
|
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
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user