rvt 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rvt-0.1.2/CHANGELOG.md +344 -0
- rvt-0.1.2/CITATION.cff +32 -0
- rvt-0.1.2/CODE_OF_CONDUCT.md +67 -0
- rvt-0.1.2/CONTRIBUTING.md +137 -0
- rvt-0.1.2/Cargo.lock +792 -0
- rvt-0.1.2/Cargo.toml +113 -0
- rvt-0.1.2/LICENSE +201 -0
- rvt-0.1.2/NOTICE +80 -0
- rvt-0.1.2/PKG-INFO +350 -0
- rvt-0.1.2/README.md +327 -0
- rvt-0.1.2/SECURITY.md +63 -0
- rvt-0.1.2/docs/benchmarks.md +59 -0
- rvt-0.1.2/docs/corpus-sources.md +77 -0
- rvt-0.1.2/docs/data/bench-2024.md +9 -0
- rvt-0.1.2/docs/data/tag-drift-heatmap.svg +1485 -0
- rvt-0.1.2/docs/python.md +198 -0
- rvt-0.1.2/docs/rvt-moat-break-reconnaissance.md +1774 -0
- rvt-0.1.2/docs/rvt-phase4c-session-2026-04-19.md +259 -0
- rvt-0.1.2/docs/rvt-python-quickstart.ipynb +224 -0
- rvt-0.1.2/examples/adocument_bruteforce.rs +182 -0
- rvt-0.1.2/examples/adocument_entry.rs +161 -0
- rvt-0.1.2/examples/adocument_walk.rs +198 -0
- rvt-0.1.2/examples/adocument_walker_v1.rs +358 -0
- rvt-0.1.2/examples/contents_probe.rs +99 -0
- rvt-0.1.2/examples/directory_bytewise_scan.rs +248 -0
- rvt-0.1.2/examples/directory_class_lookup.rs +150 -0
- rvt-0.1.2/examples/directory_probe.rs +131 -0
- rvt-0.1.2/examples/directory_vs_elemtable.rs +214 -0
- rvt-0.1.2/examples/elem_table_probe.rs +105 -0
- rvt-0.1.2/examples/elem_table_sanity.rs +66 -0
- rvt-0.1.2/examples/field_type_probe.rs +300 -0
- rvt-0.1.2/examples/flag_word_probe.rs +143 -0
- rvt-0.1.2/examples/instance_scan.rs +100 -0
- rvt-0.1.2/examples/link_schema.rs +119 -0
- rvt-0.1.2/examples/partition_diff.rs +34 -0
- rvt-0.1.2/examples/partition_full.rs +99 -0
- rvt-0.1.2/examples/partition_invariant.rs +145 -0
- rvt-0.1.2/examples/partitions_header_probe.rs +112 -0
- rvt-0.1.2/examples/partitions_q7.rs +92 -0
- rvt-0.1.2/examples/post_directory.rs +145 -0
- rvt-0.1.2/examples/post_table_b_density.rs +164 -0
- rvt-0.1.2/examples/post_table_b_head.rs +116 -0
- rvt-0.1.2/examples/probe_link.rs +67 -0
- rvt-0.1.2/examples/record_framing.rs +179 -0
- rvt-0.1.2/examples/roundtrip.rs +36 -0
- rvt-0.1.2/examples/second_table_probe.rs +92 -0
- rvt-0.1.2/examples/table_b_structure.rs +152 -0
- rvt-0.1.2/examples/tag_bytes.rs +34 -0
- rvt-0.1.2/examples/tag_drift.rs +220 -0
- rvt-0.1.2/examples/tag_drift_svg.rs +177 -0
- rvt-0.1.2/examples/tag_dump.rs +116 -0
- rvt-0.1.2/examples/unknown_bytes.rs +59 -0
- rvt-0.1.2/examples/unknown_bytes_deep.rs +71 -0
- rvt-0.1.2/examples/write_roundtrip.rs +79 -0
- rvt-0.1.2/pyproject.toml +41 -0
- rvt-0.1.2/pytest.ini +6 -0
- rvt-0.1.2/python/rvt/__init__.py +28 -0
- rvt-0.1.2/python/rvt/__init__.pyi +161 -0
- rvt-0.1.2/python/rvt/py.typed +0 -0
- rvt-0.1.2/src/basic_file_info.rs +202 -0
- rvt-0.1.2/src/bin/rvt_analyze.rs +891 -0
- rvt-0.1.2/src/bin/rvt_corpus.rs +122 -0
- rvt-0.1.2/src/bin/rvt_diff.rs +171 -0
- rvt-0.1.2/src/bin/rvt_doc.rs +175 -0
- rvt-0.1.2/src/bin/rvt_dump.rs +118 -0
- rvt-0.1.2/src/bin/rvt_history.rs +209 -0
- rvt-0.1.2/src/bin/rvt_ifc.rs +64 -0
- rvt-0.1.2/src/bin/rvt_info.rs +165 -0
- rvt-0.1.2/src/bin/rvt_schema.rs +137 -0
- rvt-0.1.2/src/class_index.rs +101 -0
- rvt-0.1.2/src/compression.rs +171 -0
- rvt-0.1.2/src/corpus.rs +329 -0
- rvt-0.1.2/src/elem_table.rs +138 -0
- rvt-0.1.2/src/error.rs +31 -0
- rvt-0.1.2/src/formats.rs +1033 -0
- rvt-0.1.2/src/ifc/entities.rs +59 -0
- rvt-0.1.2/src/ifc/mod.rs +203 -0
- rvt-0.1.2/src/ifc/step_writer.rs +650 -0
- rvt-0.1.2/src/lib.rs +108 -0
- rvt-0.1.2/src/object_graph.rs +269 -0
- rvt-0.1.2/src/part_atom.rs +199 -0
- rvt-0.1.2/src/partitions.rs +169 -0
- rvt-0.1.2/src/python.rs +315 -0
- rvt-0.1.2/src/reader.rs +253 -0
- rvt-0.1.2/src/redact.rs +156 -0
- rvt-0.1.2/src/streams.rs +54 -0
- rvt-0.1.2/src/walker.rs +428 -0
- rvt-0.1.2/src/writer.rs +204 -0
- rvt-0.1.2/tests/common/mod.rs +36 -0
- rvt-0.1.2/tests/field_type_coverage.rs +122 -0
- rvt-0.1.2/tests/ifc_roundtrip.rs +125 -0
- rvt-0.1.2/tests/python/conftest.py +6 -0
- rvt-0.1.2/tests/python/test_rvt.py +317 -0
- rvt-0.1.2/tests/samples.rs +205 -0
rvt-0.1.2/CHANGELOG.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes will be documented here. This project follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
|
5
|
+
[semver](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.2] — 2026-04-19
|
|
10
|
+
|
|
11
|
+
First tagged release since 0.1.0. Bundles the Python bindings,
|
|
12
|
+
document-level IFC4 export, Layer 5a ADocument walker, and the
|
|
13
|
+
spatial-hierarchy / classification extensions that land between
|
|
14
|
+
`v0.1.0` (initial public release) and the PyPI debut. Changelog
|
|
15
|
+
entries previously accumulated under `[Unreleased]` move here
|
|
16
|
+
verbatim.
|
|
17
|
+
|
|
18
|
+
### Changed — IFC exporter now emits the full spatial hierarchy
|
|
19
|
+
|
|
20
|
+
- **`rvt-ifc` output now includes `IfcSite → IfcBuilding → IfcBuildingStorey`**
|
|
21
|
+
with `IfcLocalPlacement` per container and `IfcRelAggregates`
|
|
22
|
+
binding each level to its parent. Previous output was a valid-but-
|
|
23
|
+
empty `IfcProject`; BlenderBIM and IfcOpenShell-based viewers
|
|
24
|
+
accepted it but couldn't render anything because there was no
|
|
25
|
+
spatial structure for them to attach geometry to. The minimal
|
|
26
|
+
`Default Site / Default Building / Level 1` hierarchy now opens as
|
|
27
|
+
a navigable scene directly. Once the walker surfaces real
|
|
28
|
+
`BasePoint` / `Level` / `Building` records from the Revit file,
|
|
29
|
+
these placeholder names and the zero-elevation storey will be
|
|
30
|
+
replaced with the actual values.
|
|
31
|
+
- **`make_guid(index)` deterministic GUID generator** — replaces the
|
|
32
|
+
constant `random_guid_stub()` placeholder. Emits 22-character
|
|
33
|
+
strings in the IFC-GUID alphabet (`0-9A-Za-z_$`), prefix `0rvtrs`
|
|
34
|
+
+ base-64 big-endian-encoded entity index. Every entity in one
|
|
35
|
+
export now has a distinct GUID; identical models produce
|
|
36
|
+
byte-identical STEP output (STEP text diffs now work).
|
|
37
|
+
- **`IfcClassification` + `IfcClassificationReference` +
|
|
38
|
+
`IfcRelAssociatesClassification` emission.** `RvtDocExporter`
|
|
39
|
+
already extracted OmniClass codes from PartAtom (e.g.
|
|
40
|
+
`23.45.12.34`) into `model.classifications`; the STEP writer now
|
|
41
|
+
actually emits them. Each classification source (OmniClass,
|
|
42
|
+
Uniformat, …) gets one `IfcClassification`; each coded item gets
|
|
43
|
+
an `IfcClassificationReference` linked back to its source; the
|
|
44
|
+
project gets one `IfcRelAssociatesClassification` per reference
|
|
45
|
+
binding the code to the root `IfcProject`. BIM consumers that
|
|
46
|
+
track code/category provenance (Solibri, IfcOpenShell
|
|
47
|
+
classification viewer) can now read those codes directly from
|
|
48
|
+
the exported IFC.
|
|
49
|
+
- 7 new unit tests total pinning spatial-hierarchy presence,
|
|
50
|
+
entity counts, GUID alphabet, GUID determinism, per-file GUID
|
|
51
|
+
uniqueness, OmniClass classification emission with items + names
|
|
52
|
+
+ edition, and a guard that empty classifications produce no
|
|
53
|
+
classification entities. Existing `ifc_roundtrip` integration
|
|
54
|
+
tests continue to pass across the 11-release corpus.
|
|
55
|
+
|
|
56
|
+
### Added — Python bindings via pyo3 + maturin
|
|
57
|
+
|
|
58
|
+
- **`rvt` Python package** — `pip install rvt` produces a single wheel
|
|
59
|
+
per OS/arch that works on every Python ≥ 3.8 (via pyo3 `abi3-py38`).
|
|
60
|
+
Pure-Python `rvt` package wraps the compiled `rvt._rvt` extension
|
|
61
|
+
and ships a PEP 561 `py.typed` marker + hand-maintained
|
|
62
|
+
`__init__.pyi` stubs so mypy, pyright, and IDE autocomplete work
|
|
63
|
+
out of the box.
|
|
64
|
+
- **`rvt.RevitFile` class** — Python surface onto `RustRevitFile`.
|
|
65
|
+
Properties: `version`, `original_path`, `build`, `guid`,
|
|
66
|
+
`part_atom_title`. Methods: `stream_names()`,
|
|
67
|
+
`missing_required_streams()`, `schema_summary()`,
|
|
68
|
+
`read_adocument()` (returns a dict with the walker's
|
|
69
|
+
`ADocumentInstance` serialised to native Python types), and
|
|
70
|
+
`write_ifc()` (returns the IFC4 STEP text).
|
|
71
|
+
- **`rvt.rvt_to_ifc(path)`** one-shot helper — equivalent to
|
|
72
|
+
`RevitFile(path).write_ifc()` for callers that just want the IFC
|
|
73
|
+
string and never touch the intermediate object.
|
|
74
|
+
- **`RevitFile.schema_json()`** — returns the full schema as a JSON
|
|
75
|
+
string (parse with `json.loads` to get a dict equivalent to
|
|
76
|
+
Rust's `SchemaTable`). Zero-copy relative to the decoded schema;
|
|
77
|
+
~1-2 MB per typical Revit family. `schema_summary()` remains the
|
|
78
|
+
cheap counts-only variant. Two new pytest tests cross-check that
|
|
79
|
+
summary counts match `schema_json()`'s full-parse counts and that
|
|
80
|
+
the `ADocument` class (the walker's target) is always present.
|
|
81
|
+
- **`RevitFile.basic_file_info_json()`** — `BasicFileInfo` as JSON
|
|
82
|
+
in one call. Single-call equivalent of the four individual
|
|
83
|
+
getters (`version` / `original_path` / `build` / `guid`) plus
|
|
84
|
+
any future fields. Returns `None` when the stream is unparseable.
|
|
85
|
+
- **`RevitFile.part_atom_json()`** — `PartAtom` as JSON in one
|
|
86
|
+
call. Superset of `part_atom_title` — also carries `id`,
|
|
87
|
+
`updated`, `taxonomies`, `categories`, `omniclass`, and `raw_xml`
|
|
88
|
+
(the original XML for lossless downstream reuse). Returns `None`
|
|
89
|
+
when the stream is absent (common on project `.rvt` files).
|
|
90
|
+
- Two new pytest tests pin `basic_file_info_json` ↔ individual
|
|
91
|
+
getters agreement, and `part_atom_json` ↔ `part_atom_title`
|
|
92
|
+
agreement plus presence of the structural keys.
|
|
93
|
+
- **`RevitFile.read_stream(name)`** — return the raw bytes of an
|
|
94
|
+
OLE stream by name as a Python `bytes` object. Accepts either
|
|
95
|
+
path form (`"/Formats/Latest"` or `"Formats/Latest"`). Raises
|
|
96
|
+
`IOError` for unknown streams. Use `stream_names()` to enumerate
|
|
97
|
+
what's available. Opens up forensic-inspection use cases the
|
|
98
|
+
announcement draft calls out (reading raw bytes without the
|
|
99
|
+
Rust-API dependency). Three new pytest tests pin bytes
|
|
100
|
+
round-trip, path-normalisation equivalence, and
|
|
101
|
+
missing-stream-raises semantics.
|
|
102
|
+
- **CI wheel build matrix** (`.github/workflows/ci.yml` `python-wheel`
|
|
103
|
+
job) — `PyO3/maturin-action@v1` builds a release wheel on Ubuntu,
|
|
104
|
+
macOS, and Windows runners, installs it into the runner's Python,
|
|
105
|
+
runs the pytest integration suite (`tests/python/test_rvt.py`), and
|
|
106
|
+
uploads the wheel as a workflow artifact. Any regression in the
|
|
107
|
+
Python surface fails CI across all three OSes.
|
|
108
|
+
- **38 pytest integration tests** covering module surface, error
|
|
109
|
+
handling on missing / non-CFB files, happy-path reads against every
|
|
110
|
+
one of the 11 corpus releases (2016–2026), cross-version
|
|
111
|
+
`read_adocument` consistency-band checks, and `write_ifc` output
|
|
112
|
+
shape. Gracefully skips with a clear message when
|
|
113
|
+
`_corpus/rac_basic_sample_family` is absent so local runs work
|
|
114
|
+
without LFS fetches.
|
|
115
|
+
- **`docs/python.md`** — full Python API reference (install, quick
|
|
116
|
+
start, tables per method, return shapes, error handling,
|
|
117
|
+
limitations, troubleshooting, contribution notes).
|
|
118
|
+
- **`docs/rvt-python-quickstart.ipynb`** — 15-cell Jupyter notebook
|
|
119
|
+
mirror of `docs/python.md` for anyone who prefers an interactive
|
|
120
|
+
walkthrough.
|
|
121
|
+
- **`.github/workflows/publish.yml`** — PyPI release workflow. Fires
|
|
122
|
+
on tag push (`v*`) or `workflow_dispatch`. Builds wheels on
|
|
123
|
+
Ubuntu / macOS / Windows via `PyO3/maturin-action@v1`, builds the
|
|
124
|
+
sdist on Ubuntu, downloads every artifact into one `dist/`, and
|
|
125
|
+
publishes via `pypa/gh-action-pypi-publish` using PyPI's Trusted
|
|
126
|
+
Publisher flow (OIDC) — no `PYPI_API_TOKEN` secret stored in the
|
|
127
|
+
repo. Supports `workflow_dispatch` with `test-pypi: true` for
|
|
128
|
+
TestPyPI dry runs. Per-tag releases will cover every Python ≥ 3.8
|
|
129
|
+
on mainstream OSes with one wheel each.
|
|
130
|
+
|
|
131
|
+
Design principle: expose only the stable high-level surface
|
|
132
|
+
(metadata, walker-read ADocument, IFC export). The low-level
|
|
133
|
+
byte-pattern / `FieldType` machinery stays in Rust; Python callers
|
|
134
|
+
get dicts and strings, no wrapper types to learn. To rebuild the
|
|
135
|
+
wheel locally: `maturin build --release --features python`.
|
|
136
|
+
|
|
137
|
+
### Added — Layer 5: first end-to-end `rvt → ifc` pipeline
|
|
138
|
+
|
|
139
|
+
- **`rvt::ifc::step_writer::write_step`** — pure-Rust IFC4 STEP
|
|
140
|
+
serializer. Takes an `IfcModel`, produces spec-valid ISO-10303-21
|
|
141
|
+
text with all required framework entities (IfcPerson,
|
|
142
|
+
IfcOrganization, IfcApplication, IfcOwnerHistory, IfcSIUnit×4,
|
|
143
|
+
IfcUnitAssignment, IfcGeometricRepresentationContext, IfcProject).
|
|
144
|
+
No IfcOpenShell dependency. No `unsafe`. 4 new unit tests pinning
|
|
145
|
+
envelope shape, escaping, and required entities.
|
|
146
|
+
- **`rvt::ifc::RvtDocExporter`** — concrete `Exporter` that
|
|
147
|
+
populates `IfcModel` from a `RevitFile`. Extracts project name
|
|
148
|
+
from PartAtom (falls back to BasicFileInfo path), builds a
|
|
149
|
+
description string from version + id, pulls OmniClass codes into
|
|
150
|
+
`ClassificationSource::OmniClass`.
|
|
151
|
+
- **`rvt-ifc` CLI** — ninth shipped binary. `rvt-ifc input.rfa`
|
|
152
|
+
writes `input.ifc` next to the input. `rvt-ifc -o path input.rfa`
|
|
153
|
+
overrides. `--null` uses the empty-project exporter for
|
|
154
|
+
STEP-writer testing.
|
|
155
|
+
|
|
156
|
+
First end-user deliverable for Layer 5: `cargo run --release --bin
|
|
157
|
+
rvt-ifc -- sample.rfa` produces a ~1 KB IFC4 file that
|
|
158
|
+
IfcOpenShell, BlenderBIM, and buildingSMART validators can read.
|
|
159
|
+
Geometry and per-element entities are pending walker expansion;
|
|
160
|
+
this v1 covers document-level metadata.
|
|
161
|
+
|
|
162
|
+
### Fixed
|
|
163
|
+
- **Windows CFB stream-name path separator.** `RevitFile::stream_names()`
|
|
164
|
+
returned backslash-separated paths on Windows (`Formats\Latest`)
|
|
165
|
+
because `Path::display()` uses host-native separators. Now
|
|
166
|
+
normalises to forward-slashes across all OSes so
|
|
167
|
+
`has_revit_signature()` and equivalent cross-stream comparisons
|
|
168
|
+
work uniformly. This was the root cause of the Windows-only
|
|
169
|
+
integration-test failures on the 2016 sample.
|
|
170
|
+
- **MSRV compliance.** Removed a `if let ... && ...` let-chain that
|
|
171
|
+
crept in; let-chains require Rust 1.88+ and the crate's MSRV is
|
|
172
|
+
1.85. Rewrote as nested `if let { if cond { ... } }`.
|
|
173
|
+
|
|
174
|
+
### Added — Layer 5a walker + rvt-doc CLI
|
|
175
|
+
|
|
176
|
+
- **`src/walker.rs` module** — first end-to-end schema-directed
|
|
177
|
+
instance reader. Exposes `read_adocument(&mut RevitFile) ->
|
|
178
|
+
Result<Option<ADocumentInstance>>` returning `ADocumentInstance {
|
|
179
|
+
entry_offset, version, fields }` where each field is one of
|
|
180
|
+
`InstanceField::{Pointer, ElementId, RefContainer, Bytes}`.
|
|
181
|
+
- **`rvt-doc` CLI** — eighth shipped binary. Dumps ADocument's
|
|
182
|
+
instance fields as human-readable text or machine-readable JSON
|
|
183
|
+
with `--json`. Respects `--redact` for user-path scrubbing.
|
|
184
|
+
- **Cross-version detection** — hybrid entry-point finder that
|
|
185
|
+
combines a sequential-id-table heuristic with a scoring-based
|
|
186
|
+
brute-force fallback. Works on all 11 releases (Revit 2016–2026)
|
|
187
|
+
with five cross-version-consistent bands:
|
|
188
|
+
2016–17 / 2018 (solo) / 2019–20 / 2021–23 / 2024–26.
|
|
189
|
+
- **`RevitFile::missing_required_streams()`** — diagnostic form of
|
|
190
|
+
`has_revit_signature`. Returns the list of required stream names
|
|
191
|
+
not found in the file, so "signature invalid" errors can point
|
|
192
|
+
at the specific missing stream.
|
|
193
|
+
|
|
194
|
+
### Research progress
|
|
195
|
+
|
|
196
|
+
- **Q6.3**: refuted Q6.2's "post-history bytes are ADocument"
|
|
197
|
+
hypothesis. The 131-record table at the post-history boundary
|
|
198
|
+
is a multi-table directory, not ADocument's instance.
|
|
199
|
+
- **Q6.4**: directory u16 body values are not cross-stream
|
|
200
|
+
references. Two sequential-id tables (Table A + Table B) exist
|
|
201
|
+
in Global/Latest.
|
|
202
|
+
- **Q6.5-A/B**: post-Table-B region at 0x0f67 (2024) is where
|
|
203
|
+
ADocument's actual instance data lives. 33× class-tag density
|
|
204
|
+
vs uniform-random baseline.
|
|
205
|
+
- **Q6.5-C**: first-pass walker drifts after field 2 because
|
|
206
|
+
Container wire encoding was wrong.
|
|
207
|
+
- **Q6.5-D**: Container wire is two-column `[u32 count][12 × 6B
|
|
208
|
+
ids][u32 count][12 × 6B masks]` = 152 bytes for count=12.
|
|
209
|
+
- **Q6.5-E**: walker reads 8/13 fields cleanly on Revit 2024.
|
|
210
|
+
- **Q6.5-F**: walker reads ADocument on all 11 releases with
|
|
211
|
+
cross-version-byte-identical output within each version band.
|
|
212
|
+
|
|
213
|
+
## [0.1.1] — 2026-04-19
|
|
214
|
+
|
|
215
|
+
### Added
|
|
216
|
+
- **CI-enforced 100% schema-field classification.** New integration
|
|
217
|
+
test `tests/field_type_coverage.rs` opens every file in the 11-version
|
|
218
|
+
`rac_basic_sample_family` corpus, parses the schema, and asserts zero
|
|
219
|
+
fields decode to `FieldType::Unknown`. Fails if any release regresses
|
|
220
|
+
or if the corpus is incomplete — no silent-skip. CI job fetches the
|
|
221
|
+
corpus from [phi-ag/rvt](https://github.com/phi-ag/rvt) at build time
|
|
222
|
+
via `actions/checkout@v4` with LFS (rvt-rs does not redistribute the
|
|
223
|
+
Autodesk-owned sample files; see SECURITY.md).
|
|
224
|
+
- `FieldType` enum with 8 variants (`Primitive`, `String`, `Guid`,
|
|
225
|
+
`ElementId`, `ElementIdRef`, `Pointer`, `Vector`, `Container`) —
|
|
226
|
+
classifies **100.00% of all 13,570 schema fields** across the 11-version
|
|
227
|
+
reference corpus (Revit 2016–2026). Zero fields decode to `Unknown`.
|
|
228
|
+
Evidence: `examples/unknown_bytes_deep.rs` against every sample file.
|
|
229
|
+
- `ClassEntry.tag`, `.parent`, `.ancestor_tag`, `.declared_field_count`,
|
|
230
|
+
`.was_parent_only` — richer schema metadata with cross-release stability.
|
|
231
|
+
- `writer::write_with_patches` + `StreamPatch` / `StreamFraming` types —
|
|
232
|
+
stream-level modifying writer; verified end-to-end round-trip on
|
|
233
|
+
`Formats/Latest`.
|
|
234
|
+
- `compression::truncated_gzip_encode` + `truncated_gzip_encode_with_prefix8`
|
|
235
|
+
— inverse of `inflate_at`, producing Revit-compatible gzip bytes.
|
|
236
|
+
- `redact` module with `redact_path_str` + `redact_sensitive` —
|
|
237
|
+
shared PII scrubber used by every CLI's `--redact` flag.
|
|
238
|
+
- `rvt-analyze` CLI — one-shot forensic analysis. 7 subsystems: identity,
|
|
239
|
+
history, format anchors, schema, schema→data link, content metadata,
|
|
240
|
+
disclosure scan. `--json`, `--section`, `--redact`, `--quiet`,
|
|
241
|
+
`--no-color`.
|
|
242
|
+
- `rvt-info --redact` and `rvt-history --redact` — PII propagation to the
|
|
243
|
+
other shipped CLIs.
|
|
244
|
+
- `elem_table` + `partitions` modules — Global/ElemTable + Partitions/NN
|
|
245
|
+
header parsers.
|
|
246
|
+
- `ifc` module — Layer 5 scaffold: `IfcModel`, `Exporter` trait,
|
|
247
|
+
`NullExporter`, full Revit-class → IFC-entity mapping plan.
|
|
248
|
+
- `writer::copy_file` — byte-preserving OLE round-trip (13 streams
|
|
249
|
+
identical, verified).
|
|
250
|
+
- 14 new reproducible probes under `examples/` covering every FACT in
|
|
251
|
+
the reconnaissance report.
|
|
252
|
+
- `tools/bench.sh` hyperfine benchmark harness + `docs/benchmarks.md`.
|
|
253
|
+
- First publicly-available RVT tag-drift table — `docs/data/tag-drift-2016-2026.csv`
|
|
254
|
+
(122 classes × 11 releases) + `tag-drift-heatmap.svg`.
|
|
255
|
+
- First publicly-documented Revit format-identifier GUID
|
|
256
|
+
(`3529342d-e51e-11d4-92d8-0000863f27ad`) — stable across every Revit
|
|
257
|
+
release 2016-2026.
|
|
258
|
+
|
|
259
|
+
### Changed
|
|
260
|
+
- Library surface reorganised; `src/lib.rs` has a proper crate-level
|
|
261
|
+
doc with a quickstart example, moat-layer table, and module inventory.
|
|
262
|
+
- `FieldType::Primitive` now carries `{kind, size}` instead of
|
|
263
|
+
`{size_hint}`.
|
|
264
|
+
- `FieldType::Container` now carries a `kind: u8` field marking the
|
|
265
|
+
element base type (so `Container<u32>` is distinguishable from
|
|
266
|
+
`Container<f64>` / `Container<ref>`). Existing consumers that
|
|
267
|
+
destructure with `..` continue to work.
|
|
268
|
+
- `FieldType::decode` is now panic-safe on short inputs: 0/1/2/3-byte
|
|
269
|
+
slices produce either `Unknown` or a typed variant with an empty body
|
|
270
|
+
rather than a bounds-check panic.
|
|
271
|
+
- `scan_fields_until_next_class_bounded` respects `declared_field_count`
|
|
272
|
+
— fixes the over-reader that bled from HostObjAttr into Symbol's
|
|
273
|
+
fields.
|
|
274
|
+
|
|
275
|
+
### Research findings (Phase 4c)
|
|
276
|
+
|
|
277
|
+
- **Q4**: The u16 "flag" in each tagged-class preamble is an
|
|
278
|
+
**ancestor-class reference**, not a bitmask. 9/9 non-zero values in
|
|
279
|
+
the 2024 sample resolve to named classes in the same schema.
|
|
280
|
+
- **Q5**: Decoded the field `type_encoding` byte sequence. 9 category
|
|
281
|
+
discriminators + sub-type variants.
|
|
282
|
+
- **Q5.1**: Extended to 84% coverage — wider primitive discriminators
|
|
283
|
+
(`0x01 bool`, `0x02 u16`, `0x05 u32`, `0x06 f32`, `0x07 f64`,
|
|
284
|
+
`0x08 string`, `0x09 GUID`, `0x0b u64`).
|
|
285
|
+
- **Q5.2**: Extended to **100.00%** coverage across the 11-version
|
|
286
|
+
corpus. Generalized `{scalar_base} 0x0010 ...` → `Vector<base>` and
|
|
287
|
+
`{scalar_base} 0x0050 ...` → `Container<base>` for every scalar base
|
|
288
|
+
(previously only `0x07 0x10` and `0x0e 0x50` were mapped). Added the
|
|
289
|
+
`0x0d` point/transform base (seen only in composite form), the
|
|
290
|
+
`0x08 0x60 ...` alternate string encoding, the `ElementIdRef { tag,
|
|
291
|
+
sub }` variant (for references that carry a specific referenced-class
|
|
292
|
+
tag — 80+ fields per release use this), the deprecated `0x03` i32-
|
|
293
|
+
alias (2016–2018 only, 5 fields), and robust handling of truncated
|
|
294
|
+
2-byte `{kind}{modifier}` headers (schema-parse boundary artifacts).
|
|
295
|
+
- **Q6**: `Global/Latest` is **not** an index + heap. It's a flat
|
|
296
|
+
TLV stream.
|
|
297
|
+
- **Q6.1**: Instance data is **schema-directed** (tag-less, protobuf-
|
|
298
|
+
style). Decoding requires schema-first sequential walk from a known
|
|
299
|
+
entry point.
|
|
300
|
+
- **Q6.2**: Initial hypothesis — entry point located at offset `0x363`
|
|
301
|
+
in the 2024 sample (right after the document-upgrade-history
|
|
302
|
+
UTF-16LE block). Confidence 0.6. **Refuted by Q6.3.**
|
|
303
|
+
- **Q6.3 CORRECTION**: The Q6.2 entry-point hypothesis is refuted by
|
|
304
|
+
rigorous validation against the 11-version corpus. The bytes at the
|
|
305
|
+
post-history boundary are NOT ADocument's 13-field instance — they
|
|
306
|
+
are a multi-table directory / reference-pool with ~131 sequentially
|
|
307
|
+
numbered records per release (stable count across all 11 years,
|
|
308
|
+
unchanged from the 13 that would be expected if this were
|
|
309
|
+
ADocument). Body-size does not correlate with FieldType; body u16
|
|
310
|
+
values do not resolve to schema class tags (0/131 hit). ADocument's
|
|
311
|
+
actual location in `Global/Latest` (or another stream) is not yet
|
|
312
|
+
known — decoding the directory table format is the next open
|
|
313
|
+
research question (Q6.4+). Probes: `examples/adocument_walk.rs`,
|
|
314
|
+
`examples/post_directory.rs`, `examples/directory_class_lookup.rs`.
|
|
315
|
+
See `docs/rvt-moat-break-reconnaissance.md` §Q6.3 for full evidence.
|
|
316
|
+
- **Q7**: `Partitions/NN` trailer u32 fields are **not** per-chunk
|
|
317
|
+
offsets. Gzip-magic scan remains correct.
|
|
318
|
+
|
|
319
|
+
## [0.1.0] — 2026-04-19
|
|
320
|
+
|
|
321
|
+
Initial public release.
|
|
322
|
+
|
|
323
|
+
- OLE2/MS-CFB container reader (via `cfb`) — Layer 1.
|
|
324
|
+
- Truncated-gzip decompression (via `flate2`) — Layer 2.
|
|
325
|
+
- Per-stream framing for `Formats/Latest`, `Global/Latest`,
|
|
326
|
+
`Global/ElemTable`, `Partitions/NN`, `Contents`, `PartitionTable`,
|
|
327
|
+
`RevitPreview4.0` — Layer 3.
|
|
328
|
+
- Schema table parser: class names + fields + tags + parent classes
|
|
329
|
+
+ declared field counts + cross-release tag-drift map — Layer 4a.
|
|
330
|
+
- Phase D moat proof: class tags from `Formats/Latest` occur in
|
|
331
|
+
`Global/Latest` at ~340× uniform-random rate — Layer 4b.
|
|
332
|
+
- `FieldType` enum with 7 initial variants (Primitive, ElementId,
|
|
333
|
+
Pointer, Vector, Container, String, Guid). **84% field-type
|
|
334
|
+
classification** on a typical Revit 2024 sample family — Layer 4c.
|
|
335
|
+
- Stream-level modifying writer (`write_with_patches`) with
|
|
336
|
+
byte-preserving round-trips verified on all 13 streams — Layer 6.
|
|
337
|
+
- Seven shipped CLIs: `rvt-analyze`, `rvt-info`, `rvt-schema`,
|
|
338
|
+
`rvt-history`, `rvt-diff`, `rvt-corpus`, `rvt-dump`.
|
|
339
|
+
- Full PII-redaction (`--redact`) across every CLI.
|
|
340
|
+
- First publicly-documented Revit format-identifier GUID
|
|
341
|
+
(`3529342d-e51e-11d4-92d8-0000863f27ad`), stable across every
|
|
342
|
+
Revit release 2016–2026.
|
|
343
|
+
- First public RVT tag-drift table: 122 classes × 11 releases CSV
|
|
344
|
+
plus SVG heatmap.
|
rvt-0.1.2/CITATION.cff
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use rvt-rs in academic or archival work, please cite it using this metadata."
|
|
3
|
+
title: "rvt-rs: Clean-room Rust reader for Autodesk Revit files"
|
|
4
|
+
type: software
|
|
5
|
+
version: 0.1.0
|
|
6
|
+
date-released: "2026-04-19"
|
|
7
|
+
license: Apache-2.0
|
|
8
|
+
repository-code: "https://github.com/DrunkOnJava/rvt-rs"
|
|
9
|
+
url: "https://github.com/DrunkOnJava/rvt-rs"
|
|
10
|
+
abstract: >-
|
|
11
|
+
rvt-rs is an Apache-2-licensed Rust library and CLI for reading
|
|
12
|
+
Autodesk Revit files (.rvt, .rfa, .rte, .rft) without any dependency
|
|
13
|
+
on Autodesk's SDK or a Revit installation. It is the first
|
|
14
|
+
open-source tool to enumerate the Formats/Latest class schema
|
|
15
|
+
inventory and has been verified against 11 Revit releases spanning
|
|
16
|
+
2016 through 2026.
|
|
17
|
+
keywords:
|
|
18
|
+
- revit
|
|
19
|
+
- rvt
|
|
20
|
+
- rfa
|
|
21
|
+
- bim
|
|
22
|
+
- autodesk
|
|
23
|
+
- ifc
|
|
24
|
+
- file-format
|
|
25
|
+
- parser
|
|
26
|
+
- rust
|
|
27
|
+
- interoperability
|
|
28
|
+
- reverse-engineering
|
|
29
|
+
authors:
|
|
30
|
+
- family-names: Long
|
|
31
|
+
given-names: Griffin
|
|
32
|
+
alias: DrunkOnJava
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
We as members, contributors, and leaders pledge to make participation in
|
|
6
|
+
our community a harassment-free experience for everyone, regardless of
|
|
7
|
+
age, body size, visible or invisible disability, ethnicity, sex
|
|
8
|
+
characteristics, gender identity and expression, level of experience,
|
|
9
|
+
education, socio-economic status, nationality, personal appearance, race,
|
|
10
|
+
caste, color, religion, or sexual identity and orientation.
|
|
11
|
+
|
|
12
|
+
We pledge to act and interact in ways that contribute to an open,
|
|
13
|
+
welcoming, diverse, inclusive, and healthy community.
|
|
14
|
+
|
|
15
|
+
## Our Standards
|
|
16
|
+
|
|
17
|
+
Examples of behavior that contributes to a positive environment for our
|
|
18
|
+
community include:
|
|
19
|
+
|
|
20
|
+
* Demonstrating empathy and kindness toward other people
|
|
21
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
|
22
|
+
* Giving and gracefully accepting constructive feedback
|
|
23
|
+
* Accepting responsibility and apologizing to those affected by our
|
|
24
|
+
mistakes, and learning from the experience
|
|
25
|
+
* Focusing on what is best not just for us as individuals, but for the
|
|
26
|
+
overall community
|
|
27
|
+
|
|
28
|
+
Examples of unacceptable behavior include:
|
|
29
|
+
|
|
30
|
+
* The use of sexualized language or imagery, and sexual attention or
|
|
31
|
+
advances of any kind
|
|
32
|
+
* Trolling, insulting or derogatory comments, and personal or political
|
|
33
|
+
attacks
|
|
34
|
+
* Public or private harassment
|
|
35
|
+
* Publishing others' private information, such as a physical or email
|
|
36
|
+
address, without their explicit permission
|
|
37
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
38
|
+
professional setting
|
|
39
|
+
|
|
40
|
+
## Enforcement Responsibilities
|
|
41
|
+
|
|
42
|
+
The project maintainer is responsible for clarifying and enforcing our
|
|
43
|
+
standards of acceptable behavior and will take appropriate and fair
|
|
44
|
+
corrective action in response to any behavior that they deem
|
|
45
|
+
inappropriate, threatening, offensive, or harmful.
|
|
46
|
+
|
|
47
|
+
## Scope
|
|
48
|
+
|
|
49
|
+
This Code of Conduct applies within all community spaces (GitHub issues,
|
|
50
|
+
pull requests, discussions, and any other project-affiliated channels),
|
|
51
|
+
and also applies when an individual is officially representing the
|
|
52
|
+
community in public spaces.
|
|
53
|
+
|
|
54
|
+
## Enforcement
|
|
55
|
+
|
|
56
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may
|
|
57
|
+
be reported to the maintainer responsible for enforcement at
|
|
58
|
+
<151978260+DrunkOnJava@users.noreply.github.com>. All complaints will be reviewed and
|
|
59
|
+
investigated promptly and fairly.
|
|
60
|
+
|
|
61
|
+
## Attribution
|
|
62
|
+
|
|
63
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
64
|
+
version 2.1, available at
|
|
65
|
+
<https://www.contributor-covenant.org/version/2/1/code_of_conduct.html>.
|
|
66
|
+
|
|
67
|
+
[homepage]: https://www.contributor-covenant.org
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Contributing to rvt-rs
|
|
2
|
+
|
|
3
|
+
Thanks for your interest. This project is small and evolving
|
|
4
|
+
quickly, so contribution guidelines are intentionally light — but
|
|
5
|
+
a few practices keep the repo healthy.
|
|
6
|
+
|
|
7
|
+
## What's welcome
|
|
8
|
+
|
|
9
|
+
- **Bug reports** with a minimal reproducer (the smallest `.rfa`
|
|
10
|
+
or `.rvt` that triggers the issue). Security-sensitive reports
|
|
11
|
+
go through [`SECURITY.md`](SECURITY.md), not public issues.
|
|
12
|
+
- **Performance regressions** caught by the benchmark harness in
|
|
13
|
+
`tools/bench.sh` — open an issue with a before/after table.
|
|
14
|
+
- **New FACTs** about the file format. The reconnaissance report in
|
|
15
|
+
`docs/rvt-moat-break-reconnaissance.md` is the canonical place
|
|
16
|
+
for dated findings. Please mirror any new finding there AND as a
|
|
17
|
+
reproducible probe under `examples/`.
|
|
18
|
+
- **Documentation improvements.** The README and inline doc comments
|
|
19
|
+
are fair game.
|
|
20
|
+
- **Tests.** More coverage is always welcome, especially for
|
|
21
|
+
edge-case file layouts.
|
|
22
|
+
|
|
23
|
+
## Where help is most wanted (as of v0.1.1)
|
|
24
|
+
|
|
25
|
+
The schema layer (Layer 4) is complete and regression-proofed: 100%
|
|
26
|
+
field-type classification across every Revit release 2016–2026,
|
|
27
|
+
enforced by CI. The instance layer (Layer 5) is wide open, with
|
|
28
|
+
three stacked sub-questions any of which is a genuine contribution:
|
|
29
|
+
|
|
30
|
+
1. **§Q6.4 — decode the `Global/Latest` directory-table format.**
|
|
31
|
+
Right after the document-upgrade-history block in
|
|
32
|
+
`Global/Latest`, there is a contiguous sequential-ID TLV table
|
|
33
|
+
of ~131 records (stable across all 11 releases). Record bodies
|
|
34
|
+
are 2, 6, 12, 14, or 16 bytes; u16 body values are not schema
|
|
35
|
+
class tags (0/131 hit — see `examples/directory_class_lookup.rs`).
|
|
36
|
+
Strong candidates: ElementId references, indices into
|
|
37
|
+
`Global/ElemTable`, or a reference-pool. Cross-reference against
|
|
38
|
+
`Global/ElemTable`'s records and/or test on a non-sample-family
|
|
39
|
+
`.rvt` (any real project) to see whether the count is
|
|
40
|
+
per-document or format-structural.
|
|
41
|
+
2. **§Q6.5 — locate ADocument's actual instance bytes.** Blocked on
|
|
42
|
+
Q6.4 — once we know what the directory indexes, we can follow
|
|
43
|
+
its entries to ADocument's real location (not the post-history
|
|
44
|
+
offset, as originally hypothesised and refuted in §Q6.3).
|
|
45
|
+
3. **Layer 5a — schema-directed walker.** Blocked on Q6.5. The
|
|
46
|
+
skeleton has been scoped; implementation is bounded once Q6.5
|
|
47
|
+
produces the entry-point offset.
|
|
48
|
+
|
|
49
|
+
Each step has clear validation oracles: rvt-info already extracts
|
|
50
|
+
the document title + GUID via a second path, rvt-history extracts
|
|
51
|
+
the upgrade timeline via Phase D string scanning. Anything the
|
|
52
|
+
walker pulls must cross-check.
|
|
53
|
+
|
|
54
|
+
See `docs/rvt-moat-break-reconnaissance.md` §Q6 for the full state
|
|
55
|
+
of play, including the documented refutation of the original Q6.2
|
|
56
|
+
entry-point hypothesis (this is the first project section a
|
|
57
|
+
contributor should read before starting on Layer 5).
|
|
58
|
+
|
|
59
|
+
## What needs discussion first
|
|
60
|
+
|
|
61
|
+
Open an issue (or a draft PR) before starting work on any of:
|
|
62
|
+
|
|
63
|
+
- **Layer 5 itself** — the questions above are open research; a
|
|
64
|
+
one-paragraph sketch of your approach in an issue saves everyone
|
|
65
|
+
time before you spend days on a probe.
|
|
66
|
+
- **IFC exporter emission** (`src/ifc/`). Mapping decisions have
|
|
67
|
+
to align with buildingSMART IFC schema conventions.
|
|
68
|
+
- **The modifying writer** (`src/writer::write_with_patches`). Any
|
|
69
|
+
change to Revit's truncated-gzip framing must be verified
|
|
70
|
+
against a round-trip test.
|
|
71
|
+
- **Layer 4c field-type decoder changes.** Coverage is at 100%
|
|
72
|
+
and CI-gated. If you think a pattern is misclassified, file an
|
|
73
|
+
issue with byte evidence from the corpus — do not silently
|
|
74
|
+
change the decoder.
|
|
75
|
+
|
|
76
|
+
## Coding conventions
|
|
77
|
+
|
|
78
|
+
- Rust 2024 edition.
|
|
79
|
+
- `cargo fmt` before every commit.
|
|
80
|
+
- `cargo test --release` must pass. The CI in `.github/workflows/`
|
|
81
|
+
enforces this.
|
|
82
|
+
- **No `unsafe` in the library crate.** If you genuinely need it,
|
|
83
|
+
open an issue first to discuss.
|
|
84
|
+
- **No panics in parsing paths.** Malformed input must return an
|
|
85
|
+
`Error`, never `panic!`.
|
|
86
|
+
- **No PII in tests.** Use synthetic fixtures — `testuser`,
|
|
87
|
+
`111111`, `FY-20XX`, etc. The redaction tests in
|
|
88
|
+
`src/redact.rs` are the canonical examples.
|
|
89
|
+
- **Every probe under `examples/`** gets a module-level doc
|
|
90
|
+
comment explaining *what FACT it proves* and *how to verify*
|
|
91
|
+
the result against the 11-version corpus.
|
|
92
|
+
|
|
93
|
+
## Commit messages
|
|
94
|
+
|
|
95
|
+
We use Conventional Commits:
|
|
96
|
+
|
|
97
|
+
- `feat(<scope>): ...` for new features
|
|
98
|
+
- `fix(<scope>): ...` for bug fixes
|
|
99
|
+
- `docs(<scope>): ...` for documentation
|
|
100
|
+
- `test(<scope>): ...` for test-only changes
|
|
101
|
+
- `refactor(<scope>): ...` for behavior-preserving internal changes
|
|
102
|
+
- `perf(<scope>): ...` for performance
|
|
103
|
+
- `chore(<scope>): ...` for infra / CI / build
|
|
104
|
+
|
|
105
|
+
Scopes that appear frequently: `formats`, `object_graph`,
|
|
106
|
+
`elem_table`, `partitions`, `writer`, `ifc`, `readme`, `cli`.
|
|
107
|
+
|
|
108
|
+
## Reverse-engineering findings
|
|
109
|
+
|
|
110
|
+
When you discover something new about the file format:
|
|
111
|
+
|
|
112
|
+
1. Write a short probe under `examples/<name>.rs` that reproduces
|
|
113
|
+
the finding from bytes. One self-contained file, runs against
|
|
114
|
+
the phi-ag/rvt sample corpus.
|
|
115
|
+
2. Add a dated addendum to `docs/rvt-moat-break-reconnaissance.md`
|
|
116
|
+
with an evidence table and a confidence value.
|
|
117
|
+
3. If the finding is a decoding rule, also add a unit test that
|
|
118
|
+
pins the byte pattern (see `FieldType::decode` tests in
|
|
119
|
+
`src/formats.rs` for the pattern).
|
|
120
|
+
|
|
121
|
+
This keeps every claim independently verifiable, which is the
|
|
122
|
+
whole point of open reverse-engineering work.
|
|
123
|
+
|
|
124
|
+
## Legal note for contributors
|
|
125
|
+
|
|
126
|
+
rvt-rs is Apache-2.0 licensed. By submitting a contribution, you
|
|
127
|
+
agree that your work is licensable under Apache-2.0 and that you
|
|
128
|
+
have the right to grant that license.
|
|
129
|
+
|
|
130
|
+
**Please do not submit any code, comments, tests, or documentation
|
|
131
|
+
that contains information derived from Autodesk proprietary
|
|
132
|
+
sources** (NDA'd SDKs, decompiled binaries beyond what the public
|
|
133
|
+
`RevitAPI.dll` symbol export trivially exposes, leaked internal
|
|
134
|
+
documents, etc.). This project operates strictly from public
|
|
135
|
+
on-disk byte observations.
|
|
136
|
+
|
|
137
|
+
Questions: open an issue or email <151978260+DrunkOnJava@users.noreply.github.com>.
|