Skip to content

About

基于 DCEL 半边结构的平面细分:从任意二维曲线集合提取最小面域与最大面域。拓扑核心零 CAD 依赖,Revit 仅为适配器。配 58 个不变量测试。

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

PlanarSubdivision

从任意二维曲线集合中提取最小面域与最大面域——基于 DCEL(双向连接边表)的平面细分。 拓扑核心不依赖任何 CAD 平台,Revit 只是它的一个适配器。

CAD / BIM 里有个反复出现的需求:给一堆首尾未必相接、彼此交叉的线条,问它们围出了哪些区域。 听上去简单,实际要处理交点打断、容差合并、悬挂边、桥接边、孤岛包含关系,以及圆弧参与时的切向退化。 这个仓库是我对这个问题的一次完整实现。


为什么拆成两个工程

最初它只是一个 Revit 插件,拓扑算法直接写在 Autodesk.Revit.DB 的类型上。 问题很实在:这套拓扑知识没法脱离 Revit 复用,搬不到服务端,也换不了 CAD 平台。

所以把几何抽象成一个窄接口 ICurve2,拓扑层只认它:

QGY.PlanarSubdivision.Core          netstandard2.0,零 CAD 依赖
├── Geometry/
│   ├── ICurve2.cs                  几何抽象:只暴露拓扑算法真正需要的能力
│   ├── Vec2.cs                     二维向量与容差比较
│   └── Line2.cs                    Line2 / Arc2 / Box2 的纯解析实现
└── Topology/
    ├── Dcel.cs                     Vertex / HalfEdge / Loop / Face
    └── PlanarGraphBuilder.cs       建图 → 找环 → 剔桥 → 组面

QGY.PlanarSubdivision.Core.Tests    xUnit,58 个不变量测试,不需要任何 CAD 进程

QGY.Revit.PlanarSubdivision         Revit 2022 适配层与两个面域命令

接口刻意做得很窄——求交、布尔这类重活留在各自的内核里,通过 ICurveIntersector 注入。 将来换 OCCT,只需要新增一个 OcctCurve2 适配器,拓扑层一行不动。


快速开始

dotnet test QGY.PlanarSubdivision.Core.Tests

不需要安装 Revit。Core 与测试是纯 .NET,Line2 / Arc2 是自己实现的解析几何, 所以整套拓扑算法可以脱离任何 CAD 进程验证。

Revit 插件工程需要 Revit 2022 的 API 引用才能编译,用法见 QGY.Revit.PlanarSubdivision/README.md。


算法要点

最左转规则赋后继指针。 每个顶点的出边按角度排序,沿反向边取「最左」的那条作为 Next, 这样沿 Next 链走出来的环恰好是最小面域。

曲率作为隐式 tiebreak。 两条边从同一顶点出发、初始切向完全相同时(例如一条直线与一条与之相切的圆弧), 只按 t=0 的切向排序会让最左转规则失效,进而漏面或串面。 解法是沿参数往前偏移一小段再取切向——曲率会自然把它们分开:顺时针弧 < 直线 < 逆时针弧。 这是整个算法里最容易写错的一处,也是测试里专门有一条用例盯着它的原因。

迭代剔除悬挂边与桥接边。 度为 1 的顶点直接剔掉;桥接边(两侧属于同一个环)检出后重建图再来一轮, 最多十轮。达到上限就返回当前结果——宁可给出不完全正确的面域,也不在极端输入上卡死。

绕向与包含。 鞋带公式定绕向区分外环与内环,射线法建立外环与内环的包含关系。


测试怎么写的

几何算法的测试难点在于期望输出难以手工标注——一堆曲线围出的面域,正确答案得靠人画一遍。 所以这里不测输出,测不变量:无论输入什么,DCEL 都必须满足一组恒真的性质。

不变量 含义
孪生对合 twin(twin(e)) == e 且 twin(e) != e
链一致性 next(prev(e)) == e,沿 next 有限步回到起点
欧拉公式 V − E + F = 2
面积守恒 所有最小面域面积之和 == 外边界围出的面积
有向面积代数和为零 每条边正反各走一次,贡献必然抵消(含孤岛也成立)
绕向一致 连通输入下恰好一个最外层环,其余环绕向相反
曲率 tiebreak 同顶点同初始切向的三条边必须被分开
幂等性 同一输入重复构建结果一致

这些断言不需要任何手写答案,却恰好能逮住这类算法最常见的 bug——指针接错、边丢失、绕向反了。

顺带一个真实案例:写这批测试时逮到一个 bug——顶点合并用的是 VertexMergeTolerance, 而半边构造里的一致性校验写死了 1e-6,校验比合并更严,导致只要顶点合并真的发生就必然抛异常。 这类错误靠手工测试几乎撞不到。


已知限制

写在明面上,不藏:

  • 环包含判定是外环 × 内环两两射线法,O(L_out · L_in),未做包围盒预筛。图纸级输入(几十到几百个环)不构成瓶颈,环数上千需要改造。
  • Loop.ContainsPoint 用离散化多边形做射线法,纯圆弧边界存在少量误差。图纸精度下可接受,精确解需要按弧解析求交计数。
  • 未实现扫描线(Bentley–Ottmann)。网格空间哈希在图纸类输入(分布稀疏、空间局部性强)上的实际收益已足够。
  • 实测适用规模:单张平面图数千条曲线量级,未针对万级以上输入验证。
  • Revit 适配层目前只针对 2022,其余版本未验证。

复杂度逐步骤的分析见插件 README。


许可

MIT,见 LICENSE。

About

基于 DCEL 半边结构的平面细分:从任意二维曲线集合提取最小面域与最大面域。拓扑核心零 CAD 依赖,Revit 仅为适配器。配 58 个不变量测试。

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages