// ============================================================================ // dwg_review_check.cpp // 图纸审查 - 确定性检查工具(均为 c++_dll 后端) // find_duplicate_entities : 几何重复 / 重叠实体 // find_zero_length : 零长度 / 退化实体 // ============================================================================ #include "KTCad.h" #include "dwg_review_common.h" #include "mcp_plugin_api.h" #include #include #include #include #include using namespace dwg_review; namespace { const double kPi = 3.14159265358979323846; 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; } // 把坐标量化到 tolerance 网格,得到可比较的字符串 std::string quant(double v, double tol) { long long k = (long long)::llround(v / (tol > 1e-12 ? tol : 1e-12)); return std::to_string(k); } std::string q3(double x, double y, double z, double tol) { return quant(x, tol) + "," + quant(y, tol) + "," + quant(z, tol); } // 计算多段线总长度 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; } // 构造几何签名(仅对支持的类型返回 true) bool buildSignature(AcDbEntity* pEnt, double tol, const nlohmann::json& typeFilter, std::string& sig, std::string& type) { type = entityTypeName(pEnt); if (!typeMatches(type, typeFilter)) return false; if (AcDbLine* p = AcDbLine::cast(pEnt)) { double ax = p->startPoint().x, ay = p->startPoint().y, az = p->startPoint().z; double bx = p->endPoint().x, by = p->endPoint().y, bz = p->endPoint().z; // 端点顺序无关 if (ax > bx || (ax == bx && ay > by) || (ax == bx && ay == by && az > bz)) { std::swap(ax, bx); std::swap(ay, by); std::swap(az, bz); } sig = "LINE:" + q3(ax, ay, az, tol) + ";" + q3(bx, by, bz, tol); return true; } if (AcDbArc* p = AcDbArc::cast(pEnt)) { sig = "ARC:" + q3(p->center().x, p->center().y, p->center().z, tol) + ";r" + quant(p->radius(), tol) + ";a" + quant(p->startAngle(), tol) + ";" + quant(p->endAngle(), tol); return true; } if (AcDbCircle* p = AcDbCircle::cast(pEnt)) { sig = "CIRCLE:" + q3(p->center().x, p->center().y, p->center().z, tol) + ";r" + quant(p->radius(), tol); return true; } if (AcDbPoint* p = AcDbPoint::cast(pEnt)) { sig = "POINT:" + q3(p->position().x, p->position().y, p->position().z, tol); return true; } if (AcDbPolyline* p = AcDbPolyline::cast(pEnt)) { std::string s = "LWPOLYLINE:"; const int n = p->numVerts(); for (int i = 0; i < n; ++i) { AcGePoint2d pt; if (p->getPointAt(i, pt) != Acad::eOk) return false; s += q3(pt.x, pt.y, 0.0, tol) + "|"; } s += (p->isClosed() ? "C" : "O"); sig = s; return true; } return false; } } // =========================================================================== // find_duplicate_entities // =========================================================================== class mcp_Tool_FindDuplicateEntities : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { const CommonQuery q = parseCommonQuery(args); const double tol = args.value("tolerance", 0.001); const nlohmann::json typeFilter = args.contains("type_filter") ? args["type_filter"] : nlohmann::json(); const int groupLimit = args.value("limit", 200); std::map > buckets; std::map bucketType; forEachEntity(q, [&](AcDbEntity* pEnt) -> bool { std::string sig, type; if (buildSignature(pEnt, tol, typeFilter, sig, type)) { buckets[sig].push_back(handleOf(pEnt)); bucketType[sig] = type; } return true; }); nlohmann::json groups = nlohmann::json::array(); int count = 0; for (std::map >::iterator it = buckets.begin(); it != buckets.end(); ++it) { if (it->second.size() < 2) continue; // 只保留重复组 if (count >= groupLimit) break; nlohmann::json g; g["reason"] = "identical_geometry"; g["type"] = bucketType[it->first]; nlohmann::json hs = nlohmann::json::array(); for (size_t i = 0; i < it->second.size(); ++i) hs.push_back(it->second[i]); g["handles"] = hs; groups.push_back(g); ++count; } nlohmann::json r; r["count"] = count; r["groups"] = groups; return makeJsonResult(r); } }; EXPORT_MCP_TOOL(mcp_Tool_FindDuplicateEntities, CreateFindDuplicateEntitiesTool) // =========================================================================== // find_zero_length // =========================================================================== class mcp_Tool_FindZeroLength : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { const CommonQuery q = parseCommonQuery(args); const double tol = args.value("tolerance", 0.001); int total = 0, returned = 0; nlohmann::json items = nlohmann::json::array(); forEachEntity(q, [&](AcDbEntity* pEnt) -> bool { double len = -1.0; const std::string type = entityTypeName(pEnt); if (AcDbLine* p = AcDbLine::cast(pEnt)) { len = p->startPoint().distanceTo(p->endPoint()); } else if (AcDbArc* p = AcDbArc::cast(pEnt)) { double span = p->endAngle() - p->startAngle(); while (span < 0.0) span += 2.0 * kPi; while (span > 2.0 * kPi) span -= 2.0 * kPi; len = p->radius() * span; } else if (AcDbPolyline* p = AcDbPolyline::cast(pEnt)) { len = polyLength(p); } else { return true; } if (len >= 0.0 && len < tol) { if (total >= q.offset && returned < q.limit) { nlohmann::json o; o["handle"] = handleOf(pEnt); o["type"] = type; o["layer"] = layerOf(pEnt); o["length"] = len; items.push_back(o); ++returned; } ++total; } return true; }); return makeJsonResult(pageResult(total, returned, q.offset, items)); } }; EXPORT_MCP_TOOL(mcp_Tool_FindZeroLength, CreateFindZeroLengthTool)