@ifc-lite/codegen 1.0.0 → 1.1.1

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 (44) hide show
  1. package/dist/cli.js +23 -5
  2. package/dist/cli.js.map +1 -1
  3. package/dist/crc32.d.ts +16 -0
  4. package/dist/crc32.d.ts.map +1 -0
  5. package/dist/crc32.js +69 -0
  6. package/dist/crc32.js.map +1 -0
  7. package/dist/generator.d.ts +20 -4
  8. package/dist/generator.d.ts.map +1 -1
  9. package/dist/generator.js +175 -19
  10. package/dist/generator.js.map +1 -1
  11. package/dist/index.d.ts +13 -2
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +19 -2
  14. package/dist/index.js.map +1 -1
  15. package/dist/rust-generator.d.ts +20 -0
  16. package/dist/rust-generator.d.ts.map +1 -0
  17. package/dist/rust-generator.js +640 -0
  18. package/dist/rust-generator.js.map +1 -0
  19. package/dist/serialization-generator.d.ts +11 -0
  20. package/dist/serialization-generator.d.ts.map +1 -0
  21. package/dist/serialization-generator.js +333 -0
  22. package/dist/serialization-generator.js.map +1 -0
  23. package/dist/type-ids-generator.d.ts +11 -0
  24. package/dist/type-ids-generator.d.ts.map +1 -0
  25. package/dist/type-ids-generator.js +153 -0
  26. package/dist/type-ids-generator.js.map +1 -0
  27. package/generated/ifc4x3/entities.ts +9 -13
  28. package/generated/ifc4x3/enums.ts +0 -4
  29. package/generated/ifc4x3/index.ts +4 -2
  30. package/generated/ifc4x3/schema-registry.ts +326 -2718
  31. package/generated/ifc4x3/selects.ts +0 -4
  32. package/generated/ifc4x3/serializers.ts +322 -0
  33. package/generated/ifc4x3/test-compile.ts +49 -0
  34. package/generated/ifc4x3/type-ids.ts +1882 -0
  35. package/generated/ifc4x3/types.ts +0 -4
  36. package/package.json +1 -1
  37. package/src/cli.ts +49 -18
  38. package/src/crc32.ts +77 -0
  39. package/src/generator.ts +213 -21
  40. package/src/index.ts +28 -2
  41. package/src/rust-generator.ts +715 -0
  42. package/src/serialization-generator.ts +343 -0
  43. package/src/type-ids-generator.ts +166 -0
  44. package/tsconfig.json +1 -0
