// ============================================================================ // dwg_review_view.cpp // 图纸审查 - 视图 / 行动层工具(均为 c++_dll 后端) // zoom_to_entity : 缩放到指定 handle(或 handle 列表)实体 // zoom_to_window : 缩放到指定 WCS 范围(逐图框审查用;对直线组图框尤其实用) // highlight_entities : 按 handle 列表临时高亮(改色) // ============================================================================ #include "KTCad.h" #include "dwg_review_common.h" #include "mcp_plugin_api.h" #include #include #include using namespace dwg_review; namespace { // 数字格式化为命令行可用的定点字符串(避免科学计数法) std::wstring fmt(double v) { wchar_t buf[64]; swprintf_s(buf, 64, L"%.6f", v); return std::wstring(buf); } // 缩放到 WCS 范围(四周按 marginFrac 留边),outView 回传实际使用范围 void zoomToBox(double x1, double y1, double x2, double y2, double marginFrac, double outView[4]) { double w = x2 - x1; double h = y2 - y1; if (w < 1e-6) w = 1.0; if (h < 1e-6) h = 1.0; const double mx = w * marginFrac, my = h * marginFrac; outView[0] = x1 - mx; outView[1] = y1 - my; outView[2] = x2 + mx; outView[3] = y2 + my; std::wstring cmd = L"_.ZOOM _W "; cmd += fmt(outView[0]) + L"," + fmt(outView[1]) + L" "; cmd += fmt(outView[2]) + L"," + fmt(outView[3]) + L" "; acDocManager->sendStringToExecute(acDocManager->curDocument(), cmd.c_str()); } // 按句柄打开实体并取包围盒 bool bboxByHandle(const std::string& hs, BBox& out) { AcDbObjectId id; if (!objectIdFromHandle(hs, id)) return false; AcDbEntity* pEnt = nullptr; if (acdbOpenObject(pEnt, id, AcDb::kForRead) != Acad::eOk || !pEnt) return false; const bool ok = entityBBox(pEnt, out); pEnt->close(); return ok; } // 从 args 里取句柄列表(支持 "handle" 或 "handles") std::vector collectHandles(const nlohmann::json& args) { std::vector hs; if (args.contains("handle") && args["handle"].is_string()) hs.push_back(args["handle"].get()); if (args.contains("handles") && args["handles"].is_array()) { for (size_t i = 0; i < args["handles"].size(); ++i) if (args["handles"][i].is_string()) hs.push_back(args["handles"][i].get()); } return hs; } } // =========================================================================== // zoom_to_entity // =========================================================================== class mcp_Tool_ZoomToEntity : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { const std::vector hs = collectHandles(args); if (hs.empty()) return makeError(kErrBadParam, "handle or handles is required"); // 合并所有目标实体的包围盒 BBox box; bool first = true; for (size_t i = 0; i < hs.size(); ++i) { BBox b; if (!bboxByHandle(hs[i], b)) continue; if (first) { box = b; first = false; } else { if (b.minX < box.minX) box.minX = b.minX; if (b.minY < box.minY) box.minY = b.minY; if (b.maxX > box.maxX) box.maxX = b.maxX; if (b.maxY > box.maxY) box.maxY = b.maxY; } } if (first) return makeError(kErrNotFound, "no valid entity for the given handle(s)"); double view[4]; zoomToBox(box.minX, box.minY, box.maxX, box.maxY, 0.05, view); nlohmann::json r; r["ok"] = true; r["view"] = nlohmann::json::array({ view[0], view[1], view[2], view[3] }); return makeJsonResult(r); } }; EXPORT_MCP_TOOL(mcp_Tool_ZoomToEntity, CreateZoomToEntityTool) // =========================================================================== // zoom_to_window // =========================================================================== class mcp_Tool_ZoomToWindow : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { double x1, y1, x2, y2; if (args.contains("window") && args["window"].is_object()) { const nlohmann::json& w = args["window"]; if (!w.contains("min_x") || !w.contains("min_y") || !w.contains("max_x") || !w.contains("max_y")) return makeError(kErrBadParam, "window requires min_x/min_y/max_x/max_y"); x1 = w["min_x"].get(); y1 = w["min_y"].get(); x2 = w["max_x"].get(); y2 = w["max_y"].get(); } else if (args.contains("min_x") && args.contains("min_y") && args.contains("max_x") && args.contains("max_y")) { x1 = args["min_x"].get(); y1 = args["min_y"].get(); x2 = args["max_x"].get(); y2 = args["max_y"].get(); } else { return makeError(kErrBadParam, "min_x/min_y/max_x/max_y (or window) is required"); } const double margin = args.value("margin", 0.02); double view[4]; zoomToBox(x1, y1, x2, y2, margin, view); nlohmann::json r; r["ok"] = true; r["view"] = nlohmann::json::array({ view[0], view[1], view[2], view[3] }); return makeJsonResult(r); } }; EXPORT_MCP_TOOL(mcp_Tool_ZoomToWindow, CreateZoomToWindowTool) // =========================================================================== // highlight_entities // =========================================================================== class mcp_Tool_HighlightEntities : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { const std::vector hs = collectHandles(args); if (hs.empty()) return makeError(kErrBadParam, "handle or handles is required"); // restore=true 时把颜色复位为 ByLayer(256) const bool restore = args.value("restore", false); const int color = restore ? 256 : args.value("color", 1); // 默认红色 int count = 0; for (size_t i = 0; i < hs.size(); ++i) { AcDbObjectId id; if (!objectIdFromHandle(hs[i], id)) continue; AcDbEntity* pEnt = nullptr; if (acdbOpenObject(pEnt, id, AcDb::kForWrite) != Acad::eOk || !pEnt) continue; pEnt->setColorIndex((Adesk::UInt16)color); pEnt->draw(); pEnt->close(); ++count; } nlohmann::json r; r["ok"] = true; r["count"] = count; return makeJsonResult(r); } }; EXPORT_MCP_TOOL(mcp_Tool_HighlightEntities, CreateHighlightEntitiesTool)