DrunkOnJava/rvt-rs

GitHub: DrunkOnJava/rvt-rs

一个离线的、无需 Autodesk Revit 的 Revit 文件字节级读取与分析工具包。

Stars: 4 | Forks: 2

# rvt-rs **Apache-2.0 净室开发的 Rust/Python 工具包,用于无需安装 Revit 即可检查 Autodesk Revit 文件(`.rvt`, `.rfa`, `.rte`, `.rft`)。** 它能打开 OLE/CFB 容器,解码 Revit 截断的 gzip 流,提取元数据和预览,解析内嵌的 `Formats/Latest` schema,并在涵盖 2016–2026 年 11 个发布的参考语料库中,对所有观察到的 schema 字段编码进行分类。 **这目前还不是一个完整的 Revit 模型读取器。** 基于/schema 驱动的实例遍历已在 Revit 2024–2026 上验证了 `ADocument` 的切入点(仅限文档级别的元数据——不包含每个元素),并且 `Formats/Latest` schema 分类覆盖了 100% 的观察字段编码。IFC4 STEP 导出生成了一个带有类型化元素的有效空间树**(当输入由测试夹具合成的 `DecodedElement` 时)** —— 参见 `tests/fixtures/synthetic-project.ifc`。 **在真实的 `.rvt` 项目文件上,元素提取管道目前无法生成类型化元素** —— 基于 schema 的扫描器在 `Revit_IFC5_Einhoven.rvt` 上仅命中了 405 个类中的 1 个(`HostObjAttr`,一个宽松的父类),而在 `2024_Core_Interior.rvt` 上命中了 0 个类。项目内置了 80 个按类划分的解码器(`elements::all_decoders()` —— Wall, Floor, Door, Window, Column, Beam, Stair, Railing, Rebar, Room/Area/Space, Furniture, DesignOption, Phase, Workset, Electrical/Mechanical/Plumbing FamilyInstance 子类型),但仅针对合成的测试输入进行了测试——它们并未接入到 `iter_elements` 的调度路径中。根本原因调查(`reports/element-framing/RE-01-synthesis.md`)发现,元素实例数据存在于 `Partitions/*` 流中,其传输封装格式尚未被逆向工程破解;当前的遍历器只扫描包含文档级元数据的 `Global/Latest`。Q-01 语料库验证已获得许可并编写了脚本(`tools/fetch-corpus.sh`),但尚未针对 `project_corpus_smoke` 执行。请参阅下方的[尚不可用的功能](#what-does-not-work-yet)。 **随库一起发布的还有一个零上传的客户端浏览器查看器**,已上线于 。将 `.rvt` / `.rfa` 文件拖放到页面上——WebAssembly 构建会在当前标签页内对其进行解析,通过 Three.js 渲染 3D 模型(带有轨道控制 + 元素拾取 + 场景树),并提供一键**导出 glTF** / **导出 IFC** / **导出平面 SVG** 的功能。无需上传,无需账号,无遥测。CI 会确保编译后的 `.wasm` 不包含任何 `fetch` / `XMLHttpRequest` / `WebSocket` 导入。 非技术工作流程请从 [`docs/user-guide.md`](docs/user-guide.md) 开始。安装路径位于 [`docs/install.md`](docs/install.md)。有关简要的支持范围,请阅读 [`docs/status.md`](docs/status.md) 以及 [`docs/supported-profile.md`](docs/supported-profile.md) 中受支持的 MVP 输入配置。详细路线图任务位于 [`TODO.md`](TODO.md) 以及相应的 GitHub 里程碑/议题中。 采用 Rust 2024 版本 (MSRV 1.85)。**内置 15 个 CLI**(`rvt-analyze`, `rvt-info`, `rvt-inspect`, `rvt-schema`, `rvt-history`, `rvt-diff`, `rvt-corpus`, `rvt-dump`, `rvt-doc`, `rvt-ifc`, `rvt-write`, `rvt-gltf`, `rvt-sheet`, `rvt-elem-table`, `gen-fixture`)以及 `examples/` 下的 36 个可重现的探测程序。通过 `rvt-py` 工作区成员中的 pyo3+maturin 提供 Python 绑定(SEC-12/13 —— 核心 `rvt` crate 无条件启用 `#![forbid(unsafe_code)]`)—— `pip install rvt`。 ## 目前可用的功能 | 层级 | 状态 | 说明 | |---|---|---| | OLE/CFB 容器打开 | ✓ | 无需 Revit | | 截断的 gzip 流解码 | ✓ | | | `BasicFileInfo` 元数据 | ✓ | 版本、构建号、GUID、原始路径 | | `PartAtom` XML | ✓ | 标题、OmniClass 代码、分类体系 | | 流预览提取 | ✓ | 干净的 PNG,已剥离封装 | | `Formats/Latest` schema 解析 | ✓ | 395 个类,13,570 个字段 | | 字段类型分类 | ✓ | **在 `rac_basic_sample_family` 11 个发布的语料库上达到 100%** —— CI 回归门控 | | 跨版本标签漂移表 | ✓ | 首个公开的 122×11 数据集 | | Layer 5a ADocument 遍历器 | 部分可用 | 在 Revit 2024–2026 上可靠;2016–2023 的入口点检测待完善 | | 流级修改写入器 | ✓ | 13/13 个流字节保持不变;`rvt-write` CLI + JSON 补丁清单 | | 字段级语义写入器 | 待定 | 受 [ADR-002](docs/decisions/ADR-002-semantic-write-api-gate.md) 限制;在解码器/验证证据足够充分之前不会提供稳定的 API | | Layer 5b 按类解码器 | 部分可用 | **`elements::all_decoders()` 中存在 80 个解码器结构**(Level/Wall/Floor/Roof/Ceiling/Door/Window/Column/Beam/Stair/Railing/Room/Furniture/Rebar/DesignOption/Phase/Workset/FamilyInstance 子类型等),并且通过了针对合成 `InstanceField` 输入的单元测试。**在真实的 `.rvt` 文件上,`walker::iter_elements` 无法触达它们** —— 扫描器在它所扫描的流中找不到它们的类标签(RE-01 综合分析)。仅在已提交的测试夹具上进行了端到端测试。将其接入 `iter_elements` 被跟踪为 DEC-01..05。 | | IFC4 STEP 导出 —— 空间树 | ✓ | `IfcProject` + `IfcSite` + `IfcBuilding` + `IfcBuildingStorey` + OmniClass 分类 | | IFC4 STEP 导出 —— 元素 | 部分可用 | `IfcWall`/`IfcSlab`/`IfcRoof`/`IfcCovering`/`IfcDoor`/`IfcWindow`/`IfcColumn`/`IfcBeam`/`IfcStair`/`IfcRailing`/`IfcFurniture`/`IfcFooting`/`IfcReinforcingBar`/`IfcSpace`/`IfcBuildingElementProxy` 构造函数都能正确发射**(当输入由合成的 `DecodedElement` 生成时)**(`tests/fixtures/synthetic-project.ifc` 就是一个这样的输出,包含 10 个类型化元素,已经过 BlenderBIM 验证)。在真实的项目文件上,遍历器目前只能恢复 `HostObjAttr`(一个父类),因此导出器只能发射 `IFCBUILDINGELEMENTPROXY` 代理,而不是类型化的 walls/doors。要从真实文件中获取类型化元素,需要 RE-01 综合分析中描述的传输格式突破。 | | IFC4 STEP 导出 —— 几何图形 | ✓(矩形) | `IfcExtrudedAreaSolid` + `IfcRectangleProfileDef` 链已接入元素的 Representation 插槽。仅支持矩形轮廓(弯曲的门、非正交墙仍待完善 —— IFC-17/24)。 | | IFC4 STEP 导出 —— 材质 | ✓ | 通过 `IfcMaterial` + `IfcRelAssociatesMaterial` 支持单一材质;通过 `IfcMaterialLayerSet` + `IfcMaterialLayerSetUsage` 支持复合组件(IFC-28/29)。带有分层组合的墙/地板/屋顶均可正确导出。 | | IFC4 STEP 导出 —— 属性 | ✓ | 带有类型化值(`IfcText`, `IfcInteger`, `IfcReal`, `IfcBoolean`, `IfcLengthMeasure`, `IfcPlaneAngleMeasure`, `IfcAreaMeasure`, `IfcVolumeMeasure`, `IfcCountMeasure`, `IfcTimeMeasure`, `IfcMassMeasure`)的 `IfcPropertySet` + `IfcPropertySingleValue` 通过 `IfcRelDefinesByProperties` 接入。 | | IFC4 STEP 导出 —— 洞口 | ✓ | `IfcOpeningElement` + `IfcRelVoidsElement` + `IfcRelFillsElement` —— 门和窗在宿主墙中切割出真正的孔洞(已经过 BlenderBIM 验证)。 | | 几何图形提取 | 部分可用 | 提供了用于 walls/slabs/roofs/ceilings/columns/beams/stairs/doors/windows 的拉伸辅助程序(GEO-27..35, IFC-16..26)。存在 Swept / revolved / BRep 变体(IFC-17/18/19/20),但在默认发射路径中使用了带 `rvt` 功能标志的矩形回退。 | | glTF 2.0 二进制导出 | ✓ | `model_to_glb()` 生成一个有效的 `.glb` 文件,可在 Three.js 的 `GLTFLoader` 中加载(VW1-04)。提供 `rvt-gltf` CLI。 | | 2D 平面图 SVG 导出 | ✓ | `render_plan_svg()` 生成按类别着色的 SVG(墙为黑色,门为蓝色,柱子为红色,……)(VW1-11)。提供 `rvt-sheet` CLI。 | | 浏览器查看器 | ✓ | 已上线于 。核心库的 WebAssembly 构建 + Three.js + Vite。零上传,标签页内解析,导出 glTF/IFC/SVG 按钮,通过 `share::ViewerState` 进行基于 URL 的分享。(已交付 VW1-01 至 VW1-24。) | | Fuzz 回归测试套件 | ✓ | 9 个 libFuzzer 目标 + 38 个位于 `tests/fuzz_regressions.rs` 下的合成对抗性回归用例。在 9 字节截断的标头上捕获到了一个真实的 `gzip_header_len` 边界 bug(Q-04)。 | ## 尚不可用的功能 | 缺失项 | 状态 | 证据 | |---|---|---| | 从真实 `.rvt` 项目文件中提取元素 | 不可用 | 生产环境的 `iter_elements` 比较保守,不会返回低置信度的父类候选者。诊断扫描在 `Global/Latest` 上依然只能找到 `HostObjAttr` 样式的候选者;真实的 Walls/Floors/Doors 需要分区流解码器。参见 `reports/element-framing/RE-01-synthesis.md`。 | | 将 80 个按类划分的解码器接入遍历器 | 未接入 | `elements::all_decoders()` 是一个结构体注册表;`iter_elements` 调用的是通用的 `decode_instance`。即使扫描器找到了 Wall,它也不会使用 `WallDecoder`。被跟踪为 DEC-01..05。 | | 从真实 `.rvt` 提取 IFC4 类型化元素 | 实验性 | `RvtDocExporter::export` 在证据足够充分时会发射框架实体和受版本控制的 2023 ArcWall `IFCWALL` 记录。默认情况下会抑制通用的 `HostObjAttr` 代理发射;诊断导出可以包含带有来源信息的这些候选者。门/地板/窗户和几何图形在真实项目文件上仍然未被证实。 | | 社区语料库解析验证 | 未执行 | `tools/fetch-corpus.sh` 已提交但从未运行。7 个 MIT/Apache 存储库中的 41 个候选文件尚未针对 `project_corpus_smoke` 进行检查。许可证验证非常可靠;解析兼容性未知。被跟踪为 Q01-01..04。 | | 分区流传输格式 | 未逆向工程破解 | `examples/probe_*` 中的 12 个 RE 探测程序测试了五个假设;全部被驳回。`Global/ContentDocuments` 被确定为结构化索引,但其 ID 空间与 `ElemTable` 的 ID 空间不匹配(重叠率 6/30705)。这是元素提取的阻碍。 | 真实字节上的 Scalar-Container 传输格式 | 仅假设 | L5B-09 修复假设类型 0x01/0x02/0x04/0x05/0x07/0x0b/0x0d 具有等价于 Vector 的布局。往返测试使用了合成的字节;没有进行过真实 .rvt 的往返测试。被跟踪为 WF-01..03。 | | 用于扩充/缩减情况的已修补 CFB 往返 | 已覆盖 | 族语料库测试覆盖了身份、扩充、缩减、多流和缺失流补丁;项目语料库测试覆盖了身份/扩充/缩减/多流情况,同时保留了未修补的流以及 GUID/历史记录。 | ## 为什么 schema 很重要 openBIM 社区——以 [buildingSMART International](https://www.buildingsmart.org/) 和 IFC 标准为核心——多年来一直致力于 Revit 的互操作性。Autodesk 自己的 [revit-ifc](https://github.com/Autodesk/revit-ifc) 导出器在 Revit **内部**运行并使用 Revit API,因此它只能输出 API 暴露出来的内容。现实中从 Revit 导出的 IFC 经常被公开描述为*“非常有限”*(thinkmoult.com)、*“数据丢失”*(Reddit r/bim)以及*“开箱即用,简直就是垃圾”*([OSArch Wiki 的 Revit openBIM 指南](https://wiki.osarch.org/index.php?title=Revit_setup_for_OpenBIM))。 这里的 schema 工作——解码 `Formats/Latest` 并对 11 个 Revit 版本中 100% 的字段编码进行分类——正是字节级读取器所需要的字典。一旦 [`TODO.md`](TODO.md) 中的分区流解码器工作落地,生成的 IFC 导出就能携带比 Revit API 选择暴露的内容更多的信息。这就是我们的核心论点。但它目前还不是最终交付的产品。 如果你正在构建 BIM/AEC 工具,并希望将一个 Apache-2.0 许可的 Revit 读取器组合到你的技术栈中,当前的版本涵盖: - **可靠地** —— 元数据提取,schema 内省(在 11 个版本的族语料库上实现 100% 的字段类型分类),OLE/CFB 打开,截断的 gzip 解码,从合成输入发射 IFC4 STEP,glTF 2.0 二进制,2D 平面图 SVG,80 个针对合成夹具通过单元测试的按类划分的解码器结构。 - **作为脚手架,目前在真实项目文件上还不可用** —— 广泛的诊断性元素扫描和 walker→IFC 集成。生产环境导出现在会抑制低置信度的 `HostObjAttr` 代理,仅在受到语料库证据支持时才发射狭窄的、受版本控制的 2023 ArcWall 路径(参见上方“尚不可用的功能”)。 参见 [`tests/fixtures/synthetic-project.ifc`](tests/fixtures/synthetic-project.ifc) 获取已提交的 IFC 示例输出 —— `IfcProject` + `IfcSite` + `IfcBuilding` + 三个 `IfcBuildingStorey` + 10 个类型化的 `IfcWall`/`IfcSlab`/`IfcDoor`/`IfcWindow`/`IfcStair`/`IfcBuildingElementProxy` 实体,全部通过 `IfcRelContainedInSpatialStructure` 接入楼层,已经过 BlenderBIM 和 IfcOpenShell 验证。位于 的浏览器查看器运行相同的 IFC 发射管道——因此,只需拖放一个 .rvt 文件,生成的 Export IFC 就能在标签页中端到端地生成元数据/空间脚手架(而非类型化元素)。 ## 快速演示 一条命令即可生成完整的取证图景——身份、升级历史、格式锚点、schema 表、Phase D 链接直方图、内容元数据以及披露扫描: ``` cargo build --release ./target/release/rvt-analyze --redact path/to/your.rfa ``` ### 通过 Python ``` import rvt f = rvt.RevitFile("my-project.rfa") print(f.version, f.part_atom_title) # 2024 "0610 x 0915mm" print(f.read_adocument()["fields"][-1]) # {name: m_devBranchInfo, kind: element_id, tag: 0, id: 35} open("out.ifc", "w").write(f.write_ifc()) ``` 安装:`pip install rvt` —— 或者使用 [`maturin build --release --manifest-path rvt-py/Cargo.toml`](docs/python.md#from-source) 从源码构建。完整的 API + Jupyter Notebook 演示:[`docs/python.md`](docs/python.md) 和 [`docs/rvt-python-quickstart.ipynb`](docs/rvt-python-quickstart.ipynb)。 有关 cargo、PyPI、源码和查看器安装/冒烟测试的路径,请参见 [`docs/install.md`](docs/install.md)。 ### 在浏览器中 将 `.rvt` / `.rfa` / `.rte` / `.rft` 文件拖放到 —— 绝不会有任何数据离开当前标签页。该查看器将核心库编译为 WebAssembly(`wasm-pack build --target web --features wasm`),在专用的 worker 中运行解析,并通过 Three.js 渲染 3D。一键按钮可将模型导出为 **glTF 2.0 二进制**、**IFC4 STEP** 或 **平面图 SVG**。URL 状态(相机姿态 + 类别过滤器)可通过哈希片段进行分享。 隐私策略由 CI 强制执行:部署工作流(`.github/workflows/deploy-viewer.yml`)在每次构建时都会运行 `wasm-objdump -j Import`,如果编译后的 `.wasm` 导入了 `fetch`、`XMLHttpRequest` 或 `WebSocket`,则构建失败。参见 [`docs/viewer-privacy-posture.md`](docs/viewer-privacy-posture.md)。 **示例输出**(全部使用 `--redact` 预先进行了清洗,已提交供审查): - **单屏摘要**:[`docs/demo/rvt-analyze-2024-teaser.txt`](docs/demo/rvt-analyze-2024-teaser.txt) —— 四个突出显示的部分适合在一个终端屏幕内显示(身份、格式锚点、Phase D 链接、披露扫描) - **完整终端报告**:[`docs/demo/rvt-analyze-2024-redacted.txt`](docs/demo/rvt-analyze-2024-redacted.txt) —— 130 行结构化输出 - **JSON 报告**: [`docs/demo/rvt-analyze-2024-redacted.json`](docs/demo/rvt-analyze-2024-redacted.json) —— 机器可读版本 - **标签漂移热力图**:[`docs/data/tag-drift-heatmap.svg`](docs/data/tag-drift-heatmap.svg) —— 11 个 Revit 版本中类 ID 漂移的可视化证明 `--redact` 标志(在此 repo 中提交的每个演示输出中默认开启)会将 Windows 用户名、Autodesk 内部路径和项目 ID 文件夹名称清洗为 `` 标记,同时保留路径结构,以确保声明仍然可验证。在针对你自己的文件进行私下运行时,请省略此标志。 ## 结果概览 针对一个 400 KB 的 RFA 夹具运行内置的 CLI: - **元数据**:版本、构建标签、创建者路径、文件 GUID、区域设置 (`rvt-info`) - **Atom XML**:标题、OmniClass 代码、分类体系(`rvt-info` 解析 `PartAtom`) - **预览**:干净的 PNG 缩略图,去除了 300 字节的 Revit 封装(`rvt-info --extract-preview`) - **Schema**:395 个类 + 1,114 个字段 + 每个字段的类型化编码(`rvt-schema`) - **历史**:保存过此文件的每一个 Revit 版本(`rvt-history`) - **批量字符串**:3,746 条来自 Partitions/NN 的长度前缀为 UTF-16LE 的记录 —— Autodesk 单位/规格/参数组标识符、OmniClass + Uniformat 代码、Revit 类别标签、本地化的格式字符串(`rvt-history --partitions`) `rvt-schema` 提取的每一个类和字段名称都已与公开的 `RevitAPI.dll` NuGet 包导出的 C++ 符号列表进行了交叉核对。我们检查过的所有顶级带标签类名称(ADocument, DBView, HostObj, LoadBCBase, Symbol, APIAppInfo, APropertyDouble3, ElementId 等)均出现在该导出列表中,并带有修饰名特征码(例如 `__cdecl NotNull::NotNull(class ADocument *)`),这证实了磁盘上的 schema 名称与编译后的符号是一一对应的。 在同一个 DLL 内的 C++ 断言字符串中也出现了一个构建服务器路径;为了完整性在侦测报告中提到了它,但它并不代表读取器会从 .rvt / .rfa 文件中提取任何此类信息。 ## Phase D 发现(本项目的与众不同之处) 六项可重现的发现,均记录在 [`docs/rvt-moat-break-reconnaissance.md`](docs/rvt-moat-break-reconnaissance.md) 中,并可从 `examples/` 重现: 1. **schema 为数据建立索引。** 类名不以 ASCII 形式出现在 `Global/Latest` 中;来自 `Formats/Latest` 的类标签(类名后的 u16,设置了 0x8000 标志)的出现频率是均匀随机率的约 340 倍。排在首位的标签 `AbsCurveGStep` 在 938 KB 的解压后 Global/Latest 中出现了 19,415 次。[`examples/link_schema.rs`] 2. **标签在不同版本间会发生漂移**,但是是通过稳定排序分配的。`ADocWarnings` = 0x001b(从 2016→2026 不变),因为在按字母顺序排在它前面的类从未被添加过。`AbsCurveGStep` 在这十年间随着插入了 19 个新的 A 类条目,从 0x0053 偏移到了 0x0066。完整的 122 个类 × 11 个版本的漂移表:[`docs/data/tag-drift-2016-2026.csv`](docs/data/tag-drift-2016-2026.csv),在 [`docs/data/tag-drift-heatmap.svg`](docs/data/tag-drift-heatmap.svg) 中进行了可视化。此数据的首个公开可用版本。[`examples/tag_drift.rs`] 3. **Revit 2021 是一次重大的未记录格式过渡。** Global/Latest 增长了 27 倍(~26 KB → ~715 KB),同时 Forge Design Data Schema 命名空间(`autodesk.unit.*`, `autodesk.spec.*`)在 Partitions/NN 中首次亮相。两种症状,一个事件。任何为 2016-2020 构建的读取器在指向 2021+ 时都会无声无息地丢失 30 倍以上的数据。 4. **参数组命名空间在 Revit 2024 中单独发布。** `autodesk.parameter.group.*` 标识符仅出现在 2024 及以后版本中——比单位/规格晚了三个版本。从磁盘上的字节确定 Forge schema 推出的时间:[`examples/tag_drift.rs`](examples/tag_drift.rs),[`src/object_graph.rs`](src/object_graph.rs)。 5. **族文件中有一个稳定的 Revit 格式标识符 GUID。** 在 `.rfa` 族文件中,解压后的 `Global/PartitionTable` 为 167 字节,并且**在所有 Revit 版本 2016-2026 中,这些字节中有 165 个是逐字节相同的**(98.8% 不变)。该不变区域包含一个前所未见的 UUIDv1:`3529342d-e51e-11d4-92d8-0000863f27ad`。MAC 后缀 `0000863f27ad` 与大约 2000 年已知的 Autodesk 开发工作站特征码相匹配。这对于族文件检测很有用。**范围更正(2026-04-21):** 这个不变量是一个*族文件*锚点,而不是通用的 Revit 文件锚点。我们探测的三个真实的 `.rvt` 项目文件在一个较短的 87 字节 `PartitionTable` 中带有三个不同的 GUID(Revit 2023 上是 `6a6261fd-...`,2024 上是 `552368c6-...`,2025 上是全零)。使用族 GUID 的文件类型嗅探器能正确拒绝非族文件,但无法识别它们。参见 [`docs/project-file-corpus-probe-2026-04-21.md`](docs/project-file-corpus-probe-2026-04-21.md)。[`examples/partition_full.rs`] 6. **带标签的类记录结构已解码。** `Formats/Latest` 中的每个类声明都带有一个显式标签(带有 0x8000 标志的 u16)、可选的父类和声明的字段计数,随后是 N 个包含名称 + C++ 类型编码的字段记录。`HostObjAttr` 现在解析为 `{tag=107, parent=Symbol, declared_field_count=3}`,所有三个字段名(`m_symbolInfo`, `m_renderStyleId`, `m_previewElemId`)均被逐字节提取。[`examples/record_framing.rs`, `src/formats.rs`] 在 Autodesk 发布的参考内容中还发现了三种无意披露的模式——为了避免再次传播,本 README 中隐去了具体值;为了安全研究的可重现性,被记录在 [`docs/rvt-moat-break-reconnaissance.md`](docs/rvt-moat-break-reconnaissance.md) 中: - 一个面向客户的 OneDrive 路径,泄露了 Autodesk 员工个人样本创作工作流的目录结构。 - 一个固化在公共 `RevitAPI.dll` 内 C++ 断言字符串中的构建服务器路径。 - `Contents` 流内的一个创建者名称字段,它会随着样本族的每个副本传播,保留了 Revit 1997 年最初开发人员之一的姓名。 **下游安全:** `rvt-analyze` CLI 自带 `--redact` 标志(在此 repo 中提交的任何演示输出中默认开启),它会将创建者路径、Autodesk 内部路径和构建服务器路径重写为 `` 标记,同时保留周围的结构。任何消费 rvt-rs 输出并对其进行公开展示的工具都应该这样做。 ## 库接口 所有模块均在默认构建和 `wasm` 功能标志下进行编译。有关类型文档,请参见 `src/`: | 模块 | 功能描述 | |---|---| | `reader` | 使用 `OpenLimits` 打开任何 Revit 文件,枚举每个 OLE 流,获取原始流字节,有界读取 | | `compression` | 截断的 gzip 解码(`inflate_at`, `inflate_at_auto`, `inflate_at_with_limits`)+ 多块(`inflate_all_chunks_with_limits`)+ 用于回写的截断 gzip 编码器(`truncated_gzip_encode`) | | `basic_file_info` | 版本、构建标签、GUID、创建者路径、区域设置 —— 读取路径 + 字节回写编码器(`BasicFileInfo::encode`) | | `part_atom` | 带有 Autodesk `partatom` 命名空间的 Atom XML —— 标题、OmniClass、分类体系 —— 读取 + 编码 | | `formats` | 使用 `FieldType` 分类解析 + 编码 `Formats/Latest`(在 11 个版本的语料库上达到 100%) | | `walker` | 基于 schema 的实例遍历器 + 80 个解码器调度 + `detect_adocument_start` 入口点查找器 | | `elements` | 80 个 `ElementDecoder` 实现(Wall, Floor, Door, Window, Column, Beam, Stair, Railing, Rebar, Room, Furniture, …) | | `geometry` | 曲线 / 面 / 实体变体(Line, Arc, Ellipse, NURBS, Hermite, Ruled, Revolved, Extrusion, Sweep, Blend, SweptBlend, Boolean, Mesh, PointCloud) | | `object_graph` | `DocumentHistory`,用于 Global/Latest + Partitions/NN 的字符串记录提取器 | | `class_index` | 快速类名清单(BTreeSet) | | `corpus` | 跨版本字节差异分类器 | | `elem_table` | `Global/ElemTable` 头解析器 + 粗略的记录枚举 | | `partitions` | Partitions/NN 44 字节头部解码器 + gzip 数据块拆分器 | | `writer` | 字节保留的往返 `copy_file` + `write_with_patches`(原子临时文件重命名、流哈希验证)+ GUID + 历史记录保存 | | `round_trip` | 按类划分的编码器往返验证(`verify_instance_round_trip`) | | `ifc` | 完整的 IFC4 空间树 + 元素 + 材质 + 属性 + 洞口 + 拉伸几何 + glTF 2.0 二进制(`gltf::model_to_glb`)+ 平面图 SVG(`sheet::render_plan_svg`)+ 查看器数据模型(`scene_graph`, `camera`, `clipping`, `sheet`, `share`, `measure`, `annotation`, `pbr`) | | `streams` | Revit 文件中每个不变的 OLE 流的命名常量 | | `redact` | 为所有 CLI 提供的共享 PII 清洗器(`--redact` 标志) | | `wasm` | `#[cfg(feature = "wasm")]` —— 支持浏览器查看器的 14 个可由 JS 调用的 wasm-bindgen 绑定 | | `error` | 结构化错误类型(`Error` / `Result`) | 运行时能力: - 从磁盘打开任何 Revit 文件(magic `D0 CF 11 E0 A1 B1 1A E1`) - 枚举每个 OLE 流;查找特定版本的 `Partitions/NN` - 解压任何流(截断的 gzip 格式——标准 gzip 标头,无尾部 CRC/ISIZE) - 解析 `BasicFileInfo`、`PartAtom`,提取预览 PNG - 从 `Formats/Latest` 中提取 **395 条类记录**,包含每个带标签类的标签 + 父类 + 祖先标签 + 声明的字段计数 - 解码 167 字节的 `Global/PartitionTable` 结构,包括稳定的 Revit 格式标识符 GUID - 解码 307 字节的 `Contents` 流,包括内嵌的 UTF-16LE 元数据块 - 生成任何 `.rfa` / `.rvt` 文件的逐字节往返副本 - 每个文件在完整的 11 个版本的语料库上运行耗时 < 500 ms(release 构建) 内置 **14 个 CLI**: ``` cargo build --release # 一次性取证分析 — 所有 subsystems 汇总于一份报告 ./target/release/rvt-analyze --redact my-project.rvt ./target/release/rvt-analyze --redact --json my-project.rvt > report.json # 快速提取 metadata + schema 摘要 ./target/release/rvt-info --show-classes my-project.rvt # 机器可读 (JSON) ./target/release/rvt-info -f json my-project.rvt > meta.json # 提取嵌入的缩略图 ./target/release/rvt-info --extract-preview preview.png my-project.rvt # 通俗易懂的文件健康状况与 IFC export 准备度 ./target/release/rvt-inspect my-project.rvt ./target/release/rvt-inspect my-project.rvt --json # 比较同一文件的两个版本 (cross-version byte diff) ./target/release/rvt-diff --decompress 2018.rfa 2024.rfa # 导出完整的 class schema (395 个 classes,13,570 个 fields) ./target/release/rvt-schema my-project.rvt # 文档升级历史 (哪些 Revit 版本打开过此文件) ./target/release/rvt-history my-project.rvt # 从 Partitions/NN 中提取所有 UTF-16LE string record # (categories, OmniClass, Uniformat, Autodesk unit identifiers, …) ./target/release/rvt-history --partitions my-project.rvt # 对每个 decompressed stream 进行 Hex-dump (用于 Phase D 工作) ./target/release/rvt-dump my-project.rvt # IFC4 STEP export — 默认生成符合 spec-valid 的 scaffold,并附带如实客观的质量警告 ./target/release/rvt-ifc my-project.rvt -o out.ifc # 在写入 IFC 前要求更严格的质量门禁 ./target/release/rvt-ifc my-project.rvt -o out.ifc --mode strict # 附带可共享的 JSON readiness/support sidecar 的 IFC4 export ./target/release/rvt-ifc my-project.rvt -o out.ifc --diagnostics out.diagnostics.json # 诊断性 IFC export — 包含带有 provenance 的低置信度 proxy candidates ./target/release/rvt-ifc my-project.rvt -o diagnostic.ifc --diagnostic-proxies # glTF 2.0 binary export — 可加载至 Three.js / Blender / 任何 glTF viewer 中 ./target/release/rvt-gltf my-project.rvt -o out.glb # 2D plan-view SVG — 按 category 着色,可直接用于 plot/laser-cut/printing ./target/release/rvt-sheet my-project.rvt -o out.svg # Global/ElemTable dump — 包含已声明的 element-ids + record layout (family 12B / project 28B/40B) ./target/release/rvt-elem-table my-project.rvt --limit 20 # Stream-level write path — 通过 JSON manifest 修补 named OLE streams ./target/release/rvt-write my-project.rvt --patches patches.json -o patched.rvt # 单文件文档生成器 (为任意 RVT 提供 schema + sample-data render) ./target/release/rvt-doc my-project.rvt -o doc.md # Cross-version corpus 分析 (一次性处理 11 个 releases) ./target/release/rvt-corpus /path/to/corpus-dir ``` `examples/` 中提供了三十六个可重现的探测程序——侦测报告中的每个 FACT 各一个: ``` cargo build --release --examples # --- schema ↔ data linkage (Phase D) --- ./target/release/examples/probe_link # null-hypothesis: class names absent from Global/Latest ./target/release/examples/tag_bytes # hex around known class names in Formats/Latest ./target/release/examples/tag_dump # statistical sweep of post-name u16 patterns ./target/release/examples/link_schema # tag-frequency histogram in Global/Latest (340× non-uniformity) ./target/release/examples/tag_drift # per-class drift table 2016-2026 ./target/release/examples/tag_drift_svg # render drift table as colour-coded SVG heatmap # --- record framing (Phase 4c) --- ./target/release/examples/record_framing # dump bytes at tagged-class defs + first tag occurrence ./target/release/examples/elem_table_probe # Global/ElemTable structural sweep across releases ./target/release/examples/partitions_header_probe # 44-byte Partitions/NN header + chunk offsets ./target/release/examples/contents_probe # Contents stream decoder (creator name + build tag) # --- stable anchors --- ./target/release/examples/partition_invariant # find 165-byte invariant in Global/PartitionTable ./target/release/examples/partition_diff # show the 2 varying bytes per release ./target/release/examples/partition_full # full annotated hex dump + UUID decode # --- write path (Phase 6) --- ./target/release/examples/roundtrip # copy 2024 sample, verify all 13 streams identical ``` ## 格式概述 每个 Revit 文件都是一个 Microsoft Compound File Binary (OLE2) 容器,具有 此流布局(在 Revit 发布的 11 年间保持不变): ``` ├── BasicFileInfo UTF-16LE metadata ├── Contents custom 4-byte header + DEFLATE body ├── Formats/Latest DEFLATE — class schema inventory ├── Global/ │ ├── ContentDocuments tiny document list │ ├── DocumentIncrementTable DEFLATE — change tracking │ ├── ElemTable DEFLATE — element ID index │ ├── History DEFLATE — edit history (GUIDs) │ ├── Latest DEFLATE — current object state (17:1 ratio) │ └── PartitionTable DEFLATE — partition metadata ├── PartAtom plain XML (Atom + Autodesk partatom namespace) ├── Partitions/NN bulk data: 5-10 concatenated DEFLATE segments │ NN = 58, 60-69 for Revit 2016-2026 ├── RevitPreview4.0 custom header + PNG thumbnail └── TransmissionData UTF-16LE transmission metadata ``` 所有压缩流都使用“截断的 gzip”格式——标准的 10 字节 gzip 标头(magic `1F 8B 08 ...`)后跟原始的 DEFLATE 数据,但*没有* 合规的 gzip 写入器会生成的尾部 8 字节的 CRC32 + ISIZE。 Python 的 `gzip.GzipFile` 和 Rust 的 `flate2::read::GzDecoder` 都会拒绝 这些流。解决方法是手动跳过 10 字节的标头,并 在原始正文上使用 `flate2::read::DeflateDecoder`。 ## 逆向工程状态 | 层级 | 描述 | 状态 | |---|---|---| | 1 · 容器 | OLE2 / Microsoft Compound File ([MS-CFB]) | **完成** | | 2 · 压缩 | 截断的 gzip → 原始 DEFLATE | **完成** | | 3 · 流框架 | 每个流的自定义标头,`Partitions/NN` 数据块布局,`Contents` / `Preview` / `PartitionTable` 封装 | **完成** —— `PartitionTable` 的 165/167 个字节保持不变;44 字节的 `Partitions/NN` 标头已解码;`62 19 22 05` 封装 magic 已在 `Contents` + `RevitPreview4.0` 上确认 | | 4a · Schema 表 | 来自 `Formats/Latest` 的类名 + 字段 + C++ 类型特征码;按类划分的标签 + 父类 + 声明的字段计数;跨版本标签漂移图 | **完成** | | 4b · Schema→数据链接 | 来自 `Formats/Latest` 的标签在 `Global/Latest` 中的出现频率约为噪音率的 340 倍;schema 正是对象图的实时类型字典 | **完成** | | 4c.1 · 记录框架 | `Formats/Latest` 中带标签的类记录解析为结构化记录:`{tag, parent, ancestor_tag, declared_field_count}`;HostObjAttr → `{tag=107, parent=Symbol, ancestor_tag=0x0025 → APIVSTAMacroElem, declared_field_count=3}` | **完成** | | 4c.2 · 字段正文解码 | `FieldType` 枚举对跨 8 种变体(Primitive, String, Guid, ElementId, ElementIdRef, Pointer, Vector, Container)的 **100%** 的 schema 字段进行了分类。映射了 11 个判别字节,包括广义的标量基 Vector/Container(`{kind} 0x10 ...` / `{kind} 0x50 ...`)以及 `0x0d` 点类型基。 | **完成(在跨 11 个版本的语料库的 13,570 个字段上达到 100.00%;无 `Unknown`)** | | 4d · ElemTable | `Global/ElemTable` 头解析器 + 粗略记录枚举;由于等待每个元素的 schema 查找,记录语义仍未解决 | **部分完成** | | 5 · IFC4 导出 | 完整的空间树 + 按元素划分的 IFC 实体 + `IfcLocalPlacement` + `IfcExtrudedAreaSolid` + 复合材质层 + 类型化属性集 + 用于门和窗的 `IfcOpeningElement`/`IfcRelVoidsElement`/`IfcRelFillsElement`。确定性的 ISO-10303-21 输出。经 IfcOpenShell + BlenderBIM 验证。 | **完成**(矩形轮廓;swept / revolved / BRep 回退已交付,但在默认发射路径中使用矩形——IFC-17/24 是待完善的部分) | | 6 · 写入路径 | 对未更改文件的字节保留副本;对命名的 OLE 流进行流级补丁,带有原子临时文件重命名、按流验证、扩充/缩减/多流覆盖以及 GUID/历史记录保存检查。字段级语义补丁属于 Phase 7。 | **完成(流级);字段级待定** | | 7 · 浏览器查看器 | 核心库的 WebAssembly 构建 + Three.js + Vite + Pages 部署。零上传,标签页内解析,glTF/IFC/SVG 导出按钮,URL 状态分享。上线于 。 | **完成** (VW1-01..24) | 最初的 5 个 P0 研究问题(Q4-Q7)均已**解决**。Layer 4c.2 在 11 个版本的参考语料库上实现了 **100.00% 的字段类型分类**(总计 13,570 个 schema 字段,零 `Unknown`)。IFC4 发射、glTF 导出、2D 平面图和浏览器查看器均已交付。下一个前沿是真实世界项目文件语料库的验证(Q-01)——一次 `.rvt` 探测已经捕获到了一个族文件从未遇到过的 `gzip_header_len` 边界 bug。 此阶段的主要发现: - **Q4** 每个带标签类前导码中的 u16“标志”字是一个**类标签引用**(祖先 / mixin / protocol)。9/9 个非零值都解析为同一 schema 中的命名类。 - **Q5** 每个字段的 `type_encoding` 格式为 `[byte category][u16 sub_type][optional body]`。映射了 9 个类别字节(`0x01` bool, `0x02` u16, `0x04/0x05` u32, `0x06` f32, `0x07` f64, `0x08` string, `0x09` GUID, `0x0b` u64, `0x0e` reference/container)。 - **Q5.1** 覆盖范围扩展到了 84% 的字段。 - **Q5.2** 覆盖范围达到了 **100%** 的字段(跨 11 个版本共 13,570 个)。将广义的 `{scalar_base} 0x10 ...` / `{scalar_base} 0x50 ...` 作为 vector/container 修饰符;添加了 `0x0d` 点类型基;添加了 `0x08 0x60 ...` 备用字符串编码;为带有特定目标类标签的引用添加了 `ElementIdRef { referenced_tag, sub }`;添加了仅在 2016–2018 年见过的已废弃的 `0x03` i32-alias。参见 `docs/rvt-moat-break-reconnaissance.md` §Q5.2。 - **Q6** `Global/Latest` **并不是**一个索引 + 堆结构——它是一个扁平的 TLV 流。 - **Q6.1** 实例数据是**基于 schema 的**(无标签,protobuf 风格)。解码需要从已知入口点开始进行 schema 优先的顺序遍历。 - **Q7** `Partitions/NN` 尾部的 u32 字段**不是**按数据块划分的偏移量。Gzip-magic 扫描依然是正确的。 包含 12 个带有日期的附录的完整分析叙述位于 [`docs/rvt-moat-break-reconnaissance.md`]()。完整长度的综合分析位于 [`docs/rvt-phase4c-session-2026-04-19.md`](docs/rvt-phase4c-session-2026-04-19.md)。 ## 样本语料库 集成测试针对 Autodesk 公开的 `rac_basic_sample_family` RFA 夹具的 11 个版本运行 (从 2016 到 2026 的每个 Revit 版本各一个)。这些文件通过 `phi-ag/rvt` 存储库中的 Git LFS 进行分发。拉取它们: ``` cd /path/to/rvt-recon/samples git clone https://github.com/phi-ag/rvt.git _phiag cd _phiag && git lfs pull cd .. && cp _phiag/examples/Autodesk/*.rfa . ``` `tests/samples.rs` 中的集成测试会跳过任何 缺失 RFA 文件的年份,因此部分语料库也是可以的——你只会看到 `skipping 2024: sample not present` 这样的消息。 ## 设计选择 - **cfb crate 优先于自定义 OLE 解析器** —— `cfb` crate 非常成熟, 已针对 Office 文档进行了测试,并且同时处理 short 和 regular 扇区。比编写我们自己的解析器要快。 - **flate2 优先于直接的 miniz_oxide** —— `flate2` 封了 `miniz_oxide` (纯 Rust)和 libz 两种后端。我们选择默认的纯 Rust 构建,以 避免依赖 C 工具链。 - **quick-xml 优先于 xml-rs** —— 速度快约 3 倍,对零拷贝友好, 并且 `.from_str` + 事件循环模式更接近 Go/Python 解析器的做法。 - **encoding_rs 优先于标准库** —— Revit 的 UTF-16LE 流有时在 边界处存在错误的字节对(单字节标记被交错插入)。 在标准库会发生 panic 的地方,`encoding_rs` 能够优雅地恢复。 - **类名使用 BTreeSet** —— 输出中的确定性排序(加上 排序的 JSON)对于可差异化的 CLI 输出至关重要。 ## 运行测试 ``` cargo test --release ``` 预期输出(截至 2026-04-21): ``` test result: ok. 697 passed; 0 failed (lib unit tests) test result: ok. 38 passed; 0 failed (fuzz-regression harness, Q-04) test result: ok. 9 passed; 0 failed (integration tests, 11-version corpus) test result: ok. 3 passed; 0 failed (ifc_roundtrip + ifc_synthetic_project/structural) ... ``` 如果缺少样本 RFA,集成测试将被跳过。Fuzz 回归测试套件(`tests/fuzz_regressions.rs`)通过每个 libFuzzer 目标的入口点运行手工制作的对抗性输入——不需要 libFuzzer 运行时——因此未来任何导致抗崩溃性退化的提交都会在本地触发门控失败。 ## 许可证与商标 - **代码**:Apache License 2.0。有关完整 文本,请参见 [`LICENSE`](LICENSE);有关归属细节,请参见 [`NOTICE`](NOTICE)。 - **商标**:“Autodesk”和“Revit”是 Autodesk, Inc. 的注册商标。本项目**未隶属于、也未经 Autodesk 认可或赞助**。本项目中提及的“Autodesk”和“Revit”是 为了标识此读取器解析的文件格式,属于指示性合理使用。 - **互操作性依据**:出于开发 独立开发的互操作性程序的目的而进行的逆向工程,在美国根据 *Sega Enterprises v. Accolade*, 977 F.2d 1510 (9th Cir. 1992) 和 *Sony Computer Entertainment v. Connectix*, 203 F.3d 596 (9th Cir. 2000) 被认定为合法的合理使用,在 欧盟根据 EU Software Directive 2009/24/EC 第 6 条被认定合法。文件格式本身不属于 可受版权保护的主题(*Baker v. Selden*, 101 U.S. 99 (1879);*Lotus Development v. Borland*, 516 U.S. 233 (1996))。 - **未使用、引用或 分发任何 Autodesk 专有代码。所有的文件格式观察均是 通过检查公开发布的 Autodesk 样本内容的字节以及解析公共的 `RevitAPI.dll` NuGet 包的 导出符号列表作出的。参见 [`NOTICE`](NOTICE)。
标签:Autodesk Revit, BIM, IFC, Rust, 云资产清单, 数据解析, 文件格式, 网络流量审计, 逆向工程, 通知系统