// ============================================================================ // dwg_annotate_draw.cpp // 图纸批注 - 绘制类工具(均为 c++_dll 后端) // draw_revision_cloud : 修订云线(rect / polygon / circle) // add_text : 单行文字 AcDbText // add_mtext : 多行文字 AcDbMText // add_polyline : 多段线 AcDbPolyline // add_line : 直线 AcDbLine // add_leader_annotation : 引线批注 AcDbLeader + AcDbMText // // 所有批注默认落在 AI_REVIEW 图层(不存在则自动创建),可在参数中覆盖。 // ============================================================================ #include "cadsdk.h" #include "dwg_annotate_common.h" #include "mcp_plugin_api.h" #include #include #include #include using namespace dwg_annotate; using namespace dwg_review; namespace { const double kPi = 3.14159265358979323846; // 圆采样为闭合多边形顶点 void circleToPoints(double cx, double cy, double r, int n, std::vector& out) { out.clear(); if (n < 3) n = 3; for (int i = 0; i < n; ++i) { const double a = 2.0 * kPi * i / n; out.push_back(AcGePoint2d(cx + r * std::cos(a), cy + r * std::sin(a))); } } // 在闭合多边形基础上细分并逐顶点设凸度,得到云线 void buildCloudPolyline(AcDbPolyline& pl, const std::vector& base, double bulge, double chord) { const size_t n = base.size(); if (n < 3) return; if (chord <= 1e-9) { double minx = base[0].x, maxx = base[0].x, miny = base[0].y, maxy = base[0].y; for (size_t i = 1; i < n; ++i) { minx = (std::min)(minx, base[i].x); maxx = (std::max)(maxx, base[i].x); miny = (std::min)(miny, base[i].y); maxy = (std::max)(maxy, base[i].y); } const double diag = std::sqrt((maxx - minx) * (maxx - minx) + (maxy - miny) * (maxy - miny)); chord = diag / 16.0; if (chord < 1e-6) chord = 1.0; } unsigned int idx = 0; for (size_t i = 0; i < n; ++i) { const AcGePoint2d& a = base[i]; const AcGePoint2d& b = base[(i + 1) % n]; const double len = a.distanceTo(b); int k = (int)std::floor(len / chord + 0.5); if (k < 1) k = 1; for (int s = 0; s < k; ++s) { const double t = (double)s / (double)k; pl.addVertexAt(idx++, AcGePoint2d(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t)); } } pl.setClosed(Adesk::kTrue); const unsigned int total = pl.numVerts(); for (unsigned int i = 0; i < total; ++i) pl.setBulgeAt(i, bulge); } // 字符串 -> MText 对齐点 AcDbMText::AttachmentPoint attachmentFromString(const std::string& s, AcDbMText::AttachmentPoint def) { if (s == "top_left") return AcDbMText::kTopLeft; if (s == "top_center") return AcDbMText::kTopCenter; if (s == "top_right") return AcDbMText::kTopRight; if (s == "middle_left") return AcDbMText::kMiddleLeft; if (s == "middle_center" || s == "center") return AcDbMText::kMiddleCenter; if (s == "middle_right") return AcDbMText::kMiddleRight; if (s == "bottom_left") return AcDbMText::kBottomLeft; if (s == "bottom_center")return AcDbMText::kBottomCenter; if (s == "bottom_right") return AcDbMText::kBottomRight; return def; } // 按名称解析文字样式 id(失败返回 kNull) AcDbObjectId resolveTextStyle(AcDbDatabase* pDb, const std::string& name) { AcDbObjectId id = AcDbObjectId::kNull; if (!pDb || name.empty()) return id; AcDbTextStyleTable* pTST = nullptr; if (pDb->getTextStyleTable(pTST, AcDb::kForRead) == Acad::eOk && pTST) { const std::wstring wn = utf8ToWStr(name); pTST->getAt(wn.c_str(), id); pTST->close(); } return id; } } // =========================================================================== // draw_revision_cloud // =========================================================================== class mcp_Tool_DrawRevisionCloud : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase(); if (!pDb) return makeError(kErrNoDocument, "no working database"); const std::string shape = args.value("shape", std::string("rect")); const double bulge = args.value("bulge", 0.5); const double chord = args.value("chord", 0.0); std::vector base; if (shape == "rect") { double x1, y1, x2, y2; if (!parseWindow(args, x1, y1, x2, y2)) return makeError(kErrBadParam, "rect cloud requires min_x/min_y/max_x/max_y or window or p1/p2"); base.push_back(AcGePoint2d(x1, y1)); base.push_back(AcGePoint2d(x2, y1)); base.push_back(AcGePoint2d(x2, y2)); base.push_back(AcGePoint2d(x1, y2)); } else if (shape == "polygon") { std::vector pts; if (!args.contains("points") || !parsePoints(args["points"], pts) || pts.size() < 3) return makeError(kErrBadParam, "polygon cloud requires points (>=3)"); for (size_t i = 0; i < pts.size(); ++i) base.push_back(AcGePoint2d(pts[i].x, pts[i].y)); } else if (shape == "circle") { AcGePoint3d c; if (!args.contains("center") || !parsePoint(args["center"], c)) return makeError(kErrBadParam, "circle cloud requires center [x,y]"); const double r = args.value("radius", 0.0); if (r <= 1e-9) return makeError(kErrBadParam, "circle cloud requires positive radius"); double ch = chord; if (ch <= 1e-9) ch = 2.0 * kPi * r / 12.0; if (ch < 1e-6) ch = r / 3.0; int n = (int)std::floor(2.0 * kPi * r / ch + 0.5); if (n < 8) n = 8; circleToPoints(c.x, c.y, r, n, base); } else { return makeError(kErrBadParam, "shape must be rect|polygon|circle"); } AcDbBlockTableRecord* pSpace = openWriteSpace(pDb, getSpaceArg(args)); if (!pSpace) return makeError(kErrNoDocument, "cannot open target space for write"); const std::string layer = getLayerArg(args); ensureLayer(pDb, layer, 1); AcDbPolyline* pl = new AcDbPolyline(); buildCloudPolyline(*pl, base, bulge, chord); applyLayerColor(pl, layer, getColorArg(args)); const AcDbObjectId id = appendToSpace(pSpace, pl); pSpace->close(); if (id == AcDbObjectId::kNull) return makeError(kErrInternal, "append failed"); std::vector created; created.push_back(makeCreatedItem(pDb, id, "LWPOLYLINE", layer)); return createdResult(created); } }; EXPORT_MCP_TOOL(mcp_Tool_DrawRevisionCloud, CreateDrawRevisionCloudTool) // =========================================================================== // add_text(单行文字) // =========================================================================== class mcp_Tool_AddText : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase(); if (!pDb) return makeError(kErrNoDocument, "no working database"); if (!args.contains("text") || !args["text"].is_string()) return makeError(kErrBadParam, "text is required"); const std::string text = args["text"].get(); AcGePoint3d pos; if (args.contains("position")) { if (!parsePoint(args["position"], pos)) return makeError(kErrBadParam, "invalid position"); } else if (args.contains("x") && args.contains("y")) { pos.x = args["x"].get(); pos.y = args["y"].get(); pos.z = 0.0; } else return makeError(kErrBadParam, "position [x,y] is required"); double height = args.value("height", 2.5); if (height <= 1e-9) height = 2.5; const double rot = args.value("rotation", 0.0) * kPi / 180.0; AcDbBlockTableRecord* pSpace = openWriteSpace(pDb, getSpaceArg(args)); if (!pSpace) return makeError(kErrNoDocument, "cannot open target space for write"); const std::string layer = getLayerArg(args); ensureLayer(pDb, layer, 1); const std::wstring wtext = utf8ToWStr(text); AcDbText* pText = new AcDbText(pos, wtext.c_str(), AcDbObjectId::kNull, height, rot); applyLayerColor(pText, layer, getColorArg(args)); if (args.contains("text_style") && args["text_style"].is_string()) { const AcDbObjectId sid = resolveTextStyle(pDb, args["text_style"].get()); if (sid != AcDbObjectId::kNull) pText->setTextStyle(sid); } const AcDbObjectId id = appendToSpace(pSpace, pText); pSpace->close(); if (id == AcDbObjectId::kNull) return makeError(kErrInternal, "append failed"); std::vector created; created.push_back(makeCreatedItem(pDb, id, "TEXT", layer)); return createdResult(created); } }; EXPORT_MCP_TOOL(mcp_Tool_AddText, CreateAddTextTool) // =========================================================================== // add_mtext(多行文字) // =========================================================================== class mcp_Tool_AddMText : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase(); if (!pDb) return makeError(kErrNoDocument, "no working database"); if (!args.contains("text") || !args["text"].is_string()) return makeError(kErrBadParam, "text is required"); const std::string text = args["text"].get(); AcGePoint3d pos; if (args.contains("position")) { if (!parsePoint(args["position"], pos)) return makeError(kErrBadParam, "invalid position"); } else if (args.contains("x") && args.contains("y")) { pos.x = args["x"].get(); pos.y = args["y"].get(); pos.z = 0.0; } else return makeError(kErrBadParam, "position [x,y] is required"); double height = args.value("height", 2.5); if (height <= 1e-9) height = 2.5; const double width = args.value("width", 0.0); const double rot = args.value("rotation", 0.0) * kPi / 180.0; AcDbMText::AttachmentPoint att = AcDbMText::kTopLeft; if (args.contains("attachment") && args["attachment"].is_string()) att = attachmentFromString(args["attachment"].get(), att); AcDbBlockTableRecord* pSpace = openWriteSpace(pDb, getSpaceArg(args)); if (!pSpace) return makeError(kErrNoDocument, "cannot open target space for write"); const std::string layer = getLayerArg(args); ensureLayer(pDb, layer, 1); const std::wstring wtext = utf8ToWStr(text); AcDbMText* pM = new AcDbMText(); pM->setContents(wtext.c_str()); pM->setTextHeight(height); pM->setLocation(pos); if (width > 1e-9) pM->setWidth(width); pM->setRotation(rot); pM->setAttachment(att); applyLayerColor(pM, layer, getColorArg(args)); if (args.contains("text_style") && args["text_style"].is_string()) { const AcDbObjectId sid = resolveTextStyle(pDb, args["text_style"].get()); if (sid != AcDbObjectId::kNull) pM->setTextStyle(sid); } const AcDbObjectId id = appendToSpace(pSpace, pM); pSpace->close(); if (id == AcDbObjectId::kNull) return makeError(kErrInternal, "append failed"); std::vector created; created.push_back(makeCreatedItem(pDb, id, "MTEXT", layer)); return createdResult(created); } }; EXPORT_MCP_TOOL(mcp_Tool_AddMText, CreateAddMTextTool) // =========================================================================== // add_polyline // =========================================================================== class mcp_Tool_AddPolyline : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase(); if (!pDb) return makeError(kErrNoDocument, "no working database"); std::vector pts; if (!args.contains("points") || !parsePoints(args["points"], pts) || pts.size() < 2) return makeError(kErrBadParam, "points (>=2) is required"); const bool closed = args.value("closed", false); AcDbBlockTableRecord* pSpace = openWriteSpace(pDb, getSpaceArg(args)); if (!pSpace) return makeError(kErrNoDocument, "cannot open target space for write"); const std::string layer = getLayerArg(args); ensureLayer(pDb, layer, 1); AcDbPolyline* pl = new AcDbPolyline(); for (size_t i = 0; i < pts.size(); ++i) pl->addVertexAt((unsigned int)i, AcGePoint2d(pts[i].x, pts[i].y)); pl->setClosed(closed ? Adesk::kTrue : Adesk::kFalse); applyLayerColor(pl, layer, getColorArg(args)); const AcDbObjectId id = appendToSpace(pSpace, pl); pSpace->close(); if (id == AcDbObjectId::kNull) return makeError(kErrInternal, "append failed"); std::vector created; created.push_back(makeCreatedItem(pDb, id, "LWPOLYLINE", layer)); return createdResult(created); } }; EXPORT_MCP_TOOL(mcp_Tool_AddPolyline, CreateAddPolylineTool) // =========================================================================== // add_line // =========================================================================== class mcp_Tool_AddLine : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase(); if (!pDb) return makeError(kErrNoDocument, "no working database"); AcGePoint3d a, b; if (args.contains("start") && args.contains("end")) { if (!parsePoint(args["start"], a) || !parsePoint(args["end"], b)) return makeError(kErrBadParam, "invalid start/end"); } else if (args.contains("x1") && args.contains("y1") && args.contains("x2") && args.contains("y2")) { a = AcGePoint3d(args["x1"].get(), args["y1"].get(), 0.0); b = AcGePoint3d(args["x2"].get(), args["y2"].get(), 0.0); } else return makeError(kErrBadParam, "start/end [x,y] (or x1/y1/x2/y2) is required"); AcDbBlockTableRecord* pSpace = openWriteSpace(pDb, getSpaceArg(args)); if (!pSpace) return makeError(kErrNoDocument, "cannot open target space for write"); const std::string layer = getLayerArg(args); ensureLayer(pDb, layer, 1); AcDbLine* pLine = new AcDbLine(a, b); applyLayerColor(pLine, layer, getColorArg(args)); const AcDbObjectId id = appendToSpace(pSpace, pLine); pSpace->close(); if (id == AcDbObjectId::kNull) return makeError(kErrInternal, "append failed"); std::vector created; created.push_back(makeCreatedItem(pDb, id, "LINE", layer)); return createdResult(created); } }; EXPORT_MCP_TOOL(mcp_Tool_AddLine, CreateAddLineTool) // =========================================================================== // add_leader_annotation(引线 + 多行文字) // =========================================================================== class mcp_Tool_AddLeaderAnnotation : public mcp_Tool { public: nlohmann::json Execute(const nlohmann::json& args) override { AcDbDatabase* pDb = acdbHostApplicationServices()->workingDatabase(); if (!pDb) return makeError(kErrNoDocument, "no working database"); if (!args.contains("text") || !args["text"].is_string()) return makeError(kErrBadParam, "text is required"); const std::string text = args["text"].get(); std::vector pts; if (!args.contains("points") || !parsePoints(args["points"], pts) || pts.size() < 2) return makeError(kErrBadParam, "points (>=2, first = arrow tip) is required"); double height = args.value("height", 2.5); if (height <= 1e-9) height = 2.5; const bool arrow = args.value("arrow", true); AcGePoint3d textPos = pts.back(); if (args.contains("text_position")) parsePoint(args["text_position"], textPos); AcDbBlockTableRecord* pSpace = openWriteSpace(pDb, getSpaceArg(args)); if (!pSpace) return makeError(kErrNoDocument, "cannot open target space for write"); const std::string layer = getLayerArg(args); const int color = getColorArg(args); ensureLayer(pDb, layer, 1); std::vector created; // 1) 文字批注(先入库,供引线关联) const std::wstring wtext = utf8ToWStr(text); AcDbMText* pM = new AcDbMText(); pM->setContents(wtext.c_str()); pM->setTextHeight(height); pM->setLocation(textPos); pM->setAttachment(AcDbMText::kMiddleLeft); applyLayerColor(pM, layer, color); const AcDbObjectId mId = appendToSpace(pSpace, pM); // 2) 引线 AcDbLeader* pL = new AcDbLeader(); pL->setDimstyleData(pDb->dimstyle()); for (size_t i = 0; i < pts.size(); ++i) pL->appendVertex(pts[i]); pL->setHasArrowHead(arrow ? Adesk::kTrue : Adesk::kFalse); applyLayerColor(pL, layer, color); const AcDbObjectId lId = appendToSpace(pSpace, pL); pSpace->close(); if (mId == AcDbObjectId::kNull && lId == AcDbObjectId::kNull) return makeError(kErrInternal, "append failed"); // 3) 关联文字并重算引线(失败则退化为"引线 + 文字"分离放置,视觉仍正确) if (lId != AcDbObjectId::kNull && mId != AcDbObjectId::kNull) { AcDbLeader* pL2 = nullptr; if (acdbOpenObject(pL2, lId, AcDb::kForWrite) == Acad::eOk && pL2) { if (pL2->attachAnnotation(mId) == Acad::eOk) pL2->evaluateLeader(); pL2->close(); } } if (mId != AcDbObjectId::kNull) created.push_back(makeCreatedItem(pDb, mId, "MTEXT", layer)); if (lId != AcDbObjectId::kNull) created.push_back(makeCreatedItem(pDb, lId, "LEADER", layer)); return createdResult(created); } }; EXPORT_MCP_TOOL(mcp_Tool_AddLeaderAnnotation, CreateAddLeaderAnnotationTool)