// ============================================================================ // dwg_review_query.cpp // 图纸审查 - 数据提取层工具(均为 c++_dll 后端,统一分页返回) // query_entities : 按图层/类型/范围查询图元摘要 // get_text_content : 提取 TEXT / MTEXT / ATTRIB 文字 // get_dimensions : 提取标注 // get_block_references : 提取块参照及其属性 // get_polylines_info : 多段线闭合性 / 面积 / 长度 // get_entity_detail : 按 handle 取单个实体完整属性 // // 返回统一分页信封:{ total, returned, offset, items:[...] } // 实体 ID 统一使用 handle。 // ============================================================================ #include "KTCad.h" #include "dwg_review_common.h" #include "mcp_plugin_api.h" #include "dbdim.h" // AcDbDimension(cadsdk 未包含) #include using namespace dwg_review; namespace { // 类型过滤:filter 为空数组/非数组时全部通过 bool typeMatches(const std::string& type, const nlohmann::json& filter) { if (!filter.is_array() || filter.empty()) return true; for (size_t i = 0; i < filter.size(); ++i) { if (filter[i].is_string() && filter[i].get() == type) return true; } return false; } // 取类名(去掉 AcDb 前缀),如 "RotatedDimension" std::string rawClassName(AcRxObject* pObj) { if (!pObj) return std::string(); std::string n = acharToUtf8(pObj->isA()->name()); if (n.rfind("AcDb", 0) == 0) n = n.substr(4); return n; } // 取标注样式名 std::string dimStyleName(AcDbObjectId id) { AcDbDimStyleTableRecord* pRec = nullptr; if (acdbOpenObject(pRec, id, AcDb::kForRead) == Acad::eOk && pRec) { const ACHAR* nm = NULL; std::string s; if (pRec->getName(nm) == Acad::eOk && nm) s = acharToUtf8(nm); pRec->close(); return s; } return std::string(); } // 计算多段线总长度 double polyLength(AcDbPolyline* p) { if (!p) return 0.0; double len = 0.0; const int n = p->numVerts(); for (int i = 0; i + 1 < n; ++i) { AcGePoint2d a, b; if (p->getPointAt(i, a) == Acad::eOk && p->getPointAt(i + 1, b) == Acad::eOk) len += a.distanceTo(b); } if (p->isClosed() && n > 1) { AcGePoint2d a, b; if (p->getPointAt(n - 1, a) == Acad::eOk && p->getPointAt(0, b) == Acad::eOk) len += a.distanceTo(b); } return len; } // 通用摘要:handle / type / layer / bbox + 类型相关 properties nlohmann::json buildEntitySummary(AcDbEntity* pEnt) { nlohmann::json o; o["handle"] = handleOf(pEnt); o["type"] = entityTypeName(pEnt); o["layer"] = layerOf(pEnt); BBox b; if (entityBBox(pEnt, b)) o["bbox"] = bboxJson(b); nlohmann::json props = nlohmann::json::object(); if (AcDbLine* p = AcDbLine::cast(pEnt)) { props["start"] = point3Json(p->startPoint().x, p->startPoint().y, p->startPoint().z); props["end"] = point3Json(p->endPoint().x, p->endPoint().y, p->endPoint().z); } else if (AcDbArc* p = AcDbArc::cast(pEnt)) { props["center"] = point3Json(p->center().x, p->center().y, p->center().z); props["radius"] = p->radius(); props["start_angle"] = p->startAngle(); props["end_angle"] = p->endAngle(); } else if (AcDbCircle* p = AcDbCircle::cast(pEnt)) { props["center"] = point3Json(p->center().x, p->center().y, p->center().z); props["radius"] = p->radius(); } else if (AcDbPolyline* p = AcDbPolyline::cast(pEnt)) { props["vertex_count"] = p->numVerts(); props["closed"] = p->isClosed(); } else if (AcDbPoint* p = AcDbPoint::cast(pEnt)) { props["position"] = point3Json(p->position().x, p->position().y, p->position().z); } else if (AcDbBlockReference* p = AcDbBlockReference::cast(pEnt)) { props["block_name"] = blockNameOf(p); } o["properties"] = props; return o; } } // =========================================================================== // query_entities // =========================================================================== class mcp_Tool_QueryEntities : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { const CommonQuery q = parseCommonQuery(args); const nlohmann::json typeFilter = args.contains("type_filter") ? args["type_filter"] : nlohmann::json(); int total = 0, returned = 0; nlohmann::json items = nlohmann::json::array(); forEachEntity(q, [&](AcDbEntity* pEnt) -> bool { if (!typeMatches(entityTypeName(pEnt), typeFilter)) return true; if (total >= q.offset && returned < q.limit) { items.push_back(buildEntitySummary(pEnt)); ++returned; } ++total; return true; }); return makeJsonResult(pageResult(total, returned, q.offset, items)); } }; EXPORT_MCP_TOOL(mcp_Tool_QueryEntities, CreateQueryEntitiesTool) // =========================================================================== // get_text_content // =========================================================================== class mcp_Tool_GetTextContent : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { const CommonQuery q = parseCommonQuery(args); const std::string contentFilter = args.value("content_filter", std::string()); int total = 0, returned = 0; nlohmann::json items = nlohmann::json::array(); forEachEntity(q, [&](AcDbEntity* pEnt) -> bool { const std::string type = entityTypeName(pEnt); if (type != "TEXT" && type != "MTEXT" && type != "ATTRIB") return true; std::string content; double height = 0.0; nlohmann::json o; o["handle"] = handleOf(pEnt); o["type"] = type; o["layer"] = layerOf(pEnt); if (AcDbMText* p = AcDbMText::cast(pEnt)) { content = acharToUtf8(p->contents()); height = p->textHeight(); o["insert"] = point3Json(p->location().x, p->location().y, p->location().z); } else if (AcDbAttribute* p = AcDbAttribute::cast(pEnt)) { content = acharToUtf8(p->textString()); height = p->height(); o["insert"] = point3Json(p->position().x, p->position().y, p->position().z); } else if (AcDbText* p = AcDbText::cast(pEnt)) { content = acharToUtf8(p->textString()); height = p->height(); o["rotation"] = p->rotation(); o["insert"] = point3Json(p->position().x, p->position().y, p->position().z); } else { return true; } // 可选内容过滤(子串,大小写敏感) if (!contentFilter.empty() && content.find(contentFilter) == std::string::npos) return true; o["content"] = content; o["height"] = height; BBox b; if (entityBBox(pEnt, b)) o["bbox"] = bboxJson(b); if (total >= q.offset && returned < q.limit) { items.push_back(o); ++returned; } ++total; return true; }); return makeJsonResult(pageResult(total, returned, q.offset, items)); } }; EXPORT_MCP_TOOL(mcp_Tool_GetTextContent, CreateGetTextContentTool) // =========================================================================== // get_dimensions // =========================================================================== class mcp_Tool_GetDimensions : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { const CommonQuery q = parseCommonQuery(args); int total = 0, returned = 0; nlohmann::json items = nlohmann::json::array(); forEachEntity(q, [&](AcDbEntity* pEnt) -> bool { AcDbDimension* pDim = AcDbDimension::cast(pEnt); if (!pDim) return true; if (total >= q.offset && returned < q.limit) { nlohmann::json o; o["handle"] = handleOf(pEnt); o["type"] = rawClassName(pDim); o["layer"] = layerOf(pEnt); double meas = 0.0; pDim->measurement(meas); o["measurement"] = meas; o["text_override"] = acharToUtf8(pDim->dimensionText()); o["dim_style"] = dimStyleName(pDim->dimensionStyle()); items.push_back(o); ++returned; } ++total; return true; }); return makeJsonResult(pageResult(total, returned, q.offset, items)); } }; EXPORT_MCP_TOOL(mcp_Tool_GetDimensions, CreateGetDimensionsTool) // =========================================================================== // get_block_references // =========================================================================== class mcp_Tool_GetBlockReferences : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { const CommonQuery q = parseCommonQuery(args); const std::string blockFilter = args.value("block_name_filter", std::string()); int total = 0, returned = 0; nlohmann::json items = nlohmann::json::array(); forEachEntity(q, [&](AcDbEntity* pEnt) -> bool { AcDbBlockReference* pRef = AcDbBlockReference::cast(pEnt); if (!pRef) return true; const std::string blk = blockNameOf(pRef); if (!blockFilter.empty() && !wildcardMatch(blk, blockFilter)) return true; if (total >= q.offset && returned < q.limit) { nlohmann::json o; o["handle"] = handleOf(pEnt); o["type"] = "INSERT"; o["layer"] = layerOf(pEnt); o["block_name"] = blk; o["insert"] = point3Json(pRef->position().x, pRef->position().y, pRef->position().z); o["rotation"] = pRef->rotation(); AcGeScale3d sc = pRef->scaleFactors(); o["scale"] = nlohmann::json::array({ sc.sx, sc.sy, sc.sz }); // 属性 nlohmann::json atts = nlohmann::json::array(); AcDbObjectIterator* ait = pRef->attributeIterator(); if (ait) { for (; !ait->done(); ait->step()) { AcDbAttribute* pAtt = nullptr; if (acdbOpenObject(pAtt, ait->objectId(), AcDb::kForRead) == Acad::eOk && pAtt) { nlohmann::json a; a["tag"] = acharToUtf8(pAtt->tag()); a["value"] = acharToUtf8(pAtt->textString()); atts.push_back(a); pAtt->close(); } } delete ait; } o["attributes"] = atts; BBox b; if (entityBBox(pEnt, b)) o["bbox"] = bboxJson(b); items.push_back(o); ++returned; } ++total; return true; }); return makeJsonResult(pageResult(total, returned, q.offset, items)); } }; EXPORT_MCP_TOOL(mcp_Tool_GetBlockReferences, CreateGetBlockReferencesTool) // =========================================================================== // get_polylines_info // =========================================================================== class mcp_Tool_GetPolylinesInfo : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { const CommonQuery q = parseCommonQuery(args); int total = 0, returned = 0; nlohmann::json items = nlohmann::json::array(); forEachEntity(q, [&](AcDbEntity* pEnt) -> bool { AcDbPolyline* p = AcDbPolyline::cast(pEnt); if (!p) return true; if (total >= q.offset && returned < q.limit) { nlohmann::json o; o["handle"] = handleOf(pEnt); o["type"] = "LWPOLYLINE"; o["layer"] = layerOf(pEnt); o["vertex_count"] = p->numVerts(); o["closed"] = p->isClosed(); o["length"] = polyLength(p); double area = 0.0; if (p->getArea(area) == Acad::eOk) o["area"] = area; BBox b; if (entityBBox(pEnt, b)) o["bbox"] = bboxJson(b); items.push_back(o); ++returned; } ++total; return true; }); return makeJsonResult(pageResult(total, returned, q.offset, items)); } }; EXPORT_MCP_TOOL(mcp_Tool_GetPolylinesInfo, CreateGetPolylinesInfoTool) // =========================================================================== // get_entity_detail // =========================================================================== class mcp_Tool_GetEntityDetail : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { if (!args.contains("handle") || !args["handle"].is_string()) return makeError(kErrBadParam, "handle is required"); const std::string hs = args["handle"].get(); AcDbObjectId id; if (!objectIdFromHandle(hs, id)) return makeError(kErrNotFound, "invalid handle: " + hs); AcDbEntity* pEnt = nullptr; if (acdbOpenObject(pEnt, id, AcDb::kForRead) != Acad::eOk || !pEnt) return makeError(kErrNotFound, "entity not found: " + hs); nlohmann::json o = buildEntitySummary(pEnt); // 补充文字 / 标注 / 块 等细节 if (AcDbText* p = AcDbText::cast(pEnt)) { o["text"] = acharToUtf8(p->textString()); o["height"] = p->height(); o["rotation"] = p->rotation(); } else if (AcDbMText* p = AcDbMText::cast(pEnt)) { o["text"] = acharToUtf8(p->contents()); o["height"] = p->textHeight(); } else if (AcDbDimension* p = AcDbDimension::cast(pEnt)) { double meas = 0.0; p->measurement(meas); o["measurement"] = meas; o["text_override"] = acharToUtf8(p->dimensionText()); o["dim_style"] = dimStyleName(p->dimensionStyle()); } pEnt->close(); return makeJsonResult(o); } }; EXPORT_MCP_TOOL(mcp_Tool_GetEntityDetail, CreateGetEntityDetailTool)