基于 Revit API 的双跑楼梯参数化自动生成插件
本文整理我的本科毕业设计(南通大学·地理信息科学),技术方向是 BIM-GIS 融合里的一个具体切片:用 Revit API 自动生成合规的双跑楼梯。相比学位论文,这里砍掉了摘要、文献综述之类的”仪式性”内容,只留下我觉得真正值得写下来的技术决策和代码。
项目速览
- 场景:Revit 二次开发,自动生成双跑平行楼梯
- 技术栈:C# / .NET Framework 4.8 / Revit API 2022 / WPF(MVVM)/ ExternalEvent
- 两个核心创新点:① 规范规则库 + 两阶段踏步解算;② 基于射线投射的净空前置校验,量取方向沿竖直轴,和 GB55031-2022 的要求一致;生成后还会导出室内拓扑 GeoJSON,接入 kepler.gl 做三维验证
- 效果:单部楼梯生成流程约 642 ms,四类建筑功能类型的规范指标全部验证通过
为什么是楼梯
CIM(城市信息模型)想往室内延伸,绕不开一个问题:BIM 模型给了几何,但室内”人怎么从这一层走到那一层”这件事,还得有人把它显式地建出来。楼梯正是干这个活的构件——它不只是造型,更是连接楼层的拓扑节点,直接决定了室内导航、应急疏散这类应用能不能跑起来。
但楼梯建模在实际工作里挺”苦力”:在 Revit 里手动画一部双跑楼梯,还要顺手把国标里一堆踏步宽、踢面高、净宽、净空的要求过一遍,一不留神就漏检。于是我给自己挖了个坑:做一个插件,输入两个点和几个参数,自动生成一部符合规范的楼梯,而且合规检查要在建模之前做,不是等建完了再补救。
现有做法的两个硬伤
在动手之前,先说清楚我想解决的是什么问题。
第一,规范校验基本靠人工。 踏步尺寸、梯段净宽、平台深度分别散落在《民用建筑通用规范》(GB55031-2022)、《住宅建筑规范》(GB55038-2025)、《建筑防火通用规范》(GB55037-2022)三本规范里,不同建筑类型对应的阈值还不一样。设计人员要逐一翻规范核对,效率低,也容易漏检。
第二,净空校验的检测方向本身是错的——这是我觉得最值得写的一点。规范要求净空要沿”踏步鼻端竖直向上”量取,但常规做法是等楼梯模型建完之后,用软件自带的碰撞检测去撞,而碰撞检测拉伸的方向是沿梯段坡面的法线方向,不是竖直方向。
拿典型住宅楼梯举例:踢面 175 mm、踏面 260 mm,梯段坡度角大概 33.9°。如果碰撞检测沿坡面法线量了 2200 mm 判定”无干涉”,这个 2200 mm 换算到竖直方向,实际对应的净高大概是 2651 mm,两者差了接近 20%。反过来说,一部竖直净空实际不够 2200 mm 的楼梯,完全可能在这种斜向检测下被判定为”合规”——这是系统性偏差,不是个别情况。而且碰撞检测必须等模型建好才能跑,一旦发现问题就要返工。
这两个问题分别对应我做的两块核心逻辑:规范规则库 + 两阶段踏步解算,和基于射线投射的净空前置校验。
整体架构:MVVM + ExternalEvent
在讲这两块之前,得先说一个绕不过去的坑:Revit 的点拾取(PickPoint)和 WPF 的消息循环会打架。
如果用 ShowDialog 弹模态窗口,在窗口事件里直接调用 Selection.PickPoint 让用户在视图里点一个点,模态窗口的消息循环会跟 PickPoint 需要的消息循环互相冲突,轻则界面卡顿,重则出现孤立浮窗甚至死锁。
解决办法是用 Revit 提供的 ExternalEvent 机制:插件窗口改用非模态的 Show() 打开;点”拾取 P1/P2”时先 Hide() 窗口,等 PickPoint 返回后再 Show() 回来;点”生成”按钮时不直接操作 Revit 文档,而是把当前参数写进一个 Handler,再调用 ExternalEvent.Raise()——相当于给 Revit 主线程”留言”,Revit 会在下一个空闲帧回调 Handler.Execute(),在合法的 API 上下文里跑事务。
三层职责划分是标准 MVVM:
- View:纯 XAML + 最少量 code-behind,只留 PickPoint 和关闭窗口这两件跟 Revit UI 强相关、没法搬到 ViewModel 的操作
- ViewModel:所有校验逻辑、两阶段解算、”生成”按钮的可用状态判断
- Handler(充当执行层):
StairGlobalEventHandler.Execute(),真正调 Revit API 建楼梯的地方

