Revit 二次开发属于计算几何开发,调试时面临一个根本痛点:调试器里只能看到
XYZ(12847.3, -5621.8, 3200.0)这类抽象数字,只有程序跑完整个方法才能在 Revit 视口里看到真实图形。这不是工具不够的问题,是**“调试器冻结进程”与”Revit 单线程渲染”之间的架构性矛盾**。
这篇是我把 Revit / CAD / Rhino / Houdini / Unreal / Polyscope 等 30+ 生态横向走了一遍后整理的笔记。提炼出 5 种设计哲学,给出工业级落地架构。
一、问题本质
Revit API 严格单线程,所有 Document / 视口操作必须在主线程。任何试图在冻结 Revit 里绘图的方案都注定失败——
- 单步调试时 VS 冻结主线程
- 即使用
Document.Transparent(curve)这种临时图元 API,几何也只是被排队,要等调试器解冻 + 渲染循环恢复才能看见 - Immediate Window 的 function evaluation 会短暂解冻执行那一行,但解冻时间只够运行一行代码,Revit 根本没机会跑渲染循环
几何位置 / 方向 / 法向量是否正确,仅靠数字推算难度太高。
二、Revit 生态内的头部方案
Tier 1 — RevitLookup + Geometry Visualization ⭐
- 组织:
lookup-foundation(原作者 Jim Awe @ Autodesk,Jeremy Tammik 开源,现由 Nice3point / Roman Karpovich 主维护) - 关键事件:2024 年 Nice3point 通过 PR #245 加入 Geometry Visualization 功能,底层用 DirectContext3D
- 能力:
- Solid Visualization — 整个实体的内外结构高亮
- Mesh Visualization — 顶点 / 网格拓扑
- Curve Visualization — 线 / 弧 / 样条的曲率与连续性
- 用法:右键任意对象 → 直接在视口渲染出来,不污染模型
- 结论:做 Revit 二次开发第一步就要装
Tier 2 — Autodesk 官方 API
| API | 时代 | 场景 | 特点 |
|---|---|---|---|
| DirectContext3D | 2017+ | 首选 | 直接喂 GPU 顶点/索引缓冲,不创建元素,能渲染百万级点/线/面,支持 Live Jig |
| Analysis Visualization Framework (AVF) | 老 | 带色分析 | SpatialFieldManager,适合温度/信号/日照着色。坑:只存内存,DesignOption 里崩溃 |
| TemporaryGraphicsManager | 2022+ | 交互式 | 视图上显示带点击事件的图片/图标 |
| BRepBuilder | 中 | 复杂 B-rep | 构造自定义 B-rep 后丢给 DirectShape / DirectContext3D 显示 |
Tier 3 — 经典方案
- DirectShape + DirectShapeFramework (NuGet) — 物化成真实 Revit 元素。优点:可选中测量出图;缺点:污染模型需事务
- ModelLine / ReferencePoint 打点法 — Jeremy Tammik 2012 年的土办法,3 条短 ModelLine 做十字光标
- 外部导出:DXF / OBJ / STL / JSON + Three.js viewer
Tier 4 — 开发流程配套
| 工具 | 作用 |
|---|---|
| Add-in Manager(Autodesk SDK) | Read Only Mode 下不重启 Revit 热加载 DLL |
| RevitAddInManager(chuongmep 开源) | 社区加速版 |
| ricaun.RevitAddin.VisualStudioDebug | 自动化 Attach to Process |
| ricaun.RevitTest | NUnit 测试直接跑在 Revit 进程内 |
三、断点冻结问题的根本解法
为什么 Document.Transparent(curve) 在断点处画不出来
Nice3point 的 TempGraphics 扩展底层走 Revit 2022+ 的 TemporaryGraphicsManager,它只是把几何塞进 Revit 的图形队列,而实际 GPU 绘制必须等:
- Revit 主线程回到消息循环
- 渲染管线下一帧被调度
断点命中时这两步都被 VS 冻结住了。
唯一出路 — 把渲染搬出 Revit 进程。
架构 A — 外部 Viewer + IPC ⭐ 最推荐
┌────────────────────────────┐ Named Pipe ┌────────────────────────────┐
│ Revit.exe (被 VS 冻结) │ "revit-debug-viz" │ RevitDebugViewer.exe │
│ │ ──────────────────▶ │ (WPF + HelixToolkit) │
│ YourAddin.dll │ JSON / MessagePack │ │
│ ├─ DebugViz.Push(xyz) ─┤ │ 3D 视口 │
│ ├─ DebugViz.Push(curve)─┤ 同步写,不依赖渲染 │ ├─ 点/线/面列表 │
│ ├─ DebugViz.Push(solid)─┤ │ ├─ 颜色 / 标签 │
│ └─ DebugViz.Clear() │ │ └─ 清屏 / 保存 / 重放 │
└────────────────────────────┘ └────────────────────────────┘
为什么能在断点处工作:
- 命名管道
Write是内核级同步 IO,不依赖 Revit 的消息泵或渲染循环 - VS 的 function evaluation 临时解冻主线程足以完成一次管道写入
- 外部 viewer 进程没有被调试,消息循环一直在跑,收到数据立即画
调试体验:
(断点命中在第 47 行)
F10 → Immediate Window: DebugViz.Push(intersection, Color.Red)
→ 外部 viewer 窗口立即出现红色交点 ✨
F10 → DebugViz.Push(projectedCurve, Color.Yellow)
→ viewer 里出现黄色曲线 ✨
F10 → DebugViz.Clear()
架构 B — 自定义 Visual Studio Debugger Visualizer
Microsoft 官方机制,和 OpenCV 的 Image Watch(解决图像调试的同类问题)同源:
- 继承
DialogDebuggerVisualizer,UI 用 Helix Toolkit (WPF 3D) - 写
VisualizerObjectSource,在调试端把XYZ/Curve/Solid提取成可序列化 DTO - 打包 DLL 放到
Documents\Visual Studio 2022\Visualizers\ - UI 运行在 VS 进程里,不被 Revit 冻结影响
优点:原生集成
缺点:Revit 类型不是 [Serializable];修改 Visualizer DLL 要重启 VS;只看”当前变量”没法叠加
架构 C — 现成 VS 插件(半适配)
| 工具 | 覆盖情况 | 结论 |
|---|---|---|
| Graphical Debugging (Adam Wulkiewicz) | C# ✅ / 自定义类型 ✅ / 3D ❌ | 只支持 2D,对 Revit 不够 |
| ILNumerics Array Visualizer | 3D ✅ / 只吃 double[,] | 要先转矩阵,能用不顺手 |
| OpenCV Image Watch | 只看图像 | 活教材不可直接用 |
结论:没有一个现成插件能直接吃 Revit 几何类型,只能自己写架构 A 或 B。
🔥 杀手级技巧 — Tracepoint(断点不停)
VS 断点右键 → Actions → 填入 {DebugViz.Push(curve, "step-" + i)} 并勾 “Continue execution”:
- 断点命中不停,只执行表达式继续跑
- 在算法 5 个关键位置放 5 个 Tracepoint → 一口气跑完 → 外部 viewer 里出现带标签的 5 个中间几何
- 整个算法的”运行轨迹”一次成图
几何算法很多 bug 是趋势型的(loop 角度累积偏移等),轨迹图一眼看出规律。
四、横向对比:其他三维生态怎么做
4.1 CAD 二次开发
AutoCAD — Transient Graphics + Jig
AutoCAD 2009 起 TransientManager.AddTransient(entity, ...) 不入数据库、不污染 dwg、无需事务。Jig 更早,命令式接管鼠标事件 + 实时重绘(按住鼠标画圆预览)。仍然没解决断点冻结问题。
SolidWorks — IBody2.Display3
Dim tempBody As Body2 = modeler.CreateBodyFromBox3(...)
tempBody.Display3(modelDoc, rgbColor, swTempBodySelectOptions_e.swTempBodySelectable)临时 body 可选中旋转,不进 FeatureTree。和 Revit 的 DirectShape 同思路。
4.2 NURBS / 建模软件
Rhino / Grasshopper — DisplayConduit ⭐
RhinoCommon 提供 DisplayConduit 基类,优雅的 Debug Draw 典范:
class DebugConduit : DisplayConduit
{
public List<Line> Lines = new();
public List<Point3d> Points = new();
protected override void DrawForeground(DrawEventArgs e)
{
foreach (var l in Lines) e.Display.DrawLine(l, Color.Red, 2);
foreach (var p in Points) e.Display.DrawPoint(p, Color.Yellow);
}
}
// 用的时候:
var conduit = new DebugConduit { Enabled = true };
conduit.Lines.Add(someLine);
RhinoDoc.ActiveDoc.Views.Redraw();- 不进文档、不需事务、一行代码加几何
Enabled = false立即关掉- 所有 CAD/CAM 里最接近游戏引擎哲学的方案
- 仍然被断点冻结问题困扰
4.3 DCC 软件
Houdini — Guide Geometry + Intermediate Output ⭐⭐
Houdini 的解决方案极其独特:
- 每个 SOP 节点的输出都可以直接点击查看 —— 架构上就是”每一步都天然持久化可查看”
- 写自定义工具时定义 Python Viewer State + 注册 Guide Geometry
- HDA 里可以加专门的 “visualize” 输出口,算法跑完所有中间结果可切换浏览
本质洞察:Houdini 压根不需要”断点时看中间结果”,因为节点式设计让每一步都是一个可持久查看的节点输出。这是架构层面的降维解决方案。
Blender — gpu Module + Draw Handler
import gpu
from gpu_extras.batch import batch_for_shader
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
batch = batch_for_shader(shader, 'LINES', {"pos": [(0,0,0), (1,1,1)]})
def draw():
shader.bind()
shader.uniform_float("color", (1, 0, 0, 1))
batch.draw(shader)
bpy.types.SpaceView3D.draw_handler_add(draw, (), 'WINDOW', 'POST_VIEW')每帧往视口自定义绘制,走 GPU。与 Rhino DisplayConduit 同哲学。
4.4 游戏引擎(天花板)
Unreal Engine — DrawDebugHelpers 黄金标准 ⭐⭐⭐
DrawDebugLine(GetWorld(), Start, End, FColor::Red, false, 5.0f, 0, 2.0f);
DrawDebugSphere(GetWorld(), Center, 50.0f, 12, FColor::Green, false, 5.0f);
DrawDebugBox(...); DrawDebugCone(...); DrawDebugString(...);革命性特性:
- 一行代码就画,不需要事务/场景图/文档
- 每条线都能设持续时间(5 秒后自动消失)
- Persistent 模式下暂停游戏也能看
- Tick / 构造函数 / 任意事件回调里都能调用
Unity — Gizmos + Debug.DrawLine
Debug.DrawLine(from, to, color, duration)— 运行时任意位置Gizmos.DrawLine / Gizmos.DrawSphere—OnDrawGizmos()回调里画,Scene 视图常驻
游戏引擎为什么做得这么好? 它们天生是帧循环架构,每一帧都在重新绘制整个场景,“加一条调试线”只是往这一帧的绘制列表里追加一项,下一帧自然出现。没有事务概念,没有文档状态概念,也没有 UI 主线程独占限制。
4.5 学术 / 科研几何库
Polyscope — 科研领域的”标准答案” ⭐⭐⭐
普林斯顿 Nick Sharp + Keenan Crane 维护的 C++/Python 几何查看器:
polyscope::init();
polyscope::registerSurfaceMesh("my mesh", V, F);
polyscope::registerPointCloud("sampled points", points);
polyscope::getSurfaceMesh("my mesh")->addVertexScalarQuantity("curvature", K);
polyscope::show();- < 10 行代码启动完整 3D viewer
- structures + quantities 的抽象 —— 注册结构,往上挂任意数量数据(标量/向量/颜色)
- 不霸占主循环,随时可嵌入任何 C++ 项目
- 科研人员调试 Laplacian / 测地线 / 曲率算法的标配
libigl Viewer
ETH + NYU 的学术库,内置 debug API:
igl::opengl::glfw::Viewer viewer;
viewer.data().set_mesh(V, F);
viewer.data().add_points(P, Eigen::RowVector3d(1,0,0));
viewer.data().add_edges(P1, P2, Eigen::RowVector3d(0,1,0));
viewer.data().add_label(pos, "step 3");
viewer.launch();OpenCASCADE — Inspector ⭐
OCCT(世界最大开源 B-rep 几何内核,FreeCAD 基础)的专用调试工具:
- Qt 写的独立应用
- 插件化架构:
DFBrowser(看 OCAF 文档)/VInspector(看 AIS 上下文)/ShapeView(看 TopoDS_Shape 拓扑树) - 交互式浏览任何 OCCT 数据结构,包括 Face / Edge / Vertex 的拓扑
- “专用调试检查器”路线的代表作,和 RevitLookup 完全对应但以桌面应用形态存在
五、5 种设计哲学
走完一圈之后,所有方案可以归到 5 种哲学:
| # | 哲学 | 代表 | 核心做法 |
|---|---|---|---|
| A | Immediate Mode Debug Draw | Unreal / Unity / Blender gpu | 每帧重画,一行代码即可,不走文档/事务 |
| B | Display Conduit / Overlay | Rhino DisplayConduit / Revit DirectContext3D | 继承基类,在 Draw 回调画,可开关 |
| C | Transient Entity | AutoCAD Transient / SW Display3 / Revit DirectShape | 宿主 API 提供”临时几何”,不入文档树 |
| D | Intermediate Node Output | Houdini HDA visualize / libigl GPP | 中间结果作为算法的一等公民输出项,事后浏览 |
| E | External Inspector / Out-of-process Viewer | OCCT Inspector / Polyscope / RevitLookup | 独立进程,IPC / 数据注册接收,UI 不受宿主影响 |
六、对 Revit 开发者的 5 个洞察
洞察 1 — 断点冻结问题只有”外部进程”能根治
游戏引擎和 Rhino 的 Debug Draw 再优雅,也解决不了断点冻结。它们体验好是因为开发者**不打断点,改用”运行时 Debug Draw + 暂停观察”**的流程。
思维转变:不要指望在断点处看到几何,而是让几何在运行时被持续记录,断点只用来看数据。Revit 里等价 = DirectContext3D + 保留所有历史帧 + 断点时从列表回溯。
洞察 2 — Houdini 的”节点式中间结果”是终极方案
Houdini 根本不需要”调试可视化”这个词 —— 节点网络让每一步天然持久化可查看。
对 Revit:把几何算法拆解成显式的阶段,每阶段输出
DebugViz.Push(namedStage: "step-3-projection")。外部 viewer 里滑动时间轴回看每一步。
洞察 3 — Polyscope 的”Structure + Quantity”抽象值得照抄
科研库的 API 设计比 CAD 更成熟:不是”画一条线”,而是”注册一个结构,往上挂数据”:
DebugViz.Register("wall-01", wallSolid);
DebugViz.Register("wall-01").AddScalar("thickness-deviation", devArray);
DebugViz.Register("wall-01").AddVector("face-normals", normals);不仅能看几何,还能把每个面的偏差值、法向量一起叠在同一个结构上着色显示。
洞察 4 — Unreal 的 duration 参数是神来之笔
DrawDebugLine(..., duration=5.0f) —— 这条线 5 秒后自动消失。对应 Revit 外部 viewer:
每个
Push支持fadeAfter: TimeSpan,算法跑完调试几何自动淡出。配合”按步骤 Push”,能看到几何按时间顺序”流动”地生成,像一场动画而非静态图。
洞察 5 — OCCT Inspector 证明”外挂调试器”路线可行
OCCT Inspector 是独立 Qt 应用,通过挂钩 OCCT AIS Context / OCAF Document 做深度检查。这和”外部 WPF viewer + 命名管道”同一路线,在一个和 Revit 一样复杂的 B-rep 系统里完全工业化落地了。
七、终极调试体验(目标架构)
┌─────────────────────────────────────────────────────┐
│ Revit 插件代码 │
│ │
│ DebugViz.Push(wallSolid, │
│ name: "step-3-wall", │
│ color: Red, │
│ duration: 10s) │
│ │
│ DebugViz.Push(wallSolid) │
│ .AddVectorField("normals", normals) │
│ .AddScalar("deviation", deviations); │
└─────────────┬───────────────────────────────────────┘
│ 命名管道 (同步写)
▼
┌─────────────────────────────────────────────────────┐
│ RevitDebugViewer.exe (独立进程) │
│ │
│ 🎨 HelixToolkit 3D 视口 │
│ 📜 时间轴 (可回放算法每一步) │
│ 🗂 结构树 (Polyscope 风格 structures + quantities) │
│ 🎛 ImGui 风格控制面板 (显隐/颜色/透明度) │
│ 💾 Save Scene → 问题场景可序列化复现 │
│ 🔄 Load Scene → 发给同事 / 发给 AI 分析 │
└─────────────────────────────────────────────────────┘
融合的设计来源:
- Unreal 的
duration自动过期 - Polyscope 的 structure + quantity 抽象
- Houdini 的时间轴 / 阶段回放
- Rhino DisplayConduit 的一行代码即可用
- OCCT Inspector 的 out-of-process 架构
- RevitLookup 的项目内对接风格
八、可复用的”巨人肩膀”
| 组件 | 参考实现 | 能复用的部分 |
|---|---|---|
| 外部 viewer 的 UI 哲学 | Polyscope | structures + quantities API 设计 |
| 外部 viewer 的 3D 渲染 | HelixToolkit.Wpf | HelixViewport3D、相机、拾取 |
| 算法阶段可视化 | libigl GPP ProcessVisualizer | 按阶段切换查看的 UI 思路 |
| 宿主端 Debug Draw API | Rhino DisplayConduit | DebugConduit.Lines.Add(...) 调用风格 |
| 持续时间 / 淡出 | Unreal DrawDebugHelpers | duration 参数设计 |
| 进程间调试检查器 | OCCT Inspector | Qt 架构、插件化、场景保存导出 |
主要参考资料
- RevitLookup GitHub (lookup-foundation) · Visualization Wiki
- Geometry visualization PR #245 by Nice3point
- Autodesk Developer Blog: RevitLookup Geometry Visualisation
- Jeremy Tammik – The Building Coder · 2080+ 篇 Revit API 实战博客
- Polyscope · libigl · OCCT Inspector
- Unreal DrawDebugLine · Unity Gizmos.DrawLine
- Rhino DisplayConduit Guide · Houdini Python Viewer States
调试器冻结进程 + 单线程渲染的矛盾,在 Revit、AutoCAD、Rhino 里都存在;游戏引擎和科研库走另一条路 —— 不打断点,让几何在帧循环里自然出现。把渲染搬出宿主进程,是工业级 CAD 二次开发能借鉴的最稳妥路线。