Files
envi-code/SourceCode/Code2026/cad_mcp_plugins/bvh.h
T
company-desktop c10f5b0083 feat: 补齐 cad_mcp_plugins 的批注/编辑/干涉工具(26 个工具齐了)
现象:mcp_config.json 里配了 40 个工具,但仓库的 cad_mcp_plugins 只有 16 个工厂,
add_line / add_text / add_mtext / add_polyline / add_leader_annotation /
draw_revision_cloud / delete_entities / set_entity_properties / find_collisions /
set_drawing_area 这 10 个在 tools/list 里被静默丢掉(实测只有 30 = 18 + 12)。

来源:d:\new-start\CadProject\src\cad_mcp_plugins(pull 之后新增的
dwg_annotate_*、dwg_review_ui、find_collisions、bvh.h),整体搬到本仓库。

改动:
- 新增 bvh.h、dwg_annotate_common.cpp/h、dwg_annotate_draw.cpp、
  dwg_annotate_edit.cpp、dwg_review_ui.cpp、find_collisions.cpp;
- cad_mcp_plugins/cmakelists.txt 的 SOURCES 补上这 6 个 cpp;
- 移植差异两处(都在本仓库编译/运行才暴露):
  1. `#include "cadsdk.h"` -> `#include "KTCad.h"`(本仓库没有 cadsdk.h);
  2. 上游 cadsdk.h 带 NOMINMAX,本仓库 KTCad.h 没有,`std::min/std::max`
     被 windows.h 的宏顶掉(C2589/C2059)→ 在 dwg_annotate_common.cpp、
     dwg_annotate_draw.cpp、find_collisions.cpp 里 `#undef min / #undef max`。
- 上游的写库工具没有文档锁:MCP 工具跑在主线程消息里、不在命令上下文,
  kForWrite 打开块表记录直接拿到 eLockViolation(0x140),报
  "cannot open target space for write"。补了 `dwg_annotate::DocLock`
  (acDocManager->lockDocument/unlockDocument 的 RAII),并在
  dwg_annotate_draw / dwg_annotate_edit 的 8 个 Execute 开头构造。
  这一条是上游在新环境下的真 bug,不修这批工具在本仓库根本用不了。
- 所有新增文件转 UTF-8 with BOM(仓库红线)。

验证:
- R230 Debug / Z2026 Debug 编译通过并部署;dumpbin 显示
  cad_mcp_plugins.dll 导出 26 个 Create*Tool + 26 个 Destroy_*。
- AutoCAD 2020 实测(部署目录放仓库的 mcp_config.json,frame 优先读它):
  tools/list = **40**,配置里没有一个工具缺失;
  add_line / add_text / add_polyline / add_mtext / add_leader_annotation /
  draw_revision_cloud 全部返回 created[handle](图层 AI_REVIEW);
  set_entity_properties 改图层+颜色、delete_entities 删两个句柄都成功;
  find_collisions / find_zero_length 正常返回结构。
2026-10-29 22:45:14 +08:00

230 lines
6.0 KiB
C++