图:View / ViewModel / ExternalEvent Handler 的职责划分
核心技术点一:规范驱动的规则库与两阶段踏步解算
规则库:一张表就够了
四类建筑功能类型,各自对应一组阈值,用字典存起来:
| 建筑类型 | 最小踏步宽 | 最大踢面高 | 最小梯段净宽 | 每跑最大级数 | 依据 |
|---|---|---|---|---|---|
| 住宅 | 260 mm | 175 mm | 1000 mm | 18 级 | GB55038-2025 §4.2.2 |
| 一般公共建筑 | 260 mm | 165 mm | 1100 mm | 18 级 | GB55031-2022 表5.3.9 第1行 |
| 附属楼梯(多层/高层) | 260 mm | 175 mm | 1100 mm | 18 级 | GB55031-2022 表5.3.9 第2行 |
| 附属楼梯(超高层) | 250 mm | 180 mm | 1100 mm | 18 级 | GB55031-2022 表5.3.9 第3行 |
净空统一要求:梯段净高 ≥ 2200 mm,休息平台净高 ≥ 2000 mm(GB55031-2022 §5.3.9)。
代码上就是一个 Dictionary<BuildingType, StairCodeParams>,用户在界面切建筑类型,ViewModel 立刻换一套阈值重新校验(下面是去掉大部分注释后的核心片段):
1 | |
新增建筑类型只要往字典里加一条,不用碰 ViewModel 或计算逻辑,扩展成本很低。
两阶段解算:先定级数,再定高度
这是我觉得比”直接指定踏步数”更贴近实际用法的一个设计。用户在 Revit 平面图里点两个点 P1、P2 确定楼梯的位置和朝向,这两点间的水平距离其实已经隐含了楼梯能摆下多少级踏步——没必要再让用户单独输入总级数。
设总踏步级数为 n,每级踏步宽为 b,休息平台深度为 L,P1P2 水平距离为 d。双跑楼梯两跑各占一半级数,两跑水平投影总长是 n×(b/2)(不是 n×b),加上平台深度就是 P1P2 距离:
1 | |
第一阶段,反过来解出总级数:
1 | |
算出来的 n 如果是奇数,就调整成偶数——双跑楼梯要求两跑级数相等。
第二阶段,级数定了以后,用底部标高到顶部标高的实际竖向净高差 H 反推每级踢面高 h:
1 | |
分母 +2 是因为 Revit 楼梯的踢面数比”用户感知的踏步级数”多算首尾各一个。
两阶段分开算的好处是:平面尺寸由 P1P2 的真实距离决定,竖向尺寸由真实层高决定,界面预览和最终生成的几何永远是同一套数字,不会出现”预览说 26 级,生成出来变 24 级”这种脱节。
对应代码(StairCalculator.Calculate 的核心分支):
1 | |
举个例子:层高 3600 mm、踏步宽 280 mm、平台深 1200 mm、P1P2 距离 4760 mm。代入第一阶段:n = (4760−1200)×2÷280 ≈ 25.4,调整为偶数后是 26 级,两跑各 13 级;代入第二阶段:h = 3600÷(26+2) ≈ 128.6 mm,小于住宅规范 175 mm 的上限,合规。
界面上这一整套校验是实时的:任何一个数值框改了,ViewModel 立刻重新解算、刷新预览区的合规徽标;输入不合法或超出规范范围时”生成”按钮直接变灰,从源头上不让不合规的参数进入 Revit 事务。

图:几何参数区的实时合规徽标(绿色合规 / 橙色警告)
核心技术点二:基于射线投射的净空前置校验
思路
前面说了碰撞检测方向不对的问题。我的解法是:不等模型建完,在参数阶段直接算出每一级踏步面代表点的世界坐标,从这些点沿 +Z 方向发一条虚拟射线,用 ReferenceIntersector 找它撞到的第一个楼板/梁/屋顶,命中距离就是这一点的净空。射线只在内存里算,不产生任何 Revit 几何。
这里有个我觉得值得单独拎出来讲的重构点:楼梯生成阶段的坐标变换、和净空校验阶段的坐标变换,必须是同一套代码,否则两边一旦出现哪怕很小的实现差异(比如旋转轴写反、乘法顺序颠倒),净空检测点和实际梯段位置就会对不上,校验就失去意义。所以我把它抽成一个静态工具类:
1 | |
生成楼梯的 StairGlobalEventHandler 和做净空校验的 ClearanceChecker 都调用这两个方法,杜绝了”两处各写一遍矩阵乘法”的重复代码风险——这也是后面 GeoJSON 拓扑坐标能做到零偏差的前提之一。
逐级发射射线
1 | |
ClearanceChecker.Check() 会遍历第一跑、第二跑的每一级踏步面,再加上休息平台中心点,对每个点都调一次 CastRayUp,取全场最小净高跟规范阈值比较。射线只碰撞楼板(OST_Floors)、结构梁(OST_StructuralFraming)和屋顶(OST_Roofs),特意排除楼梯自身构件,避免射线打到自己身上误报。
检测结果送回 StairGlobalEventHandler:不合规时弹一个 Yes/No 对话框,显示具体差多少毫米、对应哪条规范,用户可以选择”仍然生成”或者”中止去调参数”——选中止的话不会对模型产生任何修改,因为这时候 StairsEditScope 还没打开。

