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
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
// 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 modern and legacy names 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;
use crateget_legacy_entity_info;
/// Normalise to uppercase ASCII without allocating when the input is already
/// uppercase (the common case — STEP type tokens are emitted uppercase).
/// 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. Legacy IFC2x3 / removed-in-IFC4x3 names that aren't in the generated
/// enum (e.g. `IFCSLABELEMENTEDCASE`, `IFCBUILDINGELEMENT`, `IFCPROXY`,
/// `IFCEQUIPMENTELEMENT`, `IFCELECTRICDISTRIBUTIONPOINT`) resolve through
/// `legacy_entities::get_legacy_entity_info`, which carries a
/// `has_geometry` flag.
/// 3. Reinforcement variants not covered above fall back to a substring
/// match (`REINFORCING…` / `REINFORCED…`).
/// 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. See
/// `rust/processing/src/processor/mod.rs` and
/// `rust/wasm-bindings/src/api/gpu_meshes/prepass.rs`.
/// Cheap textual check for whether a STEP entity's attribute at `index`
/// (0-based, top-level — respects nested parens and quoted strings) is
/// present and non-null (`$`), without fully decoding the entity via
/// `EntityDecoder`. Companion to
/// [`is_representationless_spatial_container_by_name`]: callers use it to
/// check attribute 6 (`Representation`, stable across every `IfcProduct`
/// subtype) before deciding an otherwise-excluded spatial container
/// exceptionally carries geometry (#1910).
/// 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 `IfcType`, **legacy-aware**: a removed/renamed
/// entity (`IFCPROXY`, `IFCSOLIDSTRATUM`, …) maps to its modern base type via the
/// hand-maintained legacy table, exactly as `has_geometry_by_name` does. Any pass
/// that *classifies* or *labels* an entity must use this rather than a bare
/// `IfcType::from_str`; otherwise it disagrees with the geometry pass (which meshes
/// legacy entities), leaving a rendered node with no attribute row — the
/// geometry/attribute product-set divergence (#1496).
/// The `IfcTypeProduct` subtype a STEP keyword names, **legacy-aware**, or
/// `None` when the keyword 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 LEGACY-AWARE: under a bare [`IfcType::from_str`] the IFC2X3 type
/// products IFC4X3 dropped (`IFCDOORSTYLE`, `IFCWINDOWSTYLE`,
/// `IFCBUILDINGELEMENTTYPE`) come back `Unknown`, a subtype of nothing, so
/// every gate discarded them before they could become jobs — and they carry
/// `has_geometry: false` in the legacy table, so the ordinary product route
/// did not reach them either. Their `RepresentationMaps` geometry was dropped
/// by every path at once (#3187).
///
/// `type_name` is the raw STEP keyword, i.e. already uppercase.
/// The legacy-aware 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`], and for a
/// name IFC4X3 dropped that is `IfcType::Unknown` — which stores a **CRC32
/// hash, not the name**, so the keyword cannot be recovered from it. The record
/// is the only place it still exists.
///
/// `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:
/// every legacy keyword reached the browser labelled `"Unknown"` while the
/// native pipeline, which still has the keyword in hand, labelled it correctly
/// (#3179).