ifcfast 0.1.0__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.
Files changed (35) hide show
  1. ifcfast-0.1.0/Cargo.lock +239 -0
  2. ifcfast-0.1.0/Cargo.toml +14 -0
  3. ifcfast-0.1.0/LICENSE +21 -0
  4. ifcfast-0.1.0/PKG-INFO +292 -0
  5. ifcfast-0.1.0/README.md +265 -0
  6. ifcfast-0.1.0/crates/core/Cargo.toml +36 -0
  7. ifcfast-0.1.0/crates/core/src/bin/bench.rs +93 -0
  8. ifcfast-0.1.0/crates/core/src/bin/mesh.rs +141 -0
  9. ifcfast-0.1.0/crates/core/src/entity_table.rs +106 -0
  10. ifcfast-0.1.0/crates/core/src/extractors/classifications.rs +175 -0
  11. ifcfast-0.1.0/crates/core/src/extractors/materials.rs +236 -0
  12. ifcfast-0.1.0/crates/core/src/extractors/mod.rs +20 -0
  13. ifcfast-0.1.0/crates/core/src/extractors/psets.rs +270 -0
  14. ifcfast-0.1.0/crates/core/src/extractors/quantities.rs +188 -0
  15. ifcfast-0.1.0/crates/core/src/indexer.rs +767 -0
  16. ifcfast-0.1.0/crates/core/src/lexer.rs +477 -0
  17. ifcfast-0.1.0/crates/core/src/lib.rs +506 -0
  18. ifcfast-0.1.0/crates/core/src/mesh/brep.rs +224 -0
  19. ifcfast-0.1.0/crates/core/src/mesh/extrusion.rs +222 -0
  20. ifcfast-0.1.0/crates/core/src/mesh/faceset.rs +229 -0
  21. ifcfast-0.1.0/crates/core/src/mesh/gltf.rs +349 -0
  22. ifcfast-0.1.0/crates/core/src/mesh/mapped.rs +208 -0
  23. ifcfast-0.1.0/crates/core/src/mesh/mod.rs +421 -0
  24. ifcfast-0.1.0/crates/core/src/mesh/obj.rs +47 -0
  25. ifcfast-0.1.0/crates/core/src/mesh/placement.rs +200 -0
  26. ifcfast-0.1.0/crates/core/src/mesh/profile.rs +480 -0
  27. ifcfast-0.1.0/crates/core/src/mesh/stats.rs +379 -0
  28. ifcfast-0.1.0/pyproject.toml +50 -0
  29. ifcfast-0.1.0/python/ifcfast/__init__.py +47 -0
  30. ifcfast-0.1.0/python/ifcfast/cache.py +436 -0
  31. ifcfast-0.1.0/python/ifcfast/classify.py +207 -0
  32. ifcfast-0.1.0/python/ifcfast/cli.py +127 -0
  33. ifcfast-0.1.0/python/ifcfast/federated_floors.py +378 -0
  34. ifcfast-0.1.0/python/ifcfast/header.py +207 -0
  35. ifcfast-0.1.0/python/ifcfast/model.py +400 -0