图:从踏步面代表点沿 +Z 方向发射虚拟射线,命中楼板/梁底面计算净高

图:净空不足时的 Yes/No 确认对话框,显示实测净高与规范条文
生成之后:把楼梯变成一条”拓扑边”
楼梯造好只是第一步。既然目标是给 CIM 用,楼梯就不能只是个几何体,还得是室内路网图里的一条边——能跟走廊、房间连起来,供导航或疏散模拟使用。
所以插件在生成成功后会自动做一次全文档扫描,导出 GeoJSON:
- 房间节点:每个 Room 的中心点,带楼层名和面积
- 同层路径:Revit 自动算出的
PathOfTravel折线 - 楼梯跨层连接线:这是重点。每部楼梯输出 4 个航点——底层入口、第一跑顶端(平台入口)、第二跑底端(平台出口)、顶层出口,单跑楼梯退化成 2 点。两端的 Z 强制取标高高程(标高才是”真值”),中间两个航点用几何实际值。
拿到 GeoJSON 之后直接拖进 kepler.gl(Uber 开源的网页端地理可视化工具),按 type 字段上色,三层楼的路径会按 Z 轴自然分层,楼梯折线把相邻楼层连起来,室内竖向连通关系一眼就能看出来。
测试模型是一栋三层建筑(F1 ±0、F2 +3600、F3 +7200),验证了路径端点和楼梯航点在接驳处的平面坐标偏差——四个接合点全部是 0.0 mm,说明楼梯计算用的坐标基准和 Revit 自己算疏散路径时用的基准完全一致,这条室内路网在拓扑上是真正连通的,不是”看起来连上了”。

图:房间节点(红)、行进路径(蓝)、楼梯跨层连接(黄)在 kepler.gl 中的三维分层展示
测试情况
功能上,四类建筑类型的规范阈值都跑通了,违规参数在界面阶段就被拦下来,不需要等生成完再去模型里核查。
性能上,对一次典型的双跑楼梯生成做了断点计时:
| 阶段 | 耗时 | 占比 |
|---|---|---|
| 规范校验 + 踏步解算 | < 1 ms | < 1% |
| 净空预检(29 个采样点) | 约 199 ms | 31% |
| StairsEditScope 几何生成事务 | 约 378 ms | 59% |
| 室内拓扑提取 + GeoJSON 导出 | 约 7 ms | 1% |
| 其余辅助事务 | 约 57 ms | 9% |
| 全流程合计 | 约 642 ms | 100% |
大头是 Revit 内核提交楼梯几何本身的开销,跟插件代码关系不大;净空预检和拓扑导出这两个我自己加的模块合计占三成左右,不算瓶颈。相比人工在 Revit 里手画一部楼梯外加净空校验(通常要两三分钟),压到不到一秒,效率提升是数量级的。

图:Revit 中自动生成的双跑楼梯三维效果
局限,以及后面想做的
写完之后回头看,还有两块明显没打磨完:
- 楼梯类型太单一。 目前只支持双跑平行楼梯,剪刀梯、弧形梯、三跑折跑梯都没覆盖,通用性有限。
- 净空采样点还是太稀。 现在只在每级踏步的宽度中心取一个点发射射线,如果障碍物是单侧悬出的梁,可能会漏检——理论上加个宽度方向的多点采样就能补上,只是这次时间没排上。
再往后,比较想探索的方向是把 Revit 的项目基点(ProjectBasePoint)和测量点(SurveyPoint)接进来,算出真实地理坐标下的仿射变换,让这套室内拓扑能直接叠加到室外 GIS 底图上——这样才算真正打通”BIM 进 CIM”这条链路,而不是停在 Revit 内部自娱自乐。
写完回头看,这个项目最大的收获倒不是”用 Revit API 建了个楼梯”,而是把”规范条文”这种原本只存在于 PDF 文档里的东西,一步步拆成了可以在代码里跑起来的规则表、公式和射线检测——这个转化过程本身,可能比几何生成更值得花时间。如果你也在琢磨 BIM 和 GIS 怎么打通,欢迎交流。
本项目已开源(MIT 协议):
- GitHub 源码:TPC369max/CSharp-Revit-Stairs-Plugin
- 下载体验:请前往仓库的
Releases页面下载最新版免安装编译包。
Inspired by industrial best practices. 欢迎在 GitHub 提交 Issue 或 PR 交流讨论!