#pragma once
// ============================================================================
// bvh.h
// 2D AABB 层次包围盒 —— 干涉检查的**粗筛**层。
//
// 纯 C++(不依赖 ObjectARX),header-only,便于复用与推理。
//
// 相比「按质心中位分割」的朴素实现,这里的取舍:
// - 按**质心中位数**沿**较长轴**二分 → 深度严格 O(log n),无退化分支;
// - 内部节点记录**子树图元数**,交叉遍历时优先拆较大的一侧,减少无效下降;
// - 自相交遍历**每对只报告一次**(叶内 i<j + 子树两两交叉),无重复无自比。
//
// 坐标约定:x 向右、y 向上;Box 为 (x1,y1)-(x2,y2)。
// ============================================================================
#include <algorithm>
#include <utility>
#include <vector>
namespace dwg_bvh
{
// 叶节点最多容纳的图元数:越小查询越快、建树越慢
constexpr int kLeafSize = 4;
struct Box
{
double x1 = 0.0, y1 = 0.0, x2 = 0.0, y2 = 0.0;
double width() const { return x2 - x1; }
double height() const { return y2 - y1; }
double area() const { return width() * height(); }
bool valid() const { return x2 >= x1 && y2 >= y1; }
// 反向极值盒:任何 grow 都能把它撑开(用于求并集)
static Box empty()
{
Box b;
b.x1 = b.y1 = 1e300;
b.x2 = b.y2 = -1e300;
return b;
}
void grow(const Box& o)
{
if (o.x1 < x1) x1 = o.x1;
if (o.y1 < y1) y1 = o.y1;
if (o.x2 > x2) x2 = o.x2;
if (o.y2 > y2) y2 = o.y2;
}
// 接触即算相交
bool intersects(const Box& o) const
{
return !(x2 < o.x1 || o.x2 < x1 || y2 < o.y1 || o.y2 < y1);
}
// 交集盒(可能退化,调用方自行判断)
Box overlap(const Box& o) const
{
Box r;
r.x1 = (x1 > o.x1) ? x1 : o.x1;
r.y1 = (y1 > o.y1) ? y1 : o.y1;
r.x2 = (x2 < o.x2) ? x2 : o.x2;
r.y2 = (y2 < o.y2) ? y2 : o.y2;
return r;
}
};
struct Primitive
{
Box box;
int payload = -1; // 调用方的索引(如实体在自身数组里的下标)
};
struct Node
{
Box box;
int first = 0; // 叶:图元区间起点;内部:左子节点下标
int count = 0; // 叶:图元个数;内部:子树图元总数
bool leaf = true;
};
class Tree
{
public:
// 建树(就地重排 prims;payload 随图元一起搬,调用方索引不受影响)
void Build(std::vector<Primitive> prims)
{
m_prims.swap(prims);
m_nodes.clear();
if (m_prims.empty()) return;
m_nodes.reserve(2 * m_prims.size());
m_nodes.emplace_back();
buildNode(0, 0, static_cast<int>(m_prims.size()));
}
bool empty() const { return m_nodes.empty(); }
int size() const { return static_cast<int>(m_prims.size()); }
// 两两自检:所有包围盒相交的 (payloadA, payloadB),每对仅一次
void SelfPairs(std::vector<std::pair<int, int>>& out) const
{
if (!m_nodes.empty()) selfPairs(0, out);
}
// 查询与 box 相交的 payload
void Query(const Box& q, std::vector<int>& out) const
{
out.clear();
if (m_nodes.empty()) return;
std::vector<int> stack;
stack.push_back(0);
while (!stack.empty())
{
const int idx = stack.back();
stack.pop_back();
const Node& n = m_nodes[idx];
if (!n.box.intersects(q)) continue;
if (n.leaf)
{
const int end = n.first + n.count;
for (int i = n.first; i < end; ++i)
if (m_prims[i].box.intersects(q)) out.push_back(m_prims[i].payload);
}
else
{
stack.push_back(n.first);
stack.push_back(n.first + 1);
}
}
}
private:
void buildNode(int nodeIdx, int first, int count)
{
Box hull = Box::empty();
Box cent = Box::empty();
for (int i = first; i < first + count; ++i)
{
const Box& b = m_prims[i].box;
hull.grow(b);
Box c;
c.x1 = c.x2 = (b.x1 + b.x2) * 0.5;
c.y1 = c.y2 = (b.y1 + b.y2) * 0.5;
cent.grow(c);
}
m_nodes[nodeIdx].box = hull;
m_nodes[nodeIdx].first = first;
m_nodes[nodeIdx].count = count;
m_nodes[nodeIdx].leaf = true;
if (count <= kLeafSize) return;
const bool byX = cent.width() >= cent.height();
const int mid = count / 2;
auto begin = m_prims.begin() + first;
std::nth_element(begin, begin + mid, begin + count,
[byX](const Primitive& a, const Primitive& b)
{
const double ca = byX ? (a.box.x1 + a.box.x2) : (a.box.y1 + a.box.y2);
const double cb = byX ? (b.box.x1 + b.box.x2) : (b.box.y1 + b.box.y2);
return ca < cb;
});
// 注意:emplace_back 可能重分配 m_nodes,故不持有 Node& 引用
const int leftIdx = static_cast<int>(m_nodes.size());
m_nodes.emplace_back();
m_nodes.emplace_back();
m_nodes[nodeIdx].first = leftIdx;
m_nodes[nodeIdx].leaf = false;
buildNode(leftIdx, first, mid);
buildNode(leftIdx + 1, first + mid, count - mid);
}
void selfPairs(int idx, std::vector<std::pair<int, int>>& out) const
{
const Node& n = m_nodes[idx];
if (n.leaf)
{
const int end = n.first + n.count;
for (int i = n.first; i < end; ++i)
for (int j = i + 1; j < end; ++j)
if (m_prims[i].box.intersects(m_prims[j].box))
out.emplace_back(m_prims[i].payload, m_prims[j].payload);
return;
}
selfPairs(n.first, out);
selfPairs(n.first + 1, out);
crossPairs(n.first, n.first + 1, out);
}
void crossPairs(int a, int b, std::vector<std::pair<int, int>>& out) const
{
const Node& na = m_nodes[a];
const Node& nb = m_nodes[b];
if (!na.box.intersects(nb.box)) return;
if (na.leaf && nb.leaf)
{
const int ea = na.first + na.count;
const int eb = nb.first + nb.count;
for (int i = na.first; i < ea; ++i)
for (int j = nb.first; j < eb; ++j)
if (m_prims[i].box.intersects(m_prims[j].box))
out.emplace_back(m_prims[i].payload, m_prims[j].payload);
return;
}
// 优先拆「较大」的一侧,让递归更快收敛
const bool splitA = nb.leaf || (!na.leaf && na.count >= nb.count);
if (splitA)
{
crossPairs(na.first, b, out);
crossPairs(na.first + 1, b, out);
}
else
{
crossPairs(a, nb.first, out);
crossPairs(a, nb.first + 1, out);
}
}
std::vector<Primitive> m_prims;
std::vector<Node> m_nodes;
};
} // namespace dwg_bvh