@@ -0,0 +1,239 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "autocfg"
7
+ version = "1.5.0"
8
+ source = "registry+https://github.com/rust-lang/crates.io-index"
9
+ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
10
+
11
+ [[package]]
12
+ name = "cfg-if"
13
+ version = "1.0.4"
14
+ source = "registry+https://github.com/rust-lang/crates.io-index"
15
+ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
16
+
17
+ [[package]]
18
+ name = "earcutr"
19
+ version = "0.4.3"
20
+ source = "registry+https://github.com/rust-lang/crates.io-index"
21
+ checksum = "79127ed59a85d7687c409e9978547cffb7dc79675355ed22da6b66fd5f6ead01"
22
+ dependencies = [
23
+ "itertools",
24
+ "num-traits",
25
+ ]
26
+
27
+ [[package]]
28
+ name = "either"
29
+ version = "1.15.0"
30
+ source = "registry+https://github.com/rust-lang/crates.io-index"
31
+ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
32
+
33
+ [[package]]
34
+ name = "glam"
35
+ version = "0.29.3"
36
+ source = "registry+https://github.com/rust-lang/crates.io-index"
37
+ checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
38
+
39
+ [[package]]
40
+ name = "heck"
41
+ version = "0.5.0"
42
+ source = "registry+https://github.com/rust-lang/crates.io-index"
43
+ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
44
+
45
+ [[package]]
46
+ name = "ifcfast-core"
47
+ version = "0.1.0"
48
+ dependencies = [
49
+ "earcutr",
50
+ "glam",
51
+ "memchr",
52
+ "memmap2",
53
+ "pyo3",
54
+ ]
55
+
56
+ [[package]]
57
+ name = "indoc"
58
+ version = "2.0.7"
59
+ source = "registry+https://github.com/rust-lang/crates.io-index"
60
+ checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706"
61
+ dependencies = [
62
+ "rustversion",
63
+ ]
64
+
65
+ [[package]]
66
+ name = "itertools"
67
+ version = "0.11.0"
68
+ source = "registry+https://github.com/rust-lang/crates.io-index"
69
+ checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
70
+ dependencies = [
71
+ "either",
72
+ ]
73
+
74
+ [[package]]
75
+ name = "libc"
76
+ version = "0.2.186"
77
+ source = "registry+https://github.com/rust-lang/crates.io-index"
78
+ checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
79
+
80
+ [[package]]
81
+ name = "memchr"
82
+ version = "2.8.0"
83
+ source = "registry+https://github.com/rust-lang/crates.io-index"
84
+ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
85
+
86
+ [[package]]
87
+ name = "memmap2"
88
+ version = "0.9.10"
89
+ source = "registry+https://github.com/rust-lang/crates.io-index"
90
+ checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
91
+ dependencies = [
92
+ "libc",
93
+ ]
94
+
95
+ [[package]]
96
+ name = "memoffset"
97
+ version = "0.9.1"
98
+ source = "registry+https://github.com/rust-lang/crates.io-index"
99
+ checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
100
+ dependencies = [
101
+ "autocfg",
102
+ ]
103
+
104
+ [[package]]
105
+ name = "num-traits"
106
+ version = "0.2.19"
107
+ source = "registry+https://github.com/rust-lang/crates.io-index"
108
+ checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
109
+ dependencies = [
110
+ "autocfg",
111
+ ]
112
+
113
+ [[package]]
114
+ name = "once_cell"
115
+ version = "1.21.4"
116
+ source = "registry+https://github.com/rust-lang/crates.io-index"
117
+ checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
118
+
119
+ [[package]]
120
+ name = "portable-atomic"
121
+ version = "1.13.1"
122
+ source = "registry+https://github.com/rust-lang/crates.io-index"
123
+ checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
124
+
125
+ [[package]]
126
+ name = "proc-macro2"
127
+ version = "1.0.106"
128
+ source = "registry+https://github.com/rust-lang/crates.io-index"
129
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
130
+ dependencies = [
131
+ "unicode-ident",
132
+ ]
133
+
134
+ [[package]]
135
+ name = "pyo3"
136
+ version = "0.22.6"
137
+ source = "registry+https://github.com/rust-lang/crates.io-index"
138
+ checksum = "f402062616ab18202ae8319da13fa4279883a2b8a9d9f83f20dbade813ce1884"
139
+ dependencies = [
140
+ "cfg-if",
141
+ "indoc",
142
+ "libc",
143
+ "memoffset",
144
+ "once_cell",
145
+ "portable-atomic",
146
+ "pyo3-build-config",
147
+ "pyo3-ffi",
148
+ "pyo3-macros",
149
+ "unindent",
150
+ ]
151
+
152
+ [[package]]
153
+ name = "pyo3-build-config"
154
+ version = "0.22.6"
155
+ source = "registry+https://github.com/rust-lang/crates.io-index"
156
+ checksum = "b14b5775b5ff446dd1056212d778012cbe8a0fbffd368029fd9e25b514479c38"
157
+ dependencies = [
158
+ "once_cell",
159
+ "target-lexicon",
160
+ ]
161
+
162
+ [[package]]
163
+ name = "pyo3-ffi"
164
+ version = "0.22.6"
165
+ source = "registry+https://github.com/rust-lang/crates.io-index"
166
+ checksum = "9ab5bcf04a2cdcbb50c7d6105de943f543f9ed92af55818fd17b660390fc8636"
167
+ dependencies = [
168
+ "libc",
169
+ "pyo3-build-config",
170
+ ]
171
+
172
+ [[package]]
173
+ name = "pyo3-macros"
174
+ version = "0.22.6"
175
+ source = "registry+https://github.com/rust-lang/crates.io-index"
176
+ checksum = "0fd24d897903a9e6d80b968368a34e1525aeb719d568dba8b3d4bfa5dc67d453"
177
+ dependencies = [
178
+ "proc-macro2",
179
+ "pyo3-macros-backend",
180
+ "quote",
181
+ "syn",
182
+ ]
183
+
184
+ [[package]]
185
+ name = "pyo3-macros-backend"
186
+ version = "0.22.6"
187
+ source = "registry+https://github.com/rust-lang/crates.io-index"
188
+ checksum = "36c011a03ba1e50152b4b394b479826cad97e7a21eb52df179cd91ac411cbfbe"
189
+ dependencies = [
190
+ "heck",
191
+ "proc-macro2",
192
+ "pyo3-build-config",
193
+ "quote",
194
+ "syn",
195
+ ]
196
+
197
+ [[package]]
198
+ name = "quote"
199
+ version = "1.0.45"
200
+ source = "registry+https://github.com/rust-lang/crates.io-index"
201
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
202
+ dependencies = [
203
+ "proc-macro2",
204
+ ]
205
+
206
+ [[package]]
207
+ name = "rustversion"
208
+ version = "1.0.22"
209
+ source = "registry+https://github.com/rust-lang/crates.io-index"
210
+ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
211
+
212
+ [[package]]
213
+ name = "syn"
214
+ version = "2.0.117"
215
+ source = "registry+https://github.com/rust-lang/crates.io-index"
216
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
217
+ dependencies = [
218
+ "proc-macro2",
219
+ "quote",
220
+ "unicode-ident",
221
+ ]
222
+
223
+ [[package]]
224
+ name = "target-lexicon"
225
+ version = "0.12.16"
226
+ source = "registry+https://github.com/rust-lang/crates.io-index"
227
+ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
228
+
229
+ [[package]]
230
+ name = "unicode-ident"
231
+ version = "1.0.24"
232
+ source = "registry+https://github.com/rust-lang/crates.io-index"
233
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
234
+
235
+ [[package]]
236
+ name = "unindent"
237
+ version = "0.2.4"
238
+ source = "registry+https://github.com/rust-lang/crates.io-index"
239
+ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
@@ -0,0 +1,14 @@
1
+ [workspace]
2
+ members = ["crates/core"]
3
+ resolver = "2"
4
+
5
+ [workspace.package]
6
+ version = "0.1.0"
7
+ edition = "2021"
8
+ license = "MIT"
9
+ repository = "https://github.com/EdvardGK/ifcfast"
10
+
11
+ [profile.release]
12
+ lto = "thin"
13
+ codegen-units = 1
14
+ opt-level = 3
ifcfast-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Edvard Granskogen Kjorstad
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
ifcfast-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,292 @@
1
+ Metadata-Version: 2.4
2
+ Name: ifcfast
3
+ Version: 0.1.0
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Rust
9
+ Classifier: Topic :: Scientific/Engineering
10
+ Requires-Dist: pandas>=2.0.0
11
+ Requires-Dist: pyarrow>=14.0.0
12
+ Requires-Dist: pyyaml>=6.0.0
13
+ Requires-Dist: pytest>=7.4.0 ; extra == 'dev'
14
+ Requires-Dist: openpyxl>=3.1.0 ; extra == 'dev'
15
+ Requires-Dist: ifcopenshell>=0.8.0 ; extra == 'dev'
16
+ Provides-Extra: dev
17
+ License-File: LICENSE
18
+ Summary: Fast native IFC parsing, data extraction, and geometric analytics
19
+ Keywords: IFC,BIM,QTO,parser,rust
20
+ Author-email: Edvard Granskogen Kjorstad <ed.subscript@gmail.com>
21
+ License: MIT
22
+ Requires-Python: >=3.10
23
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
24
+ Project-URL: Homepage, https://github.com/EdvardGK/ifcfast
25
+ Project-URL: Issues, https://github.com/EdvardGK/ifcfast/issues
26
+
27
+ # ifcfast
28
+
29
+ Fast native IFC parsing, data extraction and geometric analytics. Python
30
+ on top, Rust underneath, no `ifcopenshell.open()` on the hot path.
31
+
32
+ Aimed at the "open an IFC, get the data out, run QTO / quality
33
+ queries" workflow. Tier-1 parse is byte-identical to `ifcopenshell` and
34
+ ~20-27× faster on production files.
35
+
36
+ > `ifcfast` was extracted on 2026-05-13 from the
37
+ > [`EdvardGK/ifc-workbench`](https://github.com/EdvardGK/ifc-workbench)
38
+ > scratch repo. See [`docs/history/origin.md`](docs/history/origin.md)
39
+ > for the trail back and what was renamed.
40
+
41
+ ## What it gives you
42
+
43
+ | layer | format | typical latency on 200 MB IFC |
44
+ |---|---|---|
45
+ | Products (GUID, type, name, storey, parent, tag) | dict of parallel lists | tier-1 cold: 0.5–2 s |
46
+ | Property sets | long-format `pandas.DataFrame` | 137 ms |
47
+ | Element quantities | long-format `pandas.DataFrame` | 90 ms |
48
+ | Materials (incl. layer sets) | long-format `pandas.DataFrame` | 27 ms |
49
+ | Classifications | long-format `pandas.DataFrame` | 17 ms |
50
+ | All data layers (shared scan) | bundle | 1.3 s |
51
+ | Triangle meshes (extrusion / mapped / face sets / BREP) | OBJ / glTF / CSV | 2.6 s |
52
+ | Placement-vs-mesh drift report | `pandas.DataFrame` | 322 ms |
53
+ | Parquet cache (all of the above) | parquet | 65 ms hot reload |
54
+
55
+ End-to-end cold parse of a 200 MB IFC: under 5 s. Hot reload from cache:
56
+ under 100 ms. Memory peak: under 1 GB resident (mmap-based).
57
+
58
+ Audited at **234,144 products across 5 authoring tools** (Tekla,
59
+ Archicad, Revit IFC4, Revit IFC2X3, MagiCAD, BSProLib) with byte-level
60
+ parity vs `ifcopenshell`. See
61
+ [`docs/history/audit/`](docs/history/audit/).
62
+
63
+ ## Install
64
+
65
+ Needs Rust 1.95+ and Python 3.10+.
66
+
67
+ ```bash
68
+ pip install maturin
69
+ maturin develop --release # builds the Rust extension, ~30 s first time
70
+ ```
71
+
72
+ For production: `maturin build --release` produces a wheel.
73
+
74
+ ## Quick start
75
+
76
+ ```python
77
+ import ifcfast
78
+
79
+ m = ifcfast.open("model.ifc")
80
+ print(len(m), "products,", len(m.storeys), "storeys")
81
+ print(m.authoring_app, "→", m.schema)
82
+
83
+ walls = list(m.filter(entity="IfcWall"))
84
+
85
+ # Long-format data layers (pandas DataFrames, loaded lazily).
86
+ m.psets # 63k+ rows on a 200 MB Archicad file
87
+ m.quantities # author-supplied Qto_*BaseQuantities
88
+ m.materials # (guid, role, layer, name, thickness, category)
89
+ m.classifications # NS 3451 / Uniformat / OmniClass references
90
+ m.drift # placement-vs-mesh drift report
91
+
92
+ # Standard QTO query — external walls.
93
+ external_walls = m.psets[
94
+ (m.psets.pset_name == "Pset_WallCommon")
95
+ & (m.psets.prop_name == "IsExternal")
96
+ & (m.psets.value == "True")
97
+ ].guid.unique()
98
+
99
+ # Quality gate — placement bugs.
100
+ suspect = m.drift[m.drift.drift_severity == "error"]
101
+ ```
102
+
103
+ The same model can be re-opened cheaply — the second `ifcfast.open(...)`
104
+ returns from the parquet cache in tens of milliseconds.
105
+
106
+ ## CLI
107
+
108
+ ```bash
109
+ ifcfast index model.ifc # tier-1 parse + counts
110
+ ifcfast extract model.ifc # extract data layers (writes cache)
111
+ ifcfast drift model.ifc --top 20 # placement / mesh drift report
112
+ ifcfast cache model.ifc # inspect cache for a file
113
+ ```
114
+
115
+ The Rust binary `ifcfast-mesh` writes OBJ / glTF / CSV directly:
116
+
117
+ ```bash
118
+ cargo build --release --bin ifcfast-mesh --no-default-features --features mesh
119
+ ./target/release/ifcfast-mesh model.ifc model.glb
120
+ ```
121
+
122
+ ## Cache
123
+
124
+ Parquet files live under `~/.cache/ifcfast/<cache_key>/`, where
125
+ `cache_key` is `sha256(file_size + first 4 MB + last 4 MB)` truncated.
126
+ Any edit to the IFC invalidates the entry automatically.
127
+
128
+ Override with the `IFCFAST_CACHE` environment variable, e.g.
129
+ `IFCFAST_CACHE=/srv/cache ifcfast extract model.ifc`.
130
+
131
+ Disk footprint on a 200 MB Archicad IFC: **2.4 MB total** zstd-compressed.
132
+
133
+ ## Data schemas
134
+
135
+ All extractors return long-format (one row per fact, no nested fields).
136
+ Easy to join, easy to filter, easy to flatten to Excel.
137
+
138
+ **Missing values:** string columns use pandas `StringDtype` with `nan`
139
+ as the NULL sentinel (chosen for memory and pyarrow round-trip).
140
+ Cells corresponding to a STEP `$` field hold `float('nan')`, **not**
141
+ Python `None`. Use `.isna()` to test, not `== None` or `is None`:
142
+
143
+ ```python
144
+ m.classifications[m.classifications.identification.isna()] # correct
145
+ m.classifications[m.classifications.identification == None] # always False
146
+ [r for r in m.classifications.itertuples() if r.identification is None] # always False
147
+ ```
148
+
149
+ If you're cross-checking against `ifcopenshell` (which returns `None`),
150
+ normalise NaN→None on the comparison side.
151
+
152
+
153
+ ### psets
154
+
155
+ | column | type | description |
156
+ |---|---|---|
157
+ | `guid` | str | `IfcProduct.GlobalId` |
158
+ | `pset_name` | str | e.g. `Pset_WallCommon` |
159
+ | `prop_name` | str | e.g. `IsExternal` |
160
+ | `value` | str \| None | booleans normalised to `True` / `False` / `UNKNOWN` |
161
+ | `value_type` | str \| None | `IfcBoolean`, `IfcText`, `IfcReal`, … |
162
+
163
+ ### quantities
164
+
165
+ | column | type | description |
166
+ |---|---|---|
167
+ | `guid` | str | `IfcProduct.GlobalId` |
168
+ | `qto_name` | str | e.g. `Qto_WallBaseQuantities` |
169
+ | `quantity_name` | str | e.g. `NetVolume`, `GrossArea`, `Length` |
170
+ | `value` | str \| None | numeric value as string |
171
+ | `quantity_type` | str | `Area` / `Length` / `Volume` / `Count` / `Weight` / `Time` |
172
+ | `unit_step_id` | int \| None | usually None (project default applies) |
173
+
174
+ ### materials
175
+
176
+ | column | type | description |
177
+ |---|---|---|
178
+ | `guid` | str | `IfcProduct.GlobalId` |
179
+ | `role` | str | `direct` / `list` / `layer` / `unknown` |
180
+ | `layer_index` | int | 0-based for layered materials, `-1` otherwise |
181
+ | `material_name` | str \| None | material label |
182
+ | `layer_thickness_mm` | float \| None | only set for `role="layer"` |
183
+ | `category` | str \| None | IFC4 only |
184
+
185
+ ### classifications
186
+
187
+ | column | type | description |
188
+ |---|---|---|
189
+ | `guid` | str | `IfcProduct.GlobalId` |
190
+ | `system_name` | str \| None | `NS 3451`, `Uniformat II`, `OmniClass` |
191
+ | `edition` | str \| None | e.g. `2022` |
192
+ | `identification` | str \| None | the actual code |
193
+ | `name` | str \| None | human label |
194
+ | `location` | str \| None | URI to spec (rarely populated) |
195
+ | `source` | str \| None | publisher |
196
+
197
+ ### drift
198
+
199
+ | column | type | description |
200
+ |---|---|---|
201
+ | `guid`, `entity`, `source` | str | identification |
202
+ | `triangle_count`, `surface_area`, `volume_abs` | int / float | geometric stats |
203
+ | `placement_x/y/z` | float | what `IfcLocalPlacement` says |
204
+ | `centroid_x/y/z` | float | where the mesh AABB centre actually is |
205
+ | `drift_distance` | float | Euclidean distance, mm |
206
+ | `max_extent` | float | largest AABB dimension |
207
+ | `drift_ratio` | float | `drift_distance / max_extent` |
208
+ | `drift_severity` | str | `ok` / `warn` / `error` |
209
+
210
+ Severity rule: `ok` when `drift_ratio ≤ 2.0` or `drift_distance < 10
211
+ mm`; `error` when `drift_ratio > 10.0` and `drift_distance > 10 mm`.
212
+
213
+ A 100 m wall placed at one end has ratio 0.5 (legitimate). A 50 mm sensor
214
+ 100 m from its placement has ratio 2000 (clear authoring bug).
215
+
216
+ ## Federated floor synthesis
217
+
218
+ Multi-discipline projects have the same physical floor named differently
219
+ by ARK / RIB / RIV / RIE authors. `ifcfast.federated_floors` clusters by
220
+ elevation across discipline models and applies a project-supplied YAML
221
+ rule.
222
+
223
+ ```yaml
224
+ # examples/projects/lbk-building-c.yaml
225
+ prefix: "C - "
226
+ overrides:
227
+ Plan U1: Hav
228
+ C - U1: Hav
229
+ idempotent_labels: [Hav]
230
+ apply_drop_leading_zero: true
231
+ ```
232
+
233
+ The module is project-agnostic — project tables live in user config.
234
+
235
+ ## Architecture in two paragraphs
236
+
237
+ The Rust core (`crates/core`) does one byte-level pass over the IFC's
238
+ DATA section using a string-aware STEP tokenizer (memchr-accelerated).
239
+ That pass builds an `EntityTable` — a `step_id → byte_range` map of
240
+ every entity in the file. Each PyO3 entry point (`index_ifc`,
241
+ `extract_psets`, etc.) walks the table once, dispatching on entity type
242
+ and extracting only the fields that layer needs.
243
+
244
+ The Python cache (`ifcfast.cache`) writes each extractor's output as
245
+ zstd-compressed parquet, keyed by `sha256(size + 4 MB head + 4 MB tail)`
246
+ so any IFC edit invalidates automatically. Hot reads are pure
247
+ pandas / pyarrow — no Rust call needed. There is no `ifcopenshell.open()`
248
+ anywhere in the data path; `ifcopenshell` is an *optional* dev dep used
249
+ only for cross-checking parity in tests.
250
+
251
+ ## What it doesn't do
252
+
253
+ - Write or modify IFCs. Read-only by construction.
254
+ - Schema validation. Trusts the file's syntax. Use
255
+ [bsi-validator](https://github.com/buildingSMART/IFC) for conformance.
256
+ - Tessellate `IfcBooleanClippingResult` (walls with openings render
257
+ without cutouts — gross volume correct, net volume not).
258
+ - NURBS / advanced BREP geometry. ~0.5% of elements in typical exports.
259
+ - Property variants beyond `IfcPropertySingleValue` —
260
+ `IfcPropertyEnumeratedValue`, `IfcPropertyListValue`,
261
+ `IfcPropertyBoundedValue`, `IfcComplexProperty` are skipped. Covers
262
+ ~90% of psets seen on Revit / Archicad / Tekla / MagiCAD exports.
263
+
264
+ ## Layout
265
+
266
+ ```
267
+ crates/core/ Rust extension (PyO3) — tokenizer, indexer, extractors, mesh
268
+ src/
269
+ lib.rs PyO3 entry points
270
+ lexer.rs STEP tokenizer
271
+ indexer.rs tier-1 product / storey index
272
+ entity_table.rs step_id → byte range lookup
273
+ extractors/ psets, quantities, materials, classifications
274
+ mesh/ extrusion, mapped, face sets, BREP, glTF writer
275
+ bin/ ifcfast-bench, ifcfast-mesh CLIs
276
+ python/ifcfast/ Public Python API
277
+ __init__.py ifcfast.open(), Model, header, classify
278
+ header.py STEP header reader (tier-0)
279
+ model.py Model class + native tier-1 driver
280
+ cache.py parquet cache for index + data layers
281
+ classify.py element-mode policy (count / measure / linear / skip)
282
+ federated_floors.py multi-discipline floor synthesiser
283
+ cli.py ifcfast CLI
284
+ docs/history/ origin doc + audit issues from ifc-workbench
285
+ examples/projects/ project YAMLs for federated_floors
286
+ tests/ pytest suite
287
+ ```
288
+
289
+ ## License
290
+
291
+ MIT — see [`LICENSE`](LICENSE).
292
+