feat(review): 截屏前最大化绘图区 + 空瓦片过滤覆盖回退路径
新增 MCP 工具 set_drawing_area(mode="max"|"restore"):最大化主窗口 + CLEANSCREENON (隐藏功能区/命令行/面板)。实测绘图区 2110x462 -> 3310x1084(面积 3.68x), restore 精确回位(窗口矩形/最大化态/绘图区尺寸全部还原)。 坑:CLEANSCREENOFF 是异步命令,AutoCAD 会按「进入干净屏幕时的布局」重设窗口几何, 把同步的 SetWindowPlacement 覆盖掉(表现为窗口停在最大化)。解法是插件内 SetTimer 延迟 700ms 再压回;实测窗口在 0.5~1.0s 之间收敛,故调用方等待取 1.5s。 取证层: - 新增 maximized_drawing_area(ctx) 上下文管理器,__main__ 用它包住整个取证循环 (多图框只进出一次);受 DRAWING_AREA_MAXIMIZE 控制,工具缺失/调用失败静默降级, restore 失败不掩盖业务异常。 - 内容过滤统一到规划与回退两条路径,共用同一谓词;过滤后为空则退回原集合。 - 判定改用「实体 bbox」而非「占用格」:格子量化会把跨界内容算进相邻格,反而更粗 (实测实体 bbox 正好压象限边界时 4 个象限全判有内容)。heatmap.occupied_boxes 随之删除。 实测(真实图纸 A0/A1/A2 三图框):截图张数 12/8/4 -> 8/5/4;pytest 296 passed。 新增 verify_drawing_area.py 作为该工具的验收脚本。
This commit is contained in:
1 parent
9c4ab62dc1
commit
1e5bff268a
15 files changed
+657
-50
No files matched your search
@@ -50,6 +50,26 @@
|
||||
"factory_function": "CreateViewportScreenshotTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "set_drawing_area",
|
||||
"description": "设置绘图区为最大化状态,显著提升截图像素与细节可读性。mode=max 时最大化 CAD 主窗口并进入干净屏幕(隐藏功能区/命令行/面板);mode=restore 时还原。截图取证前调用 max、结束后必须调用 restore。",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["max", "restore"],
|
||||
"description": "max=最大化绘图区(最大化主窗口并进入干净屏幕,隐藏功能区/命令行/面板);restore=还原调用前的窗口位置与干净屏幕状态"
|
||||
}
|
||||
},
|
||||
"required": ["mode"]
|
||||
},
|
||||
"backend": {
|
||||
"type": "c++_dll",
|
||||
"dll_name": "cad_mcp_plugins.dll",
|
||||
"factory_function": "CreateSetDrawingAreaTool"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "get_drawing_info",
|
||||
"description": "获取当前图纸的元数据:文件名、单位、布局数量、各空间实体总数。审查流程的第一步。",
|
||||
|
||||
@@ -14,6 +14,7 @@ set(SOURCES
|
||||
"dwg_review_frame.cpp"
|
||||
"dwg_review_check.cpp"
|
||||
"dwg_review_view.cpp"
|
||||
"dwg_review_ui.cpp"
|
||||
"dwg_annotate_common.cpp"
|
||||
"dwg_annotate_draw.cpp"
|
||||
"dwg_annotate_edit.cpp"
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
// ============================================================================
|
||||
// dwg_review_ui.cpp
|
||||
// 图纸审查 - 绘图区界面控制工具(c++_dll 后端)
|
||||
// set_drawing_area : 最大化绘图区(最大化主窗口 + 进入「干净屏幕」)与还原
|
||||
//
|
||||
// 背景:截图质量与「绘图区像素」直接相关。实测 CAD 主窗口未最大化时,绘图区只有
|
||||
// 2110x462 —— 竖向被标题栏 / 功能区 / 命令行 / 状态栏吃掉约 500px。最大化窗口并进入
|
||||
// 干净屏幕(隐藏功能区 / 命令行 / 面板)后绘图区显著变大;同样按长边 1280 下采样时
|
||||
// 有效像素更多、视口宽高比更接近图纸,自适应瓦片张数更少。
|
||||
//
|
||||
// 实测(AutoCAD 2020,屏幕 3440x1440,测试图纸)绘图区像素:
|
||||
// 非最大化 / 无干净屏幕 : 2110 x 462
|
||||
// 最大化 + 干净屏幕 : 3310 x 1084 → 面积 3.68x
|
||||
//
|
||||
// 重要:本工具会**改动用户的 CAD 界面**(窗口位置 + 干净屏幕状态)。
|
||||
// 调用方必须在 finally 里以 mode="restore" 还原;还原信息在本进程内静态保存。
|
||||
// 注意 restore 的窗口几何是**延迟**压回的(CLEANSCREENOFF 异步,见 restore 分支),
|
||||
// 调用方在 restore 后应等待约 0.8s 再认为界面已还原。
|
||||
// ============================================================================
|
||||
|
||||
#include "cadsdk.h"
|
||||
#include "dwg_review_common.h"
|
||||
#include "mcp_plugin_api.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <string>
|
||||
|
||||
using namespace dwg_review;
|
||||
|
||||
namespace
|
||||
{
|
||||
// 进程内保存的还原信息(工具调用跨请求,故用静态变量)。
|
||||
struct SavedUiState
|
||||
{
|
||||
bool valid;
|
||||
bool cleanWasOn;
|
||||
WINDOWPLACEMENT place;
|
||||
|
||||
SavedUiState() : valid(false), cleanWasOn(false)
|
||||
{
|
||||
place.length = sizeof(WINDOWPLACEMENT);
|
||||
}
|
||||
};
|
||||
SavedUiState g_saved;
|
||||
|
||||
// 待延迟还原的窗口(见 restore 分支:CLEANSCREENOFF 是异步命令,
|
||||
// AutoCAD 处理它时会按「进入干净屏幕时的布局」重设窗口几何,
|
||||
// 覆盖掉我们同步执行的 SetWindowPlacement,故需延后一段时间再压回去)。
|
||||
HWND g_pendingRestore = NULL;
|
||||
|
||||
// 把保存的窗口布局压回去(幂等;无保存信息则空操作)。
|
||||
void applySavedPlacement()
|
||||
{
|
||||
if (g_pendingRestore && ::IsWindow(g_pendingRestore) && g_saved.valid)
|
||||
::SetWindowPlacement(g_pendingRestore, &g_saved.place);
|
||||
g_pendingRestore = NULL;
|
||||
g_saved.valid = false;
|
||||
}
|
||||
|
||||
void CALLBACK delayedRestoreProc(HWND, UINT, UINT_PTR idEvent, DWORD)
|
||||
{
|
||||
::KillTimer(NULL, idEvent);
|
||||
applySavedPlacement();
|
||||
}
|
||||
|
||||
// CAD 主框架窗口:优先 adsw_acadMainWnd(),退回绘图视图的根窗口。
|
||||
HWND mainFrame()
|
||||
{
|
||||
HWND h = adsw_acadMainWnd();
|
||||
if (h && ::IsWindow(h)) return h;
|
||||
CView *v = acedGetAcadDwgView();
|
||||
if (v && v->m_hWnd) return ::GetAncestor(v->m_hWnd, GA_ROOT);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 绘图视图窗口(仅图形区,不含滚动条 / 状态栏)。
|
||||
HWND dwgView()
|
||||
{
|
||||
CView *v = acedGetAcadDwgView();
|
||||
if (v && v->m_hWnd) return v->m_hWnd;
|
||||
return adsw_acadDocWnd();
|
||||
}
|
||||
|
||||
// 发送命令(异步;调用方需等待渲染)。
|
||||
void sendCmd(const wchar_t *cmd)
|
||||
{
|
||||
if (acDocManager && acDocManager->curDocument())
|
||||
acDocManager->sendStringToExecute(acDocManager->curDocument(), cmd);
|
||||
}
|
||||
|
||||
// 读取整型系统变量;失败返回 fallback。
|
||||
int getIntVar(const ACHAR *name, int fallback)
|
||||
{
|
||||
struct resbuf rb;
|
||||
if (acedGetVar(name, &rb) == RTNORM && rb.restype == RTSHORT)
|
||||
return rb.resval.rint;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
nlohmann::json viewSize()
|
||||
{
|
||||
nlohmann::json a = nlohmann::json::array();
|
||||
HWND h = dwgView();
|
||||
RECT rc;
|
||||
if (!h || !::GetClientRect(h, &rc)) return a;
|
||||
a.push_back(rc.right - rc.left);
|
||||
a.push_back(rc.bottom - rc.top);
|
||||
return a;
|
||||
}
|
||||
|
||||
nlohmann::json mainRect()
|
||||
{
|
||||
nlohmann::json a = nlohmann::json::array();
|
||||
HWND h = mainFrame();
|
||||
RECT rc;
|
||||
if (!h || !::GetWindowRect(h, &rc)) return a;
|
||||
a.push_back(rc.left);
|
||||
a.push_back(rc.top);
|
||||
a.push_back(rc.right);
|
||||
a.push_back(rc.bottom);
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
class mcp_Tool_SetDrawingArea : public mcp_Tool
|
||||
{
|
||||
public:
|
||||
virtual nlohmann::json Execute(const nlohmann::json &args) override
|
||||
{
|
||||
std::string mode = "max";
|
||||
if (args.contains("mode") && args["mode"].is_string())
|
||||
mode = args["mode"].get<std::string>();
|
||||
|
||||
HWND hMain = mainFrame();
|
||||
if (!hMain)
|
||||
return makeError(kErrBadParam, "cannot locate AutoCAD main window");
|
||||
|
||||
nlohmann::json r;
|
||||
|
||||
if (mode == "max")
|
||||
{
|
||||
// 只保存一次(重复 max 不会覆盖首次的原始状态)
|
||||
if (!g_saved.valid)
|
||||
{
|
||||
g_saved.place.length = sizeof(WINDOWPLACEMENT);
|
||||
::GetWindowPlacement(hMain, &g_saved.place);
|
||||
// CLEANSCREENSTATE 读不到时按「原本没开」处理
|
||||
g_saved.cleanWasOn = (getIntVar(L"CLEANSCREENSTATE", 0) != 0);
|
||||
g_saved.valid = true;
|
||||
}
|
||||
if (!::IsZoomed(hMain))
|
||||
::ShowWindow(hMain, SW_MAXIMIZE);
|
||||
if (!g_saved.cleanWasOn)
|
||||
sendCmd(L"_.CLEANSCREENON ");
|
||||
r["mode"] = "max";
|
||||
r["clean_before"] = g_saved.cleanWasOn;
|
||||
}
|
||||
else if (mode == "restore")
|
||||
{
|
||||
if (g_saved.valid)
|
||||
{
|
||||
if (!g_saved.cleanWasOn)
|
||||
{
|
||||
// 退出干净屏幕是**异步**命令:AutoCAD 随后会按「进入干净屏幕时的布局」
|
||||
// 重设窗口几何,把我们同步执行的 SetWindowPlacement 覆盖掉
|
||||
// (表现为窗口仍最大化)。故延后 700ms 再压回原始布局。
|
||||
sendCmd(L"_.CLEANSCREENOFF ");
|
||||
g_pendingRestore = hMain;
|
||||
::SetTimer(NULL, 0, 700, delayedRestoreProc);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 原本就在干净屏幕:无异步覆盖,直接同步还原。
|
||||
g_pendingRestore = hMain;
|
||||
applySavedPlacement();
|
||||
}
|
||||
}
|
||||
r["mode"] = "restore";
|
||||
}
|
||||
else
|
||||
{
|
||||
return makeError(kErrBadParam, "mode must be 'max' or 'restore'");
|
||||
}
|
||||
|
||||
r["ok"] = true;
|
||||
r["main_rect"] = mainRect();
|
||||
r["view_size"] = viewSize(); // 注意:CLEANSCREENON 异步,刚调用时可能还是旧值
|
||||
return makeJsonResult(r);
|
||||
}
|
||||
};
|
||||
|
||||
EXPORT_MCP_TOOL(mcp_Tool_SetDrawingArea, CreateSetDrawingAreaTool)
|
||||
Reference in new issue
Block a user