@@ -0,0 +1,715 @@
1
+ /* This Source Code Form is subject to the terms of the Mozilla Public
2
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
+
5
+ /**
6
+ * Rust Code Generator
7
+ *
8
+ * Generates Rust types from EXPRESS schema for:
9
+ * - Type IDs (CRC32 constants)
10
+ * - IfcType enum with all variants
11
+ * - Geometry category classification
12
+ * - Type conversion functions
13
+ */
14
+
15
+ import type { ExpressSchema, EntityDefinition } from './express-parser.js';
16
+ import { crc32 } from './crc32.js';
17
+ import { getInheritanceChain } from './express-parser.js';
18
+
19
+ export interface RustGeneratedCode {
20
+ typeIds: string;
21
+ schema: string;
22
+ geometryCategories: string;
23
+ }
24
+
25
+ /**
26
+ * Generate all Rust code from EXPRESS schema
27
+ */
28
+ export function generateRust(schema: ExpressSchema): RustGeneratedCode {
29
+ return {
30
+ typeIds: generateTypeIdConstants(schema),
31
+ schema: generateIfcTypeEnum(schema),
32
+ geometryCategories: generateGeometryCategories(schema),
33
+ };
34
+ }
35
+
36
+ /**
37
+ * Generate CRC32 type ID constants
38
+ */
39
+ function generateTypeIdConstants(schema: ExpressSchema): string {
40
+ let code = `// This Source Code Form is subject to the terms of the Mozilla Public
41
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
42
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
43
+
44
+ //! Auto-generated IFC Type ID Constants
45
+ //!
46
+ //! CRC32 hashes for fast type identification.
47
+ //! Generated from EXPRESS schema: ${schema.name}
48
+ //!
49
+ //! DO NOT EDIT - This file is auto-generated by @ifc-lite/codegen
50
+
51
+ #![allow(dead_code)]
52
+
53
+ `;
54
+
55
+ // Group by category for readability
56
+ const categories = categorizeEntities(schema);
57
+
58
+ for (const [category, entities] of Object.entries(categories)) {
59
+ code += `// ${category}\n`;
60
+ for (const entity of entities) {
61
+ const id = crc32(entity.name);
62
+ code += `pub const ${entity.name.toUpperCase()}: u32 = ${id};\n`;
63
+ }
64
+ code += '\n';
65
+ }
66
+
67
+ return code;
68
+ }
69
+
70
+ /**
71
+ * Generate the main IfcType enum
72
+ */
73
+ function generateIfcTypeEnum(schema: ExpressSchema): string {
74
+ let code = `// This Source Code Form is subject to the terms of the Mozilla Public
75
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
76
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
77
+
78
+ //! Auto-generated IFC Type Enum
79
+ //!
80
+ //! Generated from EXPRESS schema: ${schema.name}
81
+ //!
82
+ //! DO NOT EDIT - This file is auto-generated by @ifc-lite/codegen
83
+
84
+ use std::fmt;
85
+
86
+ /// IFC Entity Types
87
+ ///
88
+ /// All ${schema.entities.length} entity types from the ${schema.name} schema.
89
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
90
+ pub enum IfcType {
91
+ `;
92
+
93
+ // Group entities by category
94
+ const categories = categorizeEntities(schema);
95
+
96
+ for (const [category, entities] of Object.entries(categories)) {
97
+ code += ` // ${category}\n`;
98
+ for (const entity of entities) {
99
+ if (entity.isAbstract) {
100
+ code += ` /// Abstract entity\n`;
101
+ }
102
+ code += ` ${entity.name},\n`;
103
+ }
104
+ code += '\n';
105
+ }
106
+
107
+ // Add Unknown variant for unrecognized types
108
+ code += ` /// Unknown/unrecognized IFC type (stores CRC32 hash)
109
+ Unknown(u32),
110
+ }
111
+
112
+ impl IfcType {
113
+ /// Parse IFC type from string (case-insensitive)
114
+ pub fn from_str(s: &str) -> Self {
115
+ let upper = s.to_uppercase();
116
+ match upper.as_str() {
117
+ `;
118
+
119
+ // Generate match arms for all entities
120
+ for (const entity of schema.entities) {
121
+ code += ` "${entity.name.toUpperCase()}" => Self::${entity.name},\n`;
122
+ }
123
+
124
+ code += ` _ => Self::Unknown(crc32_hash(&upper)),
125
+ }
126
+ }
127
+
128
+ /// Parse from CRC32 type ID
129
+ pub fn from_id(id: u32) -> Self {
130
+ match id {
131
+ `;
132
+
133
+ // Generate match arms for CRC32 IDs
134
+ for (const entity of schema.entities) {
135
+ const id = crc32(entity.name);
136
+ code += ` ${id} => Self::${entity.name},\n`;
137
+ }
138
+
139
+ code += ` _ => Self::Unknown(id),
140
+ }
141
+ }
142
+
143
+ /// Get CRC32 type ID
144
+ pub fn id(&self) -> u32 {
145
+ match self {
146
+ `;
147
+
148
+ for (const entity of schema.entities) {
149
+ const id = crc32(entity.name);
150
+ code += ` Self::${entity.name} => ${id},\n`;
151
+ }
152
+
153
+ code += ` Self::Unknown(id) => *id,
154
+ }
155
+ }
156
+
157
+ /// Get string representation (uppercase)
158
+ pub fn as_str(&self) -> &'static str {
159
+ match self {
160
+ `;
161
+
162
+ for (const entity of schema.entities) {
163
+ code += ` Self::${entity.name} => "${entity.name.toUpperCase()}",\n`;
164
+ }
165
+
166
+ code += ` Self::Unknown(_) => "UNKNOWN",
167
+ }
168
+ }
169
+
170
+ /// Get display name (PascalCase)
171
+ pub fn name(&self) -> &'static str {
172
+ match self {
173
+ `;
174
+
175
+ for (const entity of schema.entities) {
176
+ code += ` Self::${entity.name} => "${entity.name}",\n`;
177
+ }
178
+
179
+ code += ` Self::Unknown(_) => "Unknown",
180
+ }
181
+ }
182
+
183
+ /// Get parent type (if any)
184
+ pub fn parent(&self) -> Option<Self> {
185
+ match self {
186
+ `;
187
+
188
+ for (const entity of schema.entities) {
189
+ if (entity.supertype) {
190
+ code += ` Self::${entity.name} => Some(Self::${entity.supertype}),\n`;
191
+ }
192
+ }
193
+
194
+ code += ` _ => None,
195
+ }
196
+ }
197
+
198
+ /// Check if this type is a subtype of another
199
+ pub fn is_subtype_of(&self, parent: Self) -> bool {
200
+ let mut current = Some(*self);
201
+ while let Some(t) = current {
202
+ if t == parent {
203
+ return true;
204
+ }
205
+ current = t.parent();
206
+ }
207
+ false
208
+ }
209
+
210
+ /// Check if this is an abstract type
211
+ pub fn is_abstract(&self) -> bool {
212
+ match self {
213
+ `;
214
+
215
+ const abstractTypes = schema.entities.filter((e) => e.isAbstract);
216
+ for (const entity of abstractTypes) {
217
+ code += ` Self::${entity.name} => true,\n`;
218
+ }
219
+
220
+ code += ` _ => false,
221
+ }
222
+ }
223
+ }
224
+
225
+ impl fmt::Display for IfcType {
226
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227
+ write!(f, "{}", self.name())
228
+ }
229
+ }
230
+
231
+ /// CRC32 hash function for unknown types
232
+ fn crc32_hash(s: &str) -> u32 {
233
+ const TABLE: [u32; 256] = [
234
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
235
+ 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
236
+ 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
237
+ 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
238
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
239
+ 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
240
+ 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
241
+ 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
242
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
243
+ 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
244
+ 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
245
+ 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
246
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
247
+ 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
248
+ 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
249
+ 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
250
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
251
+ 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
252
+ 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cd9, 0x5005713c, 0x270241aa,
253
+ 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
254
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
255
+ 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
256
+ 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
257
+ 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
258
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
259
+ 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
260
+ 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
261
+ 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
262
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
263
+ 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
264
+ 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
265
+ 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
266
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
267
+ 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
268
+ 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
269
+ 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
270
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
271
+ 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
272
+ 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
273
+ 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
274
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd706b3,
275
+ 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
276
+ 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
277
+ ];
278
+
279
+ let mut crc = 0xffffffffu32;
280
+ for byte in s.bytes() {
281
+ crc = TABLE[((crc ^ byte as u32) & 0xff) as usize] ^ (crc >> 8);
282
+ }
283
+ crc ^ 0xffffffff
284
+ }
285
+ `;
286
+
287
+ return code;
288
+ }
289
+
290
+ /**
291
+ * Generate geometry category classification
292
+ */
293
+ function generateGeometryCategories(schema: ExpressSchema): string {
294
+ let code = `// This Source Code Form is subject to the terms of the Mozilla Public
295
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
296
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
297
+
298
+ //! Auto-generated Geometry Category Classification
299
+ //!
300
+ //! Generated from EXPRESS schema: ${schema.name}
301
+ //!
302
+ //! DO NOT EDIT - This file is auto-generated by @ifc-lite/codegen
303
+
304
+ use super::IfcType;
305
+
306
+ /// Geometry representation categories
307
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
308
+ pub enum GeometryCategory {
309
+ /// Swept solids (extrusions, revolutions)
310
+ SweptSolid,
311
+ /// Boolean/CSG operations
312
+ Boolean,
313
+ /// Explicit mesh representations
314
+ ExplicitMesh,
315
+ /// Mapped/instanced geometry
316
+ MappedItem,
317
+ /// Surface models
318
+ Surface,
319
+ /// Curve geometry
320
+ Curve,
321
+ /// Other geometry types
322
+ Other,
323
+ }
324
+
325
+ /// Profile definition categories
326
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
327
+ pub enum ProfileCategory {
328
+ /// Parametric profiles (rectangle, circle, I-shape, etc.)
329
+ Parametric,
330
+ /// Arbitrary closed profiles
331
+ Arbitrary,
332
+ /// Composite profiles
333
+ Composite,
334
+ }
335
+
336
+ impl IfcType {
337
+ /// Get geometry category for this type
338
+ pub fn geometry_category(&self) -> Option<GeometryCategory> {
339
+ match self {
340
+ // Swept solids
341
+ `;
342
+
343
+ // Swept solids
344
+ const sweptSolids = [
345
+ 'IfcExtrudedAreaSolid',
346
+ 'IfcExtrudedAreaSolidTapered',
347
+ 'IfcRevolvedAreaSolid',
348
+ 'IfcRevolvedAreaSolidTapered',
349
+ 'IfcSurfaceCurveSweptAreaSolid',
350
+ 'IfcFixedReferenceSweptAreaSolid',
351
+ 'IfcSweptDiskSolid',
352
+ 'IfcSweptDiskSolidPolygonal',
353
+ ];
354
+ for (const name of sweptSolids) {
355
+ if (schema.entities.find((e) => e.name === name)) {
356
+ code += ` Self::${name} => Some(GeometryCategory::SweptSolid),\n`;
357
+ }
358
+ }
359
+
360
+ code += `
361
+ // Boolean/CSG
362
+ `;
363
+
364
+ // Boolean types
365
+ const booleanTypes = [
366
+ 'IfcBooleanResult',
367
+ 'IfcBooleanClippingResult',
368
+ 'IfcCsgSolid',
369
+ 'IfcCsgPrimitive3D',
370
+ 'IfcBlock',
371
+ 'IfcSphere',
372
+ 'IfcRightCircularCone',
373
+ 'IfcRightCircularCylinder',
374
+ 'IfcRectangularPyramid',
375
+ ];
376
+ for (const name of booleanTypes) {
377
+ if (schema.entities.find((e) => e.name === name)) {
378
+ code += ` Self::${name} => Some(GeometryCategory::Boolean),\n`;
379
+ }
380
+ }
381
+
382
+ code += `
383
+ // Explicit mesh
384
+ `;
385
+
386
+ // Explicit mesh types
387
+ const meshTypes = [
388
+ 'IfcFacetedBrep',
389
+ 'IfcFacetedBrepWithVoids',
390
+ 'IfcAdvancedBrep',
391
+ 'IfcAdvancedBrepWithVoids',
392
+ 'IfcManifoldSolidBrep',
393
+ 'IfcTriangulatedFaceSet',
394
+ 'IfcPolygonalFaceSet',
395
+ 'IfcTriangulatedIrregularNetwork',
396
+ 'IfcTessellatedFaceSet',
397
+ 'IfcIndexedPolygonalFace',
398
+ 'IfcShellBasedSurfaceModel',
399
+ 'IfcFaceBasedSurfaceModel',
400
+ ];
401
+ for (const name of meshTypes) {
402
+ if (schema.entities.find((e) => e.name === name)) {
403
+ code += ` Self::${name} => Some(GeometryCategory::ExplicitMesh),\n`;
404
+ }
405
+ }
406
+
407
+ code += `
408
+ // Mapped items
409
+ `;
410
+
411
+ if (schema.entities.find((e) => e.name === 'IfcMappedItem')) {
412
+ code += ` Self::IfcMappedItem => Some(GeometryCategory::MappedItem),\n`;
413
+ }
414
+
415
+ code += `
416
+ // Surfaces
417
+ `;
418
+
419
+ const surfaceTypes = [
420
+ 'IfcBSplineSurface',
421
+ 'IfcBSplineSurfaceWithKnots',
422
+ 'IfcRationalBSplineSurfaceWithKnots',
423
+ 'IfcCylindricalSurface',
424
+ 'IfcSphericalSurface',
425
+ 'IfcToroidalSurface',
426
+ 'IfcPlane',
427
+ 'IfcCurveBoundedPlane',
428
+ 'IfcCurveBoundedSurface',
429
+ 'IfcRectangularTrimmedSurface',
430
+ 'IfcSurfaceOfLinearExtrusion',
431
+ 'IfcSurfaceOfRevolution',
432
+ ];
433
+ for (const name of surfaceTypes) {
434
+ if (schema.entities.find((e) => e.name === name)) {
435
+ code += ` Self::${name} => Some(GeometryCategory::Surface),\n`;
436
+ }
437
+ }
438
+
439
+ code += `
440
+ // Curves
441
+ `;
442
+
443
+ const curveTypes = [
444
+ 'IfcBSplineCurve',
445
+ 'IfcBSplineCurveWithKnots',
446
+ 'IfcRationalBSplineCurveWithKnots',
447
+ 'IfcCompositeCurve',
448
+ 'IfcPolyline',
449
+ 'IfcTrimmedCurve',
450
+ 'IfcCircle',
451
+ 'IfcEllipse',
452
+ 'IfcLine',
453
+ 'IfcIndexedPolyCurve',
454
+ ];
455
+ for (const name of curveTypes) {
456
+ if (schema.entities.find((e) => e.name === name)) {
457
+ code += ` Self::${name} => Some(GeometryCategory::Curve),\n`;
458
+ }
459
+ }
460
+
461
+ code += `
462
+ _ => None,
463
+ }
464
+ }
465
+
466
+ /// Get profile category for this type
467
+ pub fn profile_category(&self) -> Option<ProfileCategory> {
468
+ match self {
469
+ // Parametric profiles
470
+ `;
471
+
472
+ const parametricProfiles = [
473
+ 'IfcRectangleProfileDef',
474
+ 'IfcRectangleHollowProfileDef',
475
+ 'IfcRoundedRectangleProfileDef',
476
+ 'IfcCircleProfileDef',
477
+ 'IfcCircleHollowProfileDef',
478
+ 'IfcEllipseProfileDef',
479
+ 'IfcIShapeProfileDef',
480
+ 'IfcAsymmetricIShapeProfileDef',
481
+ 'IfcLShapeProfileDef',
482
+ 'IfcTShapeProfileDef',
483
+ 'IfcUShapeProfileDef',
484
+ 'IfcCShapeProfileDef',
485
+ 'IfcZShapeProfileDef',
486
+ 'IfcTrapeziumProfileDef',
487
+ ];
488
+ for (const name of parametricProfiles) {
489
+ if (schema.entities.find((e) => e.name === name)) {
490
+ code += ` Self::${name} => Some(ProfileCategory::Parametric),\n`;
491
+ }
492
+ }
493
+
494
+ code += `
495
+ // Arbitrary profiles
496
+ `;
497
+
498
+ const arbitraryProfiles = [
499
+ 'IfcArbitraryClosedProfileDef',
500
+ 'IfcArbitraryProfileDefWithVoids',
501
+ 'IfcArbitraryOpenProfileDef',
502
+ 'IfcCenterLineProfileDef',
503
+ ];
504
+ for (const name of arbitraryProfiles) {
505
+ if (schema.entities.find((e) => e.name === name)) {
506
+ code += ` Self::${name} => Some(ProfileCategory::Arbitrary),\n`;
507
+ }
508
+ }
509
+
510
+ code += `
511
+ // Composite profiles
512
+ `;
513
+
514
+ if (schema.entities.find((e) => e.name === 'IfcCompositeProfileDef')) {
515
+ code += ` Self::IfcCompositeProfileDef => Some(ProfileCategory::Composite),\n`;
516
+ }
517
+ if (schema.entities.find((e) => e.name === 'IfcDerivedProfileDef')) {
518
+ code += ` Self::IfcDerivedProfileDef => Some(ProfileCategory::Composite),\n`;
519
+ }
520
+ if (schema.entities.find((e) => e.name === 'IfcMirroredProfileDef')) {
521
+ code += ` Self::IfcMirroredProfileDef => Some(ProfileCategory::Composite),\n`;
522
+ }
523
+
524
+ code += `
525
+ _ => None,
526
+ }
527
+ }
528
+
529
+ /// Check if this type is a spatial structure element
530
+ pub fn is_spatial(&self) -> bool {
531
+ matches!(
532
+ self,
533
+ `;
534
+
535
+ const spatialEntities = [
536
+ 'IfcProject',
537
+ 'IfcSite',
538
+ 'IfcBuilding',
539
+ 'IfcBuildingStorey',
540
+ 'IfcSpace',
541
+ 'IfcFacility',
542
+ 'IfcFacilityPart',
543
+ ];
544
+ const validSpatial = spatialEntities.filter((n) => schema.entities.find((e) => e.name === n));
545
+ if (validSpatial.length > 0) {
546
+ code += ` ${validSpatial.map((n) => `Self::${n}`).join('\n | ')}\n`;
547
+ } else {
548
+ code += ` Self::Unknown(_) // No spatial types found\n`;
549
+ }
550
+
551
+ code += ` )
552
+ }
553
+
554
+ /// Check if this type can have geometry
555
+ pub fn can_have_geometry(&self) -> bool {
556
+ // Products can have geometry through representations
557
+ self.is_product() && !self.is_spatial()
558
+ }
559
+
560
+ /// Check if this type is a product (can have placement/representation)
561
+ pub fn is_product(&self) -> bool {
562
+ // Check if it's a subtype of IfcProduct
563
+ self.is_subtype_of(Self::IfcProduct) || *self == Self::IfcProduct
564
+ }
565
+
566
+ /// Check if this type is a relationship
567
+ pub fn is_relationship(&self) -> bool {
568
+ // Check if name starts with IfcRel
569
+ self.name().starts_with("IfcRel")
570
+ }
571
+
572
+ /// Check if this is a building element
573
+ pub fn is_building_element(&self) -> bool {
574
+ let name = self.name();
575
+ matches!(
576
+ *self,
577
+ Self::IfcWall | Self::IfcWallStandardCase | Self::IfcSlab |
578
+ Self::IfcBeam | Self::IfcColumn | Self::IfcRoof |
579
+ Self::IfcStair | Self::IfcRamp | Self::IfcRailing |
580
+ Self::IfcPlate | Self::IfcMember | Self::IfcFooting |
581
+ Self::IfcPile | Self::IfcCovering | Self::IfcCurtainWall |
582
+ Self::IfcDoor | Self::IfcWindow | Self::IfcChimney |
583
+ Self::IfcShadingDevice | Self::IfcBuildingElementProxy |
584
+ Self::IfcBuildingElementPart
585
+ ) || name.contains("Reinforc")
586
+ }
587
+ }
588
+ `;
589
+
590
+ return code;
591
+ }
592
+
593
+ /**
594
+ * Categorize entities for organized output
595
+ */
596
+ function categorizeEntities(
597
+ schema: ExpressSchema
598
+ ): Record<string, EntityDefinition[]> {
599
+ const categories: Record<string, EntityDefinition[]> = {
600
+ 'Spatial Structure': [],
601
+ 'Building Elements': [],
602
+ Openings: [],
603
+ MEP: [],
604
+ 'Geometry Representations': [],
605
+ 'Geometry Primitives': [],
606
+ Profiles: [],
607
+ Curves: [],
608
+ Surfaces: [],
609
+ Relationships: [],
610
+ Properties: [],
611
+ Materials: [],
612
+ 'Presentation & Style': [],
613
+ 'Core & Common': [],
614
+ Other: [],
615
+ };
616
+
617
+ for (const entity of schema.entities) {
618
+ const name = entity.name;
619
+ const chain = getInheritanceChain(entity, schema);
620
+
621
+ if (
622
+ name.includes('Site') ||
623
+ name.includes('Building') ||
624
+ name.includes('Storey') ||
625
+ name.includes('Space') ||
626
+ name.includes('Project') ||
627
+ chain.includes('IfcSpatialStructureElement')
628
+ ) {
629
+ categories['Spatial Structure'].push(entity);
630
+ } else if (
631
+ chain.includes('IfcBuildingElement') ||
632
+ name.includes('Wall') ||
633
+ name.includes('Slab') ||
634
+ name.includes('Beam') ||
635
+ name.includes('Column')
636
+ ) {
637
+ categories['Building Elements'].push(entity);
638
+ } else if (
639
+ name.includes('Door') ||
640
+ name.includes('Window') ||
641
+ name.includes('Opening')
642
+ ) {
643
+ categories['Openings'].push(entity);
644
+ } else if (
645
+ name.includes('Pipe') ||
646
+ name.includes('Duct') ||
647
+ name.includes('Cable') ||
648
+ chain.includes('IfcDistributionElement')
649
+ ) {
650
+ categories['MEP'].push(entity);
651
+ } else if (
652
+ name.includes('Representation') ||
653
+ name.includes('Shape') ||
654
+ name.includes('GeometricSet')
655
+ ) {
656
+ categories['Geometry Representations'].push(entity);
657
+ } else if (
658
+ name.includes('Solid') ||
659
+ name.includes('Brep') ||
660
+ name.includes('Boolean') ||
661
+ name.includes('Csg') ||
662
+ name.includes('FaceSet')
663
+ ) {
664
+ categories['Geometry Primitives'].push(entity);
665
+ } else if (name.includes('Profile')) {
666
+ categories['Profiles'].push(entity);
667
+ } else if (
668
+ name.includes('Curve') ||
669
+ name.includes('Polyline') ||
670
+ name.includes('Circle') ||
671
+ name.includes('Ellipse') ||
672
+ name.includes('Line') ||
673
+ name.includes('Spline')
674
+ ) {
675
+ categories['Curves'].push(entity);
676
+ } else if (name.includes('Surface') || name.includes('Plane')) {
677
+ categories['Surfaces'].push(entity);
678
+ } else if (name.startsWith('IfcRel')) {
679
+ categories['Relationships'].push(entity);
680
+ } else if (
681
+ name.includes('Property') ||
682
+ name.includes('Quantity') ||
683
+ name.includes('Value')
684
+ ) {
685
+ categories['Properties'].push(entity);
686
+ } else if (name.includes('Material')) {
687
+ categories['Materials'].push(entity);
688
+ } else if (
689
+ name.includes('Style') ||
690
+ name.includes('Colour') ||
691
+ name.includes('Presentation')
692
+ ) {
693
+ categories['Presentation & Style'].push(entity);
694
+ } else if (
695
+ name.includes('Root') ||
696
+ name.includes('Object') ||
697
+ name.includes('Product') ||
698
+ name.includes('Element') ||
699
+ name.includes('Resource')
700
+ ) {
701
+ categories['Core & Common'].push(entity);
702
+ } else {
703
+ categories['Other'].push(entity);
704
+ }
705
+ }
706
+
707
+ // Remove empty categories
708
+ for (const key of Object.keys(categories)) {
709
+ if (categories[key].length === 0) {
710
+ delete categories[key];
711
+ }
712
+ }
713
+
714
+ return categories;
715
+ }