1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
//! Hand-maintained schema helpers built on top of the auto-generated
//! `IfcType` enum.
//!
//! These helpers used to live appended to `generated/schema.rs` despite that
//! file's "DO NOT EDIT" header. Moving them here keeps them safe from a
//! re-run of `@ifc-lite/codegen` and lets us derive answers from the EXPRESS
//! inheritance graph instead of maintaining a leaf-level allow-list that has
//! to be amended every time a new IFC4X3 subtype shows up (see PR #585 for
//! `IfcSolarDevice`, which inherits from `IfcEnergyConversionDevice` and was
//! therefore already covered conceptually by the old whitelist's parent
//! entry, but missed in practice because the whitelist was only checked by
//! string match).
//!
//! Co-authored with Geronimo <gerald.stampfel+geronimo@gmail.com> (PR #585).
//!
//! `has_geometry_by_name`, `is_representationless_spatial_container_by_name`
//! and `is_simple_geometry_type` are all on the hot path during scene
//! construction, where the same ~50–100 distinct type names are queried
//! millions of times per file. An immutable, schema-derived table memoises
//! all generated names and bounded exporter aliases without taking a read-lock for each entity.
//! Unknown names use the same predicates without entering a growing cache.
use OnceLock;
use FxHashMap;
use crateIfcType;
/// Normalise to uppercase ASCII without allocating when the input is already
/// uppercase (the common case — STEP type tokens are emitted uppercase).
///
/// `pub(crate)` so record-level parsing and keyword resolution share one exact
/// normalisation rule.
pub
/// Build only from the finite schema catalog: file-supplied unknown names
/// cannot retain memory here. Compute via the canonical predicates, including
/// legacy overrides for names that also occur in the modern schema.
/// Check if a type name (UPPERCASE STEP string) represents an `IfcProduct`
/// subtype that can bear geometry (has `ObjectPlacement` + `Representation`).
///
/// Implementation:
/// 1. Modern names go through `IfcType::from_str` and are accepted iff they
/// inherit from `IfcProduct`, with a small block-list for abstract spatial
/// containers (`IfcBuildingStorey`, `IfcFacility`, `IfcFacilityPart`,
/// `IfcSpatialElement`, `IfcSpatialStructureElement`) that don't carry
/// geometry directly. `IfcSpace`, `IfcSite`, `IfcSpatialZone` and
/// `IfcBuilding` (and any concrete subtype of those) are intentionally
/// kept — they have boundary representations the renderer consumes. See
/// [`is_non_geometric_spatial`] for how that exempt set is maintained.
/// 2. The only non-EXPRESS compatibility spellings are the documented exporter
/// stratum aliases.
/// 3. Reinforcement variants not covered above fall back to a substring
/// match (`REINFORCING…` / `REINFORCED…`).
/// Return the geometry-bearing and representationless-spatial predicates together.
/// The tuple matches [`has_geometry_by_name`] and
/// [`is_representationless_spatial_container_by_name`], respectively, while
/// sharing one lookup of the immutable schema classification (#3987).
/// Subtypes of `IfcProduct` that exist solely as spatial containers and
/// aren't rendered directly. `IfcSpace`/`IfcSite`/`IfcSpatialZone`/`IfcBuilding`
/// and their concrete subtypes are deliberately exempt — their boundary
/// representations are consumed by the renderer when present.
///
/// The exempt set grows as exporters are found that attach a body to a
/// container. `IfcSpatialZone` was unblocked for Revit Family geometry authored
/// via Dynamo (issue #1075); `IfcBuilding` for terrain/DGM exports that hang an
/// `IfcShellBasedSurfaceModel` straight off the building (issue #1910). In both
/// cases the class was blocked, so the entity never became a geometry job and
/// the model rendered nothing at all. **The gate only *permits* meshing; a
/// container with no representation still produces nothing**, so exempting a
/// class costs one abandoned job per instance and is the safe direction.
/// `IfcBuildingStorey` and the `IfcFacility`/`IfcFacilityPart` families stay
/// blocked only because no exporter has been observed giving them a body; the
/// same one-line exemption applies if one is.
///
/// We block by inheritance, not by exact match, so IFC4X3 facility
/// subclasses like `IfcBridge`/`IfcRoad`/`IfcRailway`/`IfcMarineFacility`
/// (under `IfcFacility`), their `*Part` variants (under `IfcFacilityPart`),
/// and any future concrete spatial container all collapse to the same answer
/// without the whitelist needing to enumerate them.
/// Whether `type_name` is one of the spatial-container types that
/// [`has_geometry_by_name`] still blocks by name (`IfcBuildingStorey`,
/// `IfcFacility`, `IfcFacilityPart`, `IfcSpatialElement`,
/// `IfcSpatialStructureElement`, and their subtypes) — i.e. `IfcProduct`
/// subtypes that `is_non_geometric_spatial` treats as never carrying
/// geometry directly. `IfcBuilding` (along with `IfcSpace`, `IfcSite` and
/// `IfcSpatialZone`) is handled class-wide by `has_geometry_by_name` instead
/// — see [`is_non_geometric_spatial`] — so it is no longer part of this
/// instance-level exception.
///
/// In the overwhelming majority of real files that assumption holds for the
/// still-blocked types: these entities are pure hierarchy nodes with a null
/// `Representation`. Issue #1910 was discovered against a DGM/terrain export
/// that attached an `IfcShellBasedSurfaceModel` directly to `IfcBuilding`
/// with no `IfcBuildingElement` children at all; that concrete case is now
/// covered by `IfcBuilding`'s class-wide exemption above, but the same
/// exporter shape could in principle target `IfcBuildingStorey` or another
/// still-blocked container. `has_geometry_by_name` alone can't distinguish
/// "this type never has a body" from "this specific instance happens not
/// to", so callers that need to catch that exceptional case combine this
/// predicate with an instance-level check of whether the entity's
/// `Representation` attribute (index 6 on any `IfcProduct`) is actually
/// non-null before scheduling it for meshing
/// (`crate::parser::nth_attribute_is_present`). See
/// `rust/processing/src/processor/mod.rs` and
/// `rust/wasm-bindings/src/api/gpu_meshes/prepass.rs`.
/// Check if an IFC entity class is "simple" geometry (processed first for
/// fast first frame). Driven off the EXPRESS inheritance graph rather than
/// a leaf-level blacklist, so new IFC4X3 subtypes (e.g. `IfcSolarDevice`
/// under `IfcEnergyConversionDevice`) are categorised correctly without
/// code changes — see PR #585.
///
/// Returns `true` for "simple" elements (load first), `false` for
/// "secondary/complex" (openings, doors, windows, furniture, MEP/distribution
/// elements, spaces, sites, annotations, virtual/proxy entities).
/// Resolve a STEP keyword to its schema-local `IfcType`. Supported keywords
/// retain their exact generated variants. The three non-EXPRESS exporter
/// stratum aliases remain owned `Unknown` values and are handled explicitly by
/// `has_geometry_by_name`. Any pass that *classifies* or *labels* an entity
/// must use this helper rather than reimplementing keyword normalization
/// (#4203).
/// The `IfcTypeProduct` subtype a STEP keyword names, or `None` when it is
/// not one.
///
/// The single predicate behind every type-geometry candidate gate (#957/#962):
/// the native processor, the streaming and sharded browser pre-passes, the
/// styling pre-pass, and the attribute export's pass 3. They MUST agree — a
/// keyword one admits and another drops is either geometry with no attribute
/// row or an attribute row with no geometry (#1518).
///
/// Keeps the cheap `ends_with` pre-filter that kept the resolve and the
/// `is_subtype_of` walk off the hot path for the non-type majority, and
/// resolves through the generated schema universe. Exact variants are
/// preserved for every supported schema keyword, while the three non-EXPRESS
/// exporter stratum aliases remain owned `Unknown` values and are handled by
/// the explicit compatibility predicates (#4203).
///
/// `type_name` is the raw STEP keyword as the scanner read it, in whatever case the file wrote it.
/// The schema-resolved type for an entity, recovered from its RAW STEP RECORD.
///
/// For callers that hold a `DecodedEntity` and its source bytes but no keyword.
/// `DecodedEntity.ifc_type` comes from a bare [`IfcType::from_str`]. Supported
/// supported schema names arrive as exact variants. A genuinely unknown name,
/// including an exporter stratum alias, remains `IfcType::Unknown`, which owns
/// its normalized keyword alongside its CRC32 ID. The raw record is only the
/// fallback when a decoder has not retained a recoverable keyword.
///
/// `decoded` is returned unchanged when it is already a known type, so the
/// scan is paid only by entities that need it, and when the record is
/// malformed enough that no keyword can be read.
///
/// Exists because the wasm mesh batch had exactly this shape and got it wrong:
/// unknown keywords reached the browser labelled `"Unknown"` even when the
/// raw record could identify them. The raw-record fallback keeps both paths
/// aligned (#3179).