@ifc-lite/create 1.14.2 → 1.14.3
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.
- package/dist/ifc-creator.d.ts +270 -10
- package/dist/ifc-creator.d.ts.map +1 -1
- package/dist/ifc-creator.js +1063 -14
- package/dist/ifc-creator.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +364 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/ifc-creator.js
CHANGED
|
@@ -33,6 +33,14 @@ function num(v) {
|
|
|
33
33
|
function vecLen(v) {
|
|
34
34
|
return Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* IFC types that do NOT follow the IfcElement attribute layout (no Tag/PredefinedType).
|
|
38
|
+
* addElement/addAxisElement skip Tag+PredefinedType for these types.
|
|
39
|
+
*/
|
|
40
|
+
const NON_ELEMENT_TYPES = new Set([
|
|
41
|
+
'IFCBUILDING', 'IFCSITE', 'IFCBUILDINGSTOREY', 'IFCPROJECT',
|
|
42
|
+
'IFCSPACE', 'IFCZONE', 'IFCSYSTEM', 'IFCGROUP',
|
|
43
|
+
]);
|
|
36
44
|
/** Normalize vector — throws on zero-length (indicates geometry bug like Start === End) */
|
|
37
45
|
function vecNorm(v) {
|
|
38
46
|
const len = vecLen(v);
|
|
@@ -82,6 +90,14 @@ export class IfcCreator {
|
|
|
82
90
|
// Tracking for spatial aggregation
|
|
83
91
|
storeyIds = [];
|
|
84
92
|
storeyElements = new Map();
|
|
93
|
+
/** Storey expressId → its IfcLocalPlacement id (at [0,0,elevation]) */
|
|
94
|
+
storeyPlacements = new Map();
|
|
95
|
+
/** Element expressId → containing storey expressId */
|
|
96
|
+
elementStoreys = new Map();
|
|
97
|
+
/** Wall expressId → wall local placement */
|
|
98
|
+
wallPlacements = new Map();
|
|
99
|
+
/** Wall expressId → wall thickness */
|
|
100
|
+
wallThicknesses = new Map();
|
|
85
101
|
projectParams;
|
|
86
102
|
constructor(params = {}) {
|
|
87
103
|
this.projectParams = params;
|
|
@@ -98,12 +114,21 @@ export class IfcCreator {
|
|
|
98
114
|
const name = params.Name ?? 'Storey';
|
|
99
115
|
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
100
116
|
const elevation = num(params.Elevation);
|
|
101
|
-
|
|
117
|
+
// Create a placement at [0, 0, elevation] so child elements are offset to the correct height
|
|
118
|
+
const storeyPlacementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
119
|
+
Location: [0, 0, params.Elevation],
|
|
120
|
+
});
|
|
121
|
+
this.line(id, 'IFCBUILDINGSTOREY', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},$,$,#${storeyPlacementId},$,.ELEMENT.,${elevation}`);
|
|
102
122
|
this.storeyIds.push(id);
|
|
103
123
|
this.storeyElements.set(id, []);
|
|
124
|
+
this.storeyPlacements.set(id, storeyPlacementId);
|
|
104
125
|
this.entities.push({ expressId: id, type: 'IfcBuildingStorey', Name: name });
|
|
105
126
|
return id;
|
|
106
127
|
}
|
|
128
|
+
/** Get the IfcLocalPlacement for a storey (falls back to world if unknown) */
|
|
129
|
+
getStoreyPlacement(storeyId) {
|
|
130
|
+
return this.storeyPlacements.get(storeyId) ?? this.worldPlacementId;
|
|
131
|
+
}
|
|
107
132
|
// ============================================================================
|
|
108
133
|
// Public API — Building Elements
|
|
109
134
|
// ============================================================================
|
|
@@ -120,8 +145,8 @@ export class IfcCreator {
|
|
|
120
145
|
const dz = params.End[2] - params.Start[2];
|
|
121
146
|
const wallLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
122
147
|
const dir = vecNorm([dx, dy, dz]);
|
|
123
|
-
// Placement at Start. Local X = wall direction, Z = up (default).
|
|
124
|
-
const placementId = this.addLocalPlacement(this.
|
|
148
|
+
// Placement at Start, relative to storey. Local X = wall direction, Z = up (default).
|
|
149
|
+
const placementId = this.addLocalPlacement(this.getStoreyPlacement(storeyId), {
|
|
125
150
|
Location: params.Start,
|
|
126
151
|
RefDirection: dir,
|
|
127
152
|
});
|
|
@@ -141,6 +166,8 @@ export class IfcCreator {
|
|
|
141
166
|
this.line(wallId, 'IFCWALL', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.STANDARD.`);
|
|
142
167
|
this.elementSolids.set(wallId, [solidId]);
|
|
143
168
|
this.trackElement(storeyId, wallId);
|
|
169
|
+
this.wallPlacements.set(wallId, placementId);
|
|
170
|
+
this.wallThicknesses.set(wallId, params.Thickness);
|
|
144
171
|
this.entities.push({ expressId: wallId, type: 'IfcWall', Name: name });
|
|
145
172
|
// Add openings
|
|
146
173
|
if (params.Openings) {
|
|
@@ -155,7 +182,7 @@ export class IfcCreator {
|
|
|
155
182
|
* Width along +X, Depth along +Y, Thickness extruded along +Z.
|
|
156
183
|
*/
|
|
157
184
|
addIfcSlab(storeyId, params) {
|
|
158
|
-
const placementId = this.addLocalPlacement(this.
|
|
185
|
+
const placementId = this.addLocalPlacement(this.getStoreyPlacement(storeyId), {
|
|
159
186
|
Location: params.Position,
|
|
160
187
|
});
|
|
161
188
|
let profileId;
|
|
@@ -193,7 +220,7 @@ export class IfcCreator {
|
|
|
193
220
|
* Cross-section centered, extruded upward by Height.
|
|
194
221
|
*/
|
|
195
222
|
addIfcColumn(storeyId, params) {
|
|
196
|
-
const placementId = this.addLocalPlacement(this.
|
|
223
|
+
const placementId = this.addLocalPlacement(this.getStoreyPlacement(storeyId), {
|
|
197
224
|
Location: params.Position,
|
|
198
225
|
});
|
|
199
226
|
// Centered profile — column base center = Position
|
|
@@ -225,7 +252,7 @@ export class IfcCreator {
|
|
|
225
252
|
const dir = vecNorm([dx, dy, dz]);
|
|
226
253
|
// Local Z = beam direction, so extrusion along Z = along beam.
|
|
227
254
|
// Local X, Y define the cross-section plane.
|
|
228
|
-
const placementId = this.addLocalPlacement(this.
|
|
255
|
+
const placementId = this.addLocalPlacement(this.getStoreyPlacement(storeyId), {
|
|
229
256
|
Location: params.Start,
|
|
230
257
|
Axis: dir,
|
|
231
258
|
RefDirection: this.computeRefDirection(dir),
|
|
@@ -263,7 +290,7 @@ export class IfcCreator {
|
|
|
263
290
|
throw new Error('addStair: Width must be > 0');
|
|
264
291
|
const direction = params.Direction ?? 0;
|
|
265
292
|
// Use LocalPlacement rotation so both step positions AND profiles rotate together
|
|
266
|
-
const placementId = this.addLocalPlacement(this.
|
|
293
|
+
const placementId = this.addLocalPlacement(this.getStoreyPlacement(storeyId), {
|
|
267
294
|
Location: params.Position,
|
|
268
295
|
RefDirection: direction !== 0 ? [Math.cos(direction), Math.sin(direction), 0] : undefined,
|
|
269
296
|
});
|
|
@@ -297,19 +324,22 @@ export class IfcCreator {
|
|
|
297
324
|
return stairId;
|
|
298
325
|
}
|
|
299
326
|
/**
|
|
300
|
-
* Create a roof. Position is the minimum corner.
|
|
301
|
-
* Width along +X, Depth along +Y, Thickness extruded
|
|
302
|
-
* Optional Slope
|
|
327
|
+
* Create a flat or mono-pitch roof slab. Position is the minimum corner.
|
|
328
|
+
* Width along +X, Depth along +Y, Thickness extruded normal to the slab.
|
|
329
|
+
* Optional Slope is in radians and creates a single slope along +X.
|
|
303
330
|
*/
|
|
304
331
|
addIfcRoof(storeyId, params) {
|
|
305
332
|
const slope = params.Slope ?? 0;
|
|
333
|
+
if (slope < 0 || slope >= Math.PI / 2) {
|
|
334
|
+
throw new Error('addIfcRoof: Slope must be in radians between 0 and π/2 (e.g. Math.PI / 12 for 15°)');
|
|
335
|
+
}
|
|
306
336
|
let axis = [0, 0, 1];
|
|
307
337
|
let refDir = [1, 0, 0];
|
|
308
338
|
if (slope > 0) {
|
|
309
339
|
axis = [Math.sin(slope), 0, Math.cos(slope)];
|
|
310
340
|
refDir = [Math.cos(slope), 0, -Math.sin(slope)];
|
|
311
341
|
}
|
|
312
|
-
const placementId = this.addLocalPlacement(this.
|
|
342
|
+
const placementId = this.addLocalPlacement(this.getStoreyPlacement(storeyId), {
|
|
313
343
|
Location: params.Position,
|
|
314
344
|
Axis: axis,
|
|
315
345
|
RefDirection: refDir,
|
|
@@ -331,6 +361,702 @@ export class IfcCreator {
|
|
|
331
361
|
this.entities.push({ expressId: roofId, type: 'IfcRoof', Name: name });
|
|
332
362
|
return roofId;
|
|
333
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Create a standard dual-pitch gable roof from a rectangular footprint.
|
|
366
|
+
* The ridge runs along the longer footprint dimension to keep the roof height reasonable.
|
|
367
|
+
*/
|
|
368
|
+
addIfcGableRoof(storeyId, params) {
|
|
369
|
+
if (params.Width <= 0)
|
|
370
|
+
throw new Error('addIfcGableRoof: Width must be > 0');
|
|
371
|
+
if (params.Depth <= 0)
|
|
372
|
+
throw new Error('addIfcGableRoof: Depth must be > 0');
|
|
373
|
+
if (params.Thickness <= 0)
|
|
374
|
+
throw new Error('addIfcGableRoof: Thickness must be > 0');
|
|
375
|
+
if (params.Slope <= 0 || params.Slope >= Math.PI / 2) {
|
|
376
|
+
throw new Error('addIfcGableRoof: Slope must be in radians between 0 and π/2 (e.g. Math.PI / 12 for 15°)');
|
|
377
|
+
}
|
|
378
|
+
const overhang = params.Overhang ?? 0;
|
|
379
|
+
if (overhang < 0)
|
|
380
|
+
throw new Error('addIfcGableRoof: Overhang must be >= 0');
|
|
381
|
+
const placementId = this.addLocalPlacement(this.getStoreyPlacement(storeyId), {
|
|
382
|
+
Location: params.Position,
|
|
383
|
+
});
|
|
384
|
+
const cosSlope = Math.cos(params.Slope);
|
|
385
|
+
const sinSlope = Math.sin(params.Slope);
|
|
386
|
+
const ridgeAlongX = params.Width >= params.Depth;
|
|
387
|
+
const span = ridgeAlongX ? params.Depth : params.Width;
|
|
388
|
+
const ridgeLength = (ridgeAlongX ? params.Width : params.Depth) + (overhang * 2);
|
|
389
|
+
const run = (span / 2) + overhang;
|
|
390
|
+
const rise = run * Math.tan(params.Slope);
|
|
391
|
+
const slopedRun = run / cosSlope;
|
|
392
|
+
const createRoofPlane = (center, runDir, ridgeDir) => {
|
|
393
|
+
const originId = this.addCartesianPoint(center);
|
|
394
|
+
const axisId = this.addDirection(vecNorm(vecCross(runDir, ridgeDir)));
|
|
395
|
+
const refDirId = this.addDirection(runDir);
|
|
396
|
+
const axis2Id = this.addAxis2Placement3D(originId, axisId, refDirId);
|
|
397
|
+
const profileId = this.addRectangleProfile(slopedRun, ridgeLength);
|
|
398
|
+
return this.addExtrudedAreaSolid(profileId, params.Thickness, undefined, axis2Id);
|
|
399
|
+
};
|
|
400
|
+
const solids = ridgeAlongX
|
|
401
|
+
? [
|
|
402
|
+
createRoofPlane([params.Width / 2, (params.Depth / 4) - (overhang / 2), rise / 2], [0, -cosSlope, -sinSlope], [1, 0, 0]),
|
|
403
|
+
createRoofPlane([params.Width / 2, ((params.Depth * 3) / 4) + (overhang / 2), rise / 2], [0, cosSlope, -sinSlope], [-1, 0, 0]),
|
|
404
|
+
]
|
|
405
|
+
: [
|
|
406
|
+
createRoofPlane([(params.Width / 4) - (overhang / 2), params.Depth / 2, rise / 2], [-cosSlope, 0, -sinSlope], [0, -1, 0]),
|
|
407
|
+
createRoofPlane([((params.Width * 3) / 4) + (overhang / 2), params.Depth / 2, rise / 2], [cosSlope, 0, -sinSlope], [0, 1, 0]),
|
|
408
|
+
];
|
|
409
|
+
const shapeId = this.addShapeRepresentation('Body', solids);
|
|
410
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
411
|
+
const roofId = this.id();
|
|
412
|
+
const globalId = newGlobalId();
|
|
413
|
+
const name = params.Name ?? 'Gable Roof';
|
|
414
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
415
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
416
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
417
|
+
this.line(roofId, 'IFCROOF', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.GABLE_ROOF.`);
|
|
418
|
+
this.elementSolids.set(roofId, solids);
|
|
419
|
+
this.trackElement(storeyId, roofId);
|
|
420
|
+
this.entities.push({ expressId: roofId, type: 'IfcRoof', Name: name });
|
|
421
|
+
return roofId;
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Create a door hosted in a wall opening and aligned to the host wall.
|
|
425
|
+
* Position is wall-local: [distance_along_wall, 0, base_height].
|
|
426
|
+
*/
|
|
427
|
+
addIfcWallDoor(wallId, params) {
|
|
428
|
+
const { storeyId, placementId, wallThickness } = this.getHostedWallInfo(wallId);
|
|
429
|
+
const thickness = params.Thickness ?? wallThickness;
|
|
430
|
+
if (thickness <= 0)
|
|
431
|
+
throw new Error('addIfcWallDoor: Thickness must be > 0');
|
|
432
|
+
const openingId = this.addWallOpening(wallId, placementId, {
|
|
433
|
+
Name: params.Name ? `${params.Name} Opening` : 'Door Opening',
|
|
434
|
+
Width: params.Width,
|
|
435
|
+
Height: params.Height,
|
|
436
|
+
Position: params.Position,
|
|
437
|
+
}, wallThickness);
|
|
438
|
+
const doorPlacementId = this.addHostedWallFillPlacement(placementId, params.Position, wallThickness);
|
|
439
|
+
const profileId = this.addRectangleProfile(params.Width, params.Height, [0, params.Height / 2]);
|
|
440
|
+
const solidId = this.addExtrudedAreaSolid(profileId, thickness);
|
|
441
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
442
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
443
|
+
const doorId = this.id();
|
|
444
|
+
const globalId = newGlobalId();
|
|
445
|
+
const name = params.Name ?? 'Door';
|
|
446
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
447
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
448
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
449
|
+
const opType = params.OperationType ?? 'SINGLE_SWING_LEFT';
|
|
450
|
+
const predType = params.PredefinedType ?? 'NOTDEFINED';
|
|
451
|
+
this.line(doorId, 'IFCDOOR', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${doorPlacementId},#${prodShapeId},${tag},${num(params.Height)},${num(params.Width)},.${predType}.,.${opType}.,$`);
|
|
452
|
+
this.addIfcRelFillsElement(openingId, doorId);
|
|
453
|
+
this.elementSolids.set(doorId, [solidId]);
|
|
454
|
+
this.trackElement(storeyId, doorId);
|
|
455
|
+
this.entities.push({ expressId: doorId, type: 'IfcDoor', Name: name });
|
|
456
|
+
return doorId;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Create a window hosted in a wall opening and aligned to the host wall.
|
|
460
|
+
* Position is wall-local: [distance_along_wall, 0, sill_height].
|
|
461
|
+
*/
|
|
462
|
+
addIfcWallWindow(wallId, params) {
|
|
463
|
+
const { storeyId, placementId, wallThickness } = this.getHostedWallInfo(wallId);
|
|
464
|
+
const thickness = params.Thickness ?? wallThickness;
|
|
465
|
+
if (thickness <= 0)
|
|
466
|
+
throw new Error('addIfcWallWindow: Thickness must be > 0');
|
|
467
|
+
const openingId = this.addWallOpening(wallId, placementId, {
|
|
468
|
+
Name: params.Name ? `${params.Name} Opening` : 'Window Opening',
|
|
469
|
+
Width: params.Width,
|
|
470
|
+
Height: params.Height,
|
|
471
|
+
Position: params.Position,
|
|
472
|
+
}, wallThickness);
|
|
473
|
+
const windowPlacementId = this.addHostedWallFillPlacement(placementId, params.Position, wallThickness);
|
|
474
|
+
const profileId = this.addRectangleProfile(params.Width, params.Height, [0, params.Height / 2]);
|
|
475
|
+
const solidId = this.addExtrudedAreaSolid(profileId, thickness);
|
|
476
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
477
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
478
|
+
const windowId = this.id();
|
|
479
|
+
const globalId = newGlobalId();
|
|
480
|
+
const name = params.Name ?? 'Window';
|
|
481
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
482
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
483
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
484
|
+
const partType = params.PartitioningType ?? 'SINGLE_PANEL';
|
|
485
|
+
this.line(windowId, 'IFCWINDOW', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${windowPlacementId},#${prodShapeId},${tag},${num(params.Height)},${num(params.Width)},.NOTDEFINED.,.${partType}.,$`);
|
|
486
|
+
this.addIfcRelFillsElement(openingId, windowId);
|
|
487
|
+
this.elementSolids.set(windowId, [solidId]);
|
|
488
|
+
this.trackElement(storeyId, windowId);
|
|
489
|
+
this.entities.push({ expressId: windowId, type: 'IfcWindow', Name: name });
|
|
490
|
+
return windowId;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Create a door element. Width × Height × Thickness panel.
|
|
494
|
+
*/
|
|
495
|
+
addIfcDoor(storeyId, params) {
|
|
496
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
497
|
+
Location: params.Position,
|
|
498
|
+
});
|
|
499
|
+
const thickness = params.Thickness ?? 0.05;
|
|
500
|
+
const profileId = this.addRectangleProfile(params.Width, thickness);
|
|
501
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Height);
|
|
502
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
503
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
504
|
+
const doorId = this.id();
|
|
505
|
+
const globalId = newGlobalId();
|
|
506
|
+
const name = params.Name ?? 'Door';
|
|
507
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
508
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
509
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
510
|
+
const opType = params.OperationType ?? 'SINGLE_SWING_LEFT';
|
|
511
|
+
const predType = params.PredefinedType ?? 'NOTDEFINED';
|
|
512
|
+
this.line(doorId, 'IFCDOOR', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},${num(params.Height)},${num(params.Width)},.${predType}.,.${opType}.,$`);
|
|
513
|
+
this.elementSolids.set(doorId, [solidId]);
|
|
514
|
+
this.trackElement(storeyId, doorId);
|
|
515
|
+
this.entities.push({ expressId: doorId, type: 'IfcDoor', Name: name });
|
|
516
|
+
return doorId;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Create a window element. Width × Height × Thickness frame.
|
|
520
|
+
*/
|
|
521
|
+
addIfcWindow(storeyId, params) {
|
|
522
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
523
|
+
Location: params.Position,
|
|
524
|
+
});
|
|
525
|
+
const thickness = params.Thickness ?? 0.05;
|
|
526
|
+
const profileId = this.addRectangleProfile(params.Width, thickness);
|
|
527
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Height);
|
|
528
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
529
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
530
|
+
const windowId = this.id();
|
|
531
|
+
const globalId = newGlobalId();
|
|
532
|
+
const name = params.Name ?? 'Window';
|
|
533
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
534
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
535
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
536
|
+
const partType = params.PartitioningType ?? 'SINGLE_PANEL';
|
|
537
|
+
this.line(windowId, 'IFCWINDOW', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},${num(params.Height)},${num(params.Width)},.NOTDEFINED.,.${partType}.,$`);
|
|
538
|
+
this.elementSolids.set(windowId, [solidId]);
|
|
539
|
+
this.trackElement(storeyId, windowId);
|
|
540
|
+
this.entities.push({ expressId: windowId, type: 'IfcWindow', Name: name });
|
|
541
|
+
return windowId;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Create a ramp. Position is the low end.
|
|
545
|
+
* Width along +Y, Length along +X, Rise optionally inclines the ramp.
|
|
546
|
+
*/
|
|
547
|
+
addIfcRamp(storeyId, params) {
|
|
548
|
+
const rise = params.Rise ?? 0;
|
|
549
|
+
let axis = [0, 0, 1];
|
|
550
|
+
let refDir = [1, 0, 0];
|
|
551
|
+
if (rise > 0) {
|
|
552
|
+
const slopeAngle = Math.atan2(rise, params.Length);
|
|
553
|
+
axis = [Math.sin(slopeAngle), 0, Math.cos(slopeAngle)];
|
|
554
|
+
refDir = [Math.cos(slopeAngle), 0, -Math.sin(slopeAngle)];
|
|
555
|
+
}
|
|
556
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
557
|
+
Location: params.Position,
|
|
558
|
+
Axis: axis,
|
|
559
|
+
RefDirection: refDir,
|
|
560
|
+
});
|
|
561
|
+
const profileId = this.addRectangleProfile(params.Length, params.Width, [params.Length / 2, params.Width / 2]);
|
|
562
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Thickness);
|
|
563
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
564
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
565
|
+
const rampId = this.id();
|
|
566
|
+
const globalId = newGlobalId();
|
|
567
|
+
const name = params.Name ?? 'Ramp';
|
|
568
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
569
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
570
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
571
|
+
this.line(rampId, 'IFCRAMP', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.STRAIGHT_RUN_RAMP.`);
|
|
572
|
+
this.elementSolids.set(rampId, [solidId]);
|
|
573
|
+
this.trackElement(storeyId, rampId);
|
|
574
|
+
this.entities.push({ expressId: rampId, type: 'IfcRamp', Name: name });
|
|
575
|
+
return rampId;
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Create a railing from Start to End with given Height.
|
|
579
|
+
*/
|
|
580
|
+
addIfcRailing(storeyId, params) {
|
|
581
|
+
const dx = params.End[0] - params.Start[0];
|
|
582
|
+
const dy = params.End[1] - params.Start[1];
|
|
583
|
+
const dz = params.End[2] - params.Start[2];
|
|
584
|
+
const railLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
585
|
+
const dir = vecNorm([dx, dy, dz]);
|
|
586
|
+
// Use identity orientation so posts can extrude vertically along world Z
|
|
587
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
588
|
+
Location: params.Start,
|
|
589
|
+
});
|
|
590
|
+
const railWidth = params.Width ?? 0.05;
|
|
591
|
+
// Rail solid — extrude along the rail direction (dir) at rail Height
|
|
592
|
+
const railProfileId = this.addRectangleProfile(railWidth, railWidth);
|
|
593
|
+
const railOriginId = this.addCartesianPoint([0, 0, params.Height]);
|
|
594
|
+
const railAxisId = this.addDirection(dir);
|
|
595
|
+
const railRefId = this.addDirection(this.computeRefDirection(dir));
|
|
596
|
+
const railAxis2Id = this.addAxis2Placement3D(railOriginId, railAxisId, railRefId);
|
|
597
|
+
const railSolidId = this.id();
|
|
598
|
+
this.line(railSolidId, 'IFCEXTRUDEDAREASOLID', `#${railProfileId},#${railAxis2Id},#${this.dirZ},${num(railLen)}`);
|
|
599
|
+
const solids = [railSolidId];
|
|
600
|
+
// Vertical posts at start and end
|
|
601
|
+
const postProfile = this.addRectangleProfile(railWidth, railWidth);
|
|
602
|
+
// Start post — at origin, extrude up
|
|
603
|
+
const startPostOriginId = this.addCartesianPoint([0, 0, 0]);
|
|
604
|
+
const startPostAxis2Id = this.addAxis2Placement3D(startPostOriginId);
|
|
605
|
+
const startPostId = this.id();
|
|
606
|
+
this.line(startPostId, 'IFCEXTRUDEDAREASOLID', `#${postProfile},#${startPostAxis2Id},#${this.dirZ},${num(params.Height)}`);
|
|
607
|
+
solids.push(startPostId);
|
|
608
|
+
// End post — at the end of the rail, extrude up
|
|
609
|
+
const endPostOriginId = this.addCartesianPoint([
|
|
610
|
+
dx, dy, dz,
|
|
611
|
+
]);
|
|
612
|
+
const endPostAxis2Id = this.addAxis2Placement3D(endPostOriginId);
|
|
613
|
+
const endPostProfile = this.addRectangleProfile(railWidth, railWidth);
|
|
614
|
+
const endPostId = this.id();
|
|
615
|
+
this.line(endPostId, 'IFCEXTRUDEDAREASOLID', `#${endPostProfile},#${endPostAxis2Id},#${this.dirZ},${num(params.Height)}`);
|
|
616
|
+
solids.push(endPostId);
|
|
617
|
+
const shapeId = this.addShapeRepresentation('Body', solids);
|
|
618
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
619
|
+
const railingId = this.id();
|
|
620
|
+
const globalId = newGlobalId();
|
|
621
|
+
const name = params.Name ?? 'Railing';
|
|
622
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
623
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
624
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
625
|
+
this.line(railingId, 'IFCRAILING', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.HANDRAIL.`);
|
|
626
|
+
this.elementSolids.set(railingId, solids);
|
|
627
|
+
this.trackElement(storeyId, railingId);
|
|
628
|
+
this.entities.push({ expressId: railingId, type: 'IfcRailing', Name: name });
|
|
629
|
+
return railingId;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Create a plate (thin flat element, e.g. steel plate).
|
|
633
|
+
*/
|
|
634
|
+
addIfcPlate(storeyId, params) {
|
|
635
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
636
|
+
Location: params.Position,
|
|
637
|
+
});
|
|
638
|
+
let profileId;
|
|
639
|
+
if (params.Profile && params.Profile.length >= 3) {
|
|
640
|
+
profileId = this.addArbitraryProfile(params.Profile);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
profileId = this.addRectangleProfile(params.Width, params.Depth, [params.Width / 2, params.Depth / 2]);
|
|
644
|
+
}
|
|
645
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Thickness);
|
|
646
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
647
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
648
|
+
const plateId = this.id();
|
|
649
|
+
const globalId = newGlobalId();
|
|
650
|
+
const name = params.Name ?? 'Plate';
|
|
651
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
652
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
653
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
654
|
+
this.line(plateId, 'IFCPLATE', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.NOTDEFINED.`);
|
|
655
|
+
this.elementSolids.set(plateId, [solidId]);
|
|
656
|
+
this.trackElement(storeyId, plateId);
|
|
657
|
+
this.entities.push({ expressId: plateId, type: 'IfcPlate', Name: name });
|
|
658
|
+
return plateId;
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Create a structural member (brace, strut, etc.) from Start to End.
|
|
662
|
+
*/
|
|
663
|
+
addIfcMember(storeyId, params) {
|
|
664
|
+
const dx = params.End[0] - params.Start[0];
|
|
665
|
+
const dy = params.End[1] - params.Start[1];
|
|
666
|
+
const dz = params.End[2] - params.Start[2];
|
|
667
|
+
const memberLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
668
|
+
const dir = vecNorm([dx, dy, dz]);
|
|
669
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
670
|
+
Location: params.Start,
|
|
671
|
+
Axis: dir,
|
|
672
|
+
RefDirection: this.computeRefDirection(dir),
|
|
673
|
+
});
|
|
674
|
+
const profileId = this.addRectangleProfile(params.Width, params.Height);
|
|
675
|
+
const solidId = this.addExtrudedAreaSolid(profileId, memberLen);
|
|
676
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
677
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
678
|
+
const memberId = this.id();
|
|
679
|
+
const globalId = newGlobalId();
|
|
680
|
+
const name = params.Name ?? 'Member';
|
|
681
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
682
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
683
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
684
|
+
this.line(memberId, 'IFCMEMBER', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.NOTDEFINED.`);
|
|
685
|
+
this.elementSolids.set(memberId, [solidId]);
|
|
686
|
+
this.trackElement(storeyId, memberId);
|
|
687
|
+
this.entities.push({ expressId: memberId, type: 'IfcMember', Name: name });
|
|
688
|
+
return memberId;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Create a footing (foundation). Position is top centre, Height extends downward.
|
|
692
|
+
*/
|
|
693
|
+
addIfcFooting(storeyId, params) {
|
|
694
|
+
// Offset placement downward so extrusion starts at bottom
|
|
695
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
696
|
+
Location: [params.Position[0], params.Position[1], params.Position[2] - params.Height],
|
|
697
|
+
});
|
|
698
|
+
const profileId = this.addRectangleProfile(params.Width, params.Depth);
|
|
699
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Height);
|
|
700
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
701
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
702
|
+
const footingId = this.id();
|
|
703
|
+
const globalId = newGlobalId();
|
|
704
|
+
const name = params.Name ?? 'Footing';
|
|
705
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
706
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
707
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
708
|
+
const predefined = params.PredefinedType ?? 'PAD_FOOTING';
|
|
709
|
+
this.line(footingId, 'IFCFOOTING', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.${predefined}.`);
|
|
710
|
+
this.elementSolids.set(footingId, [solidId]);
|
|
711
|
+
this.trackElement(storeyId, footingId);
|
|
712
|
+
this.entities.push({ expressId: footingId, type: 'IfcFooting', Name: name });
|
|
713
|
+
return footingId;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Create a pile (deep foundation). Position is top, Length extends downward.
|
|
717
|
+
* Uses circular cross-section by default, rectangular if IsRectangular is set.
|
|
718
|
+
*/
|
|
719
|
+
addIfcPile(storeyId, params) {
|
|
720
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
721
|
+
Location: [params.Position[0], params.Position[1], params.Position[2] - params.Length],
|
|
722
|
+
});
|
|
723
|
+
let profileId;
|
|
724
|
+
if (params.IsRectangular) {
|
|
725
|
+
const depth = params.RectangularDepth ?? params.Diameter;
|
|
726
|
+
profileId = this.addRectangleProfile(params.Diameter, depth);
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
profileId = this.addCircleProfile(params.Diameter / 2);
|
|
730
|
+
}
|
|
731
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Length);
|
|
732
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
733
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
734
|
+
const pileId = this.id();
|
|
735
|
+
const globalId = newGlobalId();
|
|
736
|
+
const name = params.Name ?? 'Pile';
|
|
737
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
738
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
739
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
740
|
+
this.line(pileId, 'IFCPILE', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.DRIVEN.,$`);
|
|
741
|
+
this.elementSolids.set(pileId, [solidId]);
|
|
742
|
+
this.trackElement(storeyId, pileId);
|
|
743
|
+
this.entities.push({ expressId: pileId, type: 'IfcPile', Name: name });
|
|
744
|
+
return pileId;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Create a space (room volume).
|
|
748
|
+
*/
|
|
749
|
+
addIfcSpace(storeyId, params) {
|
|
750
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
751
|
+
Location: params.Position,
|
|
752
|
+
});
|
|
753
|
+
let profileId;
|
|
754
|
+
if (params.Profile && params.Profile.length >= 3) {
|
|
755
|
+
profileId = this.addArbitraryProfile(params.Profile);
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
profileId = this.addRectangleProfile(params.Width, params.Depth, [params.Width / 2, params.Depth / 2]);
|
|
759
|
+
}
|
|
760
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Height);
|
|
761
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
762
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
763
|
+
const spaceId = this.id();
|
|
764
|
+
const globalId = newGlobalId();
|
|
765
|
+
const name = params.Name ?? 'Space';
|
|
766
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
767
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
768
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
769
|
+
const longName = params.LongName ? `'${esc(params.LongName)}'` : '$';
|
|
770
|
+
this.line(spaceId, 'IFCSPACE', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${longName},.ELEMENT.,.INTERNAL.,$`);
|
|
771
|
+
this.elementSolids.set(spaceId, [solidId]);
|
|
772
|
+
this.trackElement(storeyId, spaceId);
|
|
773
|
+
this.entities.push({ expressId: spaceId, type: 'IfcSpace', Name: name });
|
|
774
|
+
return spaceId;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Create a curtain wall. Thin panel from Start to End, extruded by Height.
|
|
778
|
+
*/
|
|
779
|
+
addIfcCurtainWall(storeyId, params) {
|
|
780
|
+
const dx = params.End[0] - params.Start[0];
|
|
781
|
+
const dy = params.End[1] - params.Start[1];
|
|
782
|
+
const dz = params.End[2] - params.Start[2];
|
|
783
|
+
const wallLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
784
|
+
const dir = vecNorm([dx, dy, dz]);
|
|
785
|
+
const thickness = params.Thickness ?? 0.05;
|
|
786
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
787
|
+
Location: params.Start,
|
|
788
|
+
RefDirection: dir,
|
|
789
|
+
});
|
|
790
|
+
const profileId = this.addRectangleProfile(wallLen, thickness, [wallLen / 2, 0]);
|
|
791
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Height);
|
|
792
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
793
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
794
|
+
const cwId = this.id();
|
|
795
|
+
const globalId = newGlobalId();
|
|
796
|
+
const name = params.Name ?? 'Curtain Wall';
|
|
797
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
798
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
799
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
800
|
+
this.line(cwId, 'IFCCURTAINWALL', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.NOTDEFINED.`);
|
|
801
|
+
this.elementSolids.set(cwId, [solidId]);
|
|
802
|
+
this.trackElement(storeyId, cwId);
|
|
803
|
+
this.entities.push({ expressId: cwId, type: 'IfcCurtainWall', Name: name });
|
|
804
|
+
return cwId;
|
|
805
|
+
}
|
|
806
|
+
/**
|
|
807
|
+
* Create a furnishing element (furniture/equipment bounding box).
|
|
808
|
+
*/
|
|
809
|
+
addIfcFurnishingElement(storeyId, params) {
|
|
810
|
+
const direction = params.Direction ?? 0;
|
|
811
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
812
|
+
Location: params.Position,
|
|
813
|
+
RefDirection: direction !== 0 ? [Math.cos(direction), Math.sin(direction), 0] : undefined,
|
|
814
|
+
});
|
|
815
|
+
const profileId = this.addRectangleProfile(params.Width, params.Depth, [params.Width / 2, params.Depth / 2]);
|
|
816
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Height);
|
|
817
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
818
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
819
|
+
const furnId = this.id();
|
|
820
|
+
const globalId = newGlobalId();
|
|
821
|
+
const name = params.Name ?? 'Furnishing';
|
|
822
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
823
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
824
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
825
|
+
this.line(furnId, 'IFCFURNISHINGELEMENT', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag}`);
|
|
826
|
+
this.elementSolids.set(furnId, [solidId]);
|
|
827
|
+
this.trackElement(storeyId, furnId);
|
|
828
|
+
this.entities.push({ expressId: furnId, type: 'IfcFurnishingElement', Name: name });
|
|
829
|
+
return furnId;
|
|
830
|
+
}
|
|
831
|
+
/**
|
|
832
|
+
* Create a proxy element (generic element for custom/unclassified objects).
|
|
833
|
+
*/
|
|
834
|
+
addIfcBuildingElementProxy(storeyId, params) {
|
|
835
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
836
|
+
Location: params.Position,
|
|
837
|
+
});
|
|
838
|
+
let profileId;
|
|
839
|
+
if (params.Profile && params.Profile.length >= 3) {
|
|
840
|
+
profileId = this.addArbitraryProfile(params.Profile);
|
|
841
|
+
}
|
|
842
|
+
else {
|
|
843
|
+
profileId = this.addRectangleProfile(params.Width, params.Depth, [params.Width / 2, params.Depth / 2]);
|
|
844
|
+
}
|
|
845
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Height);
|
|
846
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
847
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
848
|
+
const proxyId = this.id();
|
|
849
|
+
const globalId = newGlobalId();
|
|
850
|
+
const name = params.Name ?? 'Proxy';
|
|
851
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
852
|
+
const objType = params.ObjectType ?? params.ProxyType ?? '';
|
|
853
|
+
const objTypeStr = objType ? `'${esc(objType)}'` : '$';
|
|
854
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
855
|
+
this.line(proxyId, 'IFCBUILDINGELEMENTPROXY', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objTypeStr},#${placementId},#${prodShapeId},${tag},.NOTDEFINED.`);
|
|
856
|
+
this.elementSolids.set(proxyId, [solidId]);
|
|
857
|
+
this.trackElement(storeyId, proxyId);
|
|
858
|
+
this.entities.push({ expressId: proxyId, type: 'IfcBuildingElementProxy', Name: name });
|
|
859
|
+
return proxyId;
|
|
860
|
+
}
|
|
861
|
+
// ============================================================================
|
|
862
|
+
// Public API — Advanced Geometry (Circle, I-Shape, L-Shape, T-Shape, U-Shape, C-Shape profiles)
|
|
863
|
+
// ============================================================================
|
|
864
|
+
/**
|
|
865
|
+
* Create a column with circular cross-section.
|
|
866
|
+
*/
|
|
867
|
+
addIfcCircularColumn(storeyId, params) {
|
|
868
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
869
|
+
Location: params.Position,
|
|
870
|
+
});
|
|
871
|
+
const profileId = this.addCircleProfile(params.Radius);
|
|
872
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Height);
|
|
873
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
874
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
875
|
+
const colId = this.id();
|
|
876
|
+
const globalId = newGlobalId();
|
|
877
|
+
const name = params.Name ?? 'Column';
|
|
878
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
879
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
880
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
881
|
+
this.line(colId, 'IFCCOLUMN', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.COLUMN.`);
|
|
882
|
+
this.elementSolids.set(colId, [solidId]);
|
|
883
|
+
this.trackElement(storeyId, colId);
|
|
884
|
+
this.entities.push({ expressId: colId, type: 'IfcColumn', Name: name });
|
|
885
|
+
return colId;
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Create a beam with I-shape (wide-flange/H-shape) cross-section.
|
|
889
|
+
*/
|
|
890
|
+
addIfcIShapeBeam(storeyId, params) {
|
|
891
|
+
const dx = params.End[0] - params.Start[0];
|
|
892
|
+
const dy = params.End[1] - params.Start[1];
|
|
893
|
+
const dz = params.End[2] - params.Start[2];
|
|
894
|
+
const beamLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
895
|
+
const dir = vecNorm([dx, dy, dz]);
|
|
896
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
897
|
+
Location: params.Start,
|
|
898
|
+
Axis: dir,
|
|
899
|
+
RefDirection: this.computeRefDirection(dir),
|
|
900
|
+
});
|
|
901
|
+
const profileId = this.addIShapeProfile(params.OverallWidth, params.OverallDepth, params.WebThickness, params.FlangeThickness, params.FilletRadius);
|
|
902
|
+
const solidId = this.addExtrudedAreaSolid(profileId, beamLen);
|
|
903
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
904
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
905
|
+
const beamId = this.id();
|
|
906
|
+
const globalId = newGlobalId();
|
|
907
|
+
const name = params.Name ?? 'Beam';
|
|
908
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
909
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
910
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
911
|
+
this.line(beamId, 'IFCBEAM', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.BEAM.`);
|
|
912
|
+
this.elementSolids.set(beamId, [solidId]);
|
|
913
|
+
this.trackElement(storeyId, beamId);
|
|
914
|
+
this.entities.push({ expressId: beamId, type: 'IfcBeam', Name: name });
|
|
915
|
+
return beamId;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Create a member with L-shape (angle) cross-section.
|
|
919
|
+
*/
|
|
920
|
+
addIfcLShapeMember(storeyId, params) {
|
|
921
|
+
const dx = params.End[0] - params.Start[0];
|
|
922
|
+
const dy = params.End[1] - params.Start[1];
|
|
923
|
+
const dz = params.End[2] - params.Start[2];
|
|
924
|
+
const memberLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
925
|
+
const dir = vecNorm([dx, dy, dz]);
|
|
926
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
927
|
+
Location: params.Start,
|
|
928
|
+
Axis: dir,
|
|
929
|
+
RefDirection: this.computeRefDirection(dir),
|
|
930
|
+
});
|
|
931
|
+
const profileId = this.addLShapeProfile(params.Depth, params.Width, params.Thickness, params.FilletRadius);
|
|
932
|
+
const solidId = this.addExtrudedAreaSolid(profileId, memberLen);
|
|
933
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
934
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
935
|
+
const memberId = this.id();
|
|
936
|
+
const globalId = newGlobalId();
|
|
937
|
+
const name = params.Name ?? 'Member';
|
|
938
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
939
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
940
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
941
|
+
this.line(memberId, 'IFCMEMBER', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.NOTDEFINED.`);
|
|
942
|
+
this.elementSolids.set(memberId, [solidId]);
|
|
943
|
+
this.trackElement(storeyId, memberId);
|
|
944
|
+
this.entities.push({ expressId: memberId, type: 'IfcMember', Name: name });
|
|
945
|
+
return memberId;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Create a member with T-shape cross-section.
|
|
949
|
+
*/
|
|
950
|
+
addIfcTShapeMember(storeyId, params) {
|
|
951
|
+
const dx = params.End[0] - params.Start[0];
|
|
952
|
+
const dy = params.End[1] - params.Start[1];
|
|
953
|
+
const dz = params.End[2] - params.Start[2];
|
|
954
|
+
const memberLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
955
|
+
const dir = vecNorm([dx, dy, dz]);
|
|
956
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
957
|
+
Location: params.Start,
|
|
958
|
+
Axis: dir,
|
|
959
|
+
RefDirection: this.computeRefDirection(dir),
|
|
960
|
+
});
|
|
961
|
+
const profileId = this.addTShapeProfile(params.FlangeWidth, params.Depth, params.WebThickness, params.FlangeThickness, params.FilletRadius);
|
|
962
|
+
const solidId = this.addExtrudedAreaSolid(profileId, memberLen);
|
|
963
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
964
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
965
|
+
const memberId = this.id();
|
|
966
|
+
const globalId = newGlobalId();
|
|
967
|
+
const name = params.Name ?? 'Member';
|
|
968
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
969
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
970
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
971
|
+
this.line(memberId, 'IFCMEMBER', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.NOTDEFINED.`);
|
|
972
|
+
this.elementSolids.set(memberId, [solidId]);
|
|
973
|
+
this.trackElement(storeyId, memberId);
|
|
974
|
+
this.entities.push({ expressId: memberId, type: 'IfcMember', Name: name });
|
|
975
|
+
return memberId;
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Create a member with U-shape (channel) cross-section.
|
|
979
|
+
*/
|
|
980
|
+
addIfcUShapeMember(storeyId, params) {
|
|
981
|
+
const dx = params.End[0] - params.Start[0];
|
|
982
|
+
const dy = params.End[1] - params.Start[1];
|
|
983
|
+
const dz = params.End[2] - params.Start[2];
|
|
984
|
+
const memberLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
985
|
+
const dir = vecNorm([dx, dy, dz]);
|
|
986
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
987
|
+
Location: params.Start,
|
|
988
|
+
Axis: dir,
|
|
989
|
+
RefDirection: this.computeRefDirection(dir),
|
|
990
|
+
});
|
|
991
|
+
const profileId = this.addUShapeProfile(params.Depth, params.FlangeWidth, params.WebThickness, params.FlangeThickness, params.FilletRadius);
|
|
992
|
+
const solidId = this.addExtrudedAreaSolid(profileId, memberLen);
|
|
993
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
994
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
995
|
+
const memberId = this.id();
|
|
996
|
+
const globalId = newGlobalId();
|
|
997
|
+
const name = params.Name ?? 'Member';
|
|
998
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
999
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
1000
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
1001
|
+
this.line(memberId, 'IFCMEMBER', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.NOTDEFINED.`);
|
|
1002
|
+
this.elementSolids.set(memberId, [solidId]);
|
|
1003
|
+
this.trackElement(storeyId, memberId);
|
|
1004
|
+
this.entities.push({ expressId: memberId, type: 'IfcMember', Name: name });
|
|
1005
|
+
return memberId;
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Create a column or pile with hollow circular cross-section.
|
|
1009
|
+
*/
|
|
1010
|
+
addIfcHollowCircularColumn(storeyId, params) {
|
|
1011
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
1012
|
+
Location: params.Position,
|
|
1013
|
+
});
|
|
1014
|
+
const profileId = this.addCircleHollowProfile(params.Radius, params.WallThickness);
|
|
1015
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Height);
|
|
1016
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
1017
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
1018
|
+
const colId = this.id();
|
|
1019
|
+
const globalId = newGlobalId();
|
|
1020
|
+
const name = params.Name ?? 'Column';
|
|
1021
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
1022
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
1023
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
1024
|
+
this.line(colId, 'IFCCOLUMN', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.COLUMN.`);
|
|
1025
|
+
this.elementSolids.set(colId, [solidId]);
|
|
1026
|
+
this.trackElement(storeyId, colId);
|
|
1027
|
+
this.entities.push({ expressId: colId, type: 'IfcColumn', Name: name });
|
|
1028
|
+
return colId;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Create a beam/column with hollow rectangular (tube) cross-section.
|
|
1032
|
+
*/
|
|
1033
|
+
addIfcRectangleHollowBeam(storeyId, params) {
|
|
1034
|
+
const dx = params.End[0] - params.Start[0];
|
|
1035
|
+
const dy = params.End[1] - params.Start[1];
|
|
1036
|
+
const dz = params.End[2] - params.Start[2];
|
|
1037
|
+
const beamLen = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1038
|
+
const dir = vecNorm([dx, dy, dz]);
|
|
1039
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
1040
|
+
Location: params.Start,
|
|
1041
|
+
Axis: dir,
|
|
1042
|
+
RefDirection: this.computeRefDirection(dir),
|
|
1043
|
+
});
|
|
1044
|
+
const profileId = this.addRectangleHollowProfile(params.XDim, params.YDim, params.WallThickness, params.InnerFilletRadius, params.OuterFilletRadius);
|
|
1045
|
+
const solidId = this.addExtrudedAreaSolid(profileId, beamLen);
|
|
1046
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
1047
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
1048
|
+
const beamId = this.id();
|
|
1049
|
+
const globalId = newGlobalId();
|
|
1050
|
+
const name = params.Name ?? 'Beam';
|
|
1051
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
1052
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
1053
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
1054
|
+
this.line(beamId, 'IFCBEAM', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},.BEAM.`);
|
|
1055
|
+
this.elementSolids.set(beamId, [solidId]);
|
|
1056
|
+
this.trackElement(storeyId, beamId);
|
|
1057
|
+
this.entities.push({ expressId: beamId, type: 'IfcBeam', Name: name });
|
|
1058
|
+
return beamId;
|
|
1059
|
+
}
|
|
334
1060
|
// ============================================================================
|
|
335
1061
|
// Public API — Properties & Quantities
|
|
336
1062
|
// ============================================================================
|
|
@@ -639,6 +1365,11 @@ ENDSEC;
|
|
|
639
1365
|
this.line(id, 'IFCAXIS2PLACEMENT3D', `#${originId},${axis},${refDir}`);
|
|
640
1366
|
return id;
|
|
641
1367
|
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Create a local placement relative to the world coordinate system.
|
|
1370
|
+
* @param relativeTo - Parent placement ID (use getWorldPlacementId() for world origin)
|
|
1371
|
+
* @param placement - Location and optional axis/ref directions
|
|
1372
|
+
*/
|
|
642
1373
|
addLocalPlacement(relativeTo, placement) {
|
|
643
1374
|
const originId = this.addCartesianPoint(placement.Location);
|
|
644
1375
|
let axisId;
|
|
@@ -670,6 +1401,85 @@ ENDSEC;
|
|
|
670
1401
|
this.line(id, 'IFCRECTANGLEPROFILEDEF', `.AREA.,$,#${profileAxis2dId},${num(xDim)},${num(yDim)}`);
|
|
671
1402
|
return id;
|
|
672
1403
|
}
|
|
1404
|
+
/** Create a circle profile. */
|
|
1405
|
+
addCircleProfile(radius) {
|
|
1406
|
+
const profileOriginId = this.addCartesianPoint2D([0, 0]);
|
|
1407
|
+
const profileAxis2dId = this.id();
|
|
1408
|
+
this.line(profileAxis2dId, 'IFCAXIS2PLACEMENT2D', `#${profileOriginId},$`);
|
|
1409
|
+
const id = this.id();
|
|
1410
|
+
this.line(id, 'IFCCIRCLEPROFILEDEF', `.AREA.,$,#${profileAxis2dId},${num(radius)}`);
|
|
1411
|
+
return id;
|
|
1412
|
+
}
|
|
1413
|
+
/** Create a hollow circle profile (pipe section). */
|
|
1414
|
+
addCircleHollowProfile(radius, wallThickness) {
|
|
1415
|
+
const profileOriginId = this.addCartesianPoint2D([0, 0]);
|
|
1416
|
+
const profileAxis2dId = this.id();
|
|
1417
|
+
this.line(profileAxis2dId, 'IFCAXIS2PLACEMENT2D', `#${profileOriginId},$`);
|
|
1418
|
+
const id = this.id();
|
|
1419
|
+
this.line(id, 'IFCCIRCLEHOLLOWPROFILEDEF', `.AREA.,$,#${profileAxis2dId},${num(radius)},${num(wallThickness)}`);
|
|
1420
|
+
return id;
|
|
1421
|
+
}
|
|
1422
|
+
/** Create an I-shape (wide-flange / H-beam) profile. */
|
|
1423
|
+
addIShapeProfile(overallWidth, overallDepth, webThickness, flangeThickness, filletRadius) {
|
|
1424
|
+
const profileOriginId = this.addCartesianPoint2D([0, 0]);
|
|
1425
|
+
const profileAxis2dId = this.id();
|
|
1426
|
+
this.line(profileAxis2dId, 'IFCAXIS2PLACEMENT2D', `#${profileOriginId},$`);
|
|
1427
|
+
const id = this.id();
|
|
1428
|
+
const fillet = filletRadius !== undefined ? num(filletRadius) : '$';
|
|
1429
|
+
this.line(id, 'IFCISHAPEPROFILEDEF', `.AREA.,$,#${profileAxis2dId},${num(overallWidth)},${num(overallDepth)},${num(webThickness)},${num(flangeThickness)},${fillet},$,$`);
|
|
1430
|
+
return id;
|
|
1431
|
+
}
|
|
1432
|
+
/** Create an L-shape (angle section) profile. */
|
|
1433
|
+
addLShapeProfile(depth, width, thickness, filletRadius) {
|
|
1434
|
+
const profileOriginId = this.addCartesianPoint2D([0, 0]);
|
|
1435
|
+
const profileAxis2dId = this.id();
|
|
1436
|
+
this.line(profileAxis2dId, 'IFCAXIS2PLACEMENT2D', `#${profileOriginId},$`);
|
|
1437
|
+
const id = this.id();
|
|
1438
|
+
const fillet = filletRadius !== undefined ? num(filletRadius) : '$';
|
|
1439
|
+
this.line(id, 'IFCLSHAPEPROFILEDEF', `.AREA.,$,#${profileAxis2dId},${num(depth)},${num(width)},${num(thickness)},${fillet},$,$`);
|
|
1440
|
+
return id;
|
|
1441
|
+
}
|
|
1442
|
+
/** Create a T-shape (tee section) profile. */
|
|
1443
|
+
addTShapeProfile(flangeWidth, depth, webThickness, flangeThickness, filletRadius) {
|
|
1444
|
+
const profileOriginId = this.addCartesianPoint2D([0, 0]);
|
|
1445
|
+
const profileAxis2dId = this.id();
|
|
1446
|
+
this.line(profileAxis2dId, 'IFCAXIS2PLACEMENT2D', `#${profileOriginId},$`);
|
|
1447
|
+
const id = this.id();
|
|
1448
|
+
const fillet = filletRadius !== undefined ? num(filletRadius) : '$';
|
|
1449
|
+
this.line(id, 'IFCTSHAPEPROFILEDEF', `.AREA.,$,#${profileAxis2dId},${num(depth)},${num(flangeWidth)},${num(webThickness)},${num(flangeThickness)},${fillet},$,$,$,$`);
|
|
1450
|
+
return id;
|
|
1451
|
+
}
|
|
1452
|
+
/** Create a U-shape (channel section) profile. */
|
|
1453
|
+
addUShapeProfile(depth, flangeWidth, webThickness, flangeThickness, filletRadius) {
|
|
1454
|
+
const profileOriginId = this.addCartesianPoint2D([0, 0]);
|
|
1455
|
+
const profileAxis2dId = this.id();
|
|
1456
|
+
this.line(profileAxis2dId, 'IFCAXIS2PLACEMENT2D', `#${profileOriginId},$`);
|
|
1457
|
+
const id = this.id();
|
|
1458
|
+
const fillet = filletRadius !== undefined ? num(filletRadius) : '$';
|
|
1459
|
+
this.line(id, 'IFCUSHAPEPROFILEDEF', `.AREA.,$,#${profileAxis2dId},${num(depth)},${num(flangeWidth)},${num(webThickness)},${num(flangeThickness)},${fillet},$`);
|
|
1460
|
+
return id;
|
|
1461
|
+
}
|
|
1462
|
+
/** Create a C-shape (cold-formed channel) profile. */
|
|
1463
|
+
addCShapeProfile(depth, width, wallThickness, girth) {
|
|
1464
|
+
const profileOriginId = this.addCartesianPoint2D([0, 0]);
|
|
1465
|
+
const profileAxis2dId = this.id();
|
|
1466
|
+
this.line(profileAxis2dId, 'IFCAXIS2PLACEMENT2D', `#${profileOriginId},$`);
|
|
1467
|
+
const id = this.id();
|
|
1468
|
+
this.line(id, 'IFCCSHAPEPROFILEDEF', `.AREA.,$,#${profileAxis2dId},${num(depth)},${num(width)},${num(wallThickness)},${num(girth)},$`);
|
|
1469
|
+
return id;
|
|
1470
|
+
}
|
|
1471
|
+
/** Create a hollow rectangle (tube section) profile. */
|
|
1472
|
+
addRectangleHollowProfile(xDim, yDim, wallThickness, innerFilletRadius, outerFilletRadius) {
|
|
1473
|
+
const profileOriginId = this.addCartesianPoint2D([0, 0]);
|
|
1474
|
+
const profileAxis2dId = this.id();
|
|
1475
|
+
this.line(profileAxis2dId, 'IFCAXIS2PLACEMENT2D', `#${profileOriginId},$`);
|
|
1476
|
+
const id = this.id();
|
|
1477
|
+
const inner = innerFilletRadius !== undefined ? num(innerFilletRadius) : '$';
|
|
1478
|
+
const outer = outerFilletRadius !== undefined ? num(outerFilletRadius) : '$';
|
|
1479
|
+
this.line(id, 'IFCRECTANGLEHOLLOWPROFILEDEF', `.AREA.,$,#${profileAxis2dId},${num(xDim)},${num(yDim)},${num(wallThickness)},${inner},${outer}`);
|
|
1480
|
+
return id;
|
|
1481
|
+
}
|
|
1482
|
+
/** Create an arbitrary closed profile from a polyline. Points are auto-closed. */
|
|
673
1483
|
addArbitraryProfile(points) {
|
|
674
1484
|
const pointIds = points.map(p => this.addCartesianPoint2D(p));
|
|
675
1485
|
if (points.length > 0) {
|
|
@@ -682,14 +1492,28 @@ ENDSEC;
|
|
|
682
1492
|
this.line(id, 'IFCARBITRARYCLOSEDPROFILEDEF', `.AREA.,$,#${polylineId}`);
|
|
683
1493
|
return id;
|
|
684
1494
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
1495
|
+
/**
|
|
1496
|
+
* Create an extruded area solid from a profile.
|
|
1497
|
+
* @param profileId - ID returned by any addXxxProfile() method
|
|
1498
|
+
* @param depth - Extrusion depth
|
|
1499
|
+
* @param extrusionDir - Optional direction ID (default: Z-up)
|
|
1500
|
+
* @param positionId - Optional local solid placement (default: origin, world axes)
|
|
1501
|
+
*/
|
|
1502
|
+
addExtrudedAreaSolid(profileId, depth, extrusionDir, positionId) {
|
|
1503
|
+
const axis2Id = positionId ?? (() => {
|
|
1504
|
+
const originId = this.addCartesianPoint([0, 0, 0]);
|
|
1505
|
+
return this.addAxis2Placement3D(originId);
|
|
1506
|
+
})();
|
|
688
1507
|
const dirRef = extrusionDir ?? this.dirZ;
|
|
689
1508
|
const id = this.id();
|
|
690
1509
|
this.line(id, 'IFCEXTRUDEDAREASOLID', `#${profileId},#${axis2Id},#${dirRef},${num(depth)}`);
|
|
691
1510
|
return id;
|
|
692
1511
|
}
|
|
1512
|
+
/**
|
|
1513
|
+
* Create a shape representation from solid IDs.
|
|
1514
|
+
* @param repType - 'Body' or 'Axis'
|
|
1515
|
+
* @param itemIds - Array of solid IDs (from addExtrudedAreaSolid, etc.)
|
|
1516
|
+
*/
|
|
693
1517
|
addShapeRepresentation(repType, itemIds) {
|
|
694
1518
|
const contextRef = repType === 'Axis' ? this.subContextAxis : this.subContextBody;
|
|
695
1519
|
const refs = itemIds.map(id => `#${id}`).join(',');
|
|
@@ -699,6 +1523,7 @@ ENDSEC;
|
|
|
699
1523
|
this.line(repId, 'IFCSHAPEREPRESENTATION', `#${contextRef},'${repIdentifier}','${repTypeName}',(${refs})`);
|
|
700
1524
|
return repId;
|
|
701
1525
|
}
|
|
1526
|
+
/** Wrap shape representations into a product definition shape. */
|
|
702
1527
|
addProductDefinitionShape(repIds) {
|
|
703
1528
|
const refs = repIds.map(id => `#${id}`).join(',');
|
|
704
1529
|
const id = this.id();
|
|
@@ -706,6 +1531,202 @@ ENDSEC;
|
|
|
706
1531
|
return id;
|
|
707
1532
|
}
|
|
708
1533
|
// ============================================================================
|
|
1534
|
+
// Public API — Low-level helpers
|
|
1535
|
+
// ============================================================================
|
|
1536
|
+
/** Get the world placement ID (use as relativeTo for addLocalPlacement). */
|
|
1537
|
+
getWorldPlacementId() {
|
|
1538
|
+
return this.worldPlacementId;
|
|
1539
|
+
}
|
|
1540
|
+
/** Create a direction entity. Returns the direction ID. */
|
|
1541
|
+
addDirection3D(d) {
|
|
1542
|
+
return this.addDirection(d);
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Create a profile from a ProfileDef union type.
|
|
1546
|
+
* This is the high-level entry point for profile creation — it dispatches
|
|
1547
|
+
* to the appropriate addXxxProfile() method based on the shape.
|
|
1548
|
+
*
|
|
1549
|
+
* ```ts
|
|
1550
|
+
* const profileId = creator.createProfile({
|
|
1551
|
+
* ProfileType: 'AREA',
|
|
1552
|
+
* Radius: 0.15,
|
|
1553
|
+
* }); // Creates a circle profile
|
|
1554
|
+
* ```
|
|
1555
|
+
*/
|
|
1556
|
+
createProfile(profile) {
|
|
1557
|
+
if ('OuterCurve' in profile) {
|
|
1558
|
+
return this.addArbitraryProfile(profile.OuterCurve);
|
|
1559
|
+
}
|
|
1560
|
+
if ('Radius' in profile && 'WallThickness' in profile) {
|
|
1561
|
+
return this.addCircleHollowProfile(profile.Radius, profile.WallThickness);
|
|
1562
|
+
}
|
|
1563
|
+
if ('Radius' in profile) {
|
|
1564
|
+
return this.addCircleProfile(profile.Radius);
|
|
1565
|
+
}
|
|
1566
|
+
if ('OverallWidth' in profile && 'WebThickness' in profile) {
|
|
1567
|
+
// IShapeProfile
|
|
1568
|
+
return this.addIShapeProfile(profile.OverallWidth, profile.OverallDepth, profile.WebThickness, profile.FlangeThickness, profile.FilletRadius);
|
|
1569
|
+
}
|
|
1570
|
+
// T-shape and U-shape are structurally identical — use Shape discriminator
|
|
1571
|
+
if ('Shape' in profile && profile.Shape === 'IfcTShapeProfileDef') {
|
|
1572
|
+
const p = profile;
|
|
1573
|
+
return this.addTShapeProfile(p.FlangeWidth, p.Depth, p.WebThickness, p.FlangeThickness, p.FilletRadius);
|
|
1574
|
+
}
|
|
1575
|
+
if ('Shape' in profile && profile.Shape === 'IfcUShapeProfileDef') {
|
|
1576
|
+
const p = profile;
|
|
1577
|
+
return this.addUShapeProfile(p.Depth, p.FlangeWidth, p.WebThickness, p.FlangeThickness, p.FilletRadius);
|
|
1578
|
+
}
|
|
1579
|
+
if ('Girth' in profile) {
|
|
1580
|
+
// CShapeProfile
|
|
1581
|
+
const p = profile;
|
|
1582
|
+
return this.addCShapeProfile(p.Depth, p.Width, p.WallThickness, p.Girth);
|
|
1583
|
+
}
|
|
1584
|
+
if ('XDim' in profile && 'YDim' in profile && 'WallThickness' in profile) {
|
|
1585
|
+
// RectangleHollowProfile
|
|
1586
|
+
const p = profile;
|
|
1587
|
+
return this.addRectangleHollowProfile(p.XDim, p.YDim, p.WallThickness, p.InnerFilletRadius, p.OuterFilletRadius);
|
|
1588
|
+
}
|
|
1589
|
+
if ('XDim' in profile && 'YDim' in profile) {
|
|
1590
|
+
// RectangleProfile
|
|
1591
|
+
return this.addRectangleProfile(profile.XDim, profile.YDim);
|
|
1592
|
+
}
|
|
1593
|
+
if ('Depth' in profile && 'Width' in profile && 'Thickness' in profile) {
|
|
1594
|
+
// LShapeProfile
|
|
1595
|
+
const p = profile;
|
|
1596
|
+
return this.addLShapeProfile(p.Depth, p.Width, p.Thickness, p.FilletRadius);
|
|
1597
|
+
}
|
|
1598
|
+
throw new Error('Unrecognized profile shape — ensure ProfileType is "AREA" and required fields are set');
|
|
1599
|
+
}
|
|
1600
|
+
// ============================================================================
|
|
1601
|
+
// Public API — Generic element creation
|
|
1602
|
+
// ============================================================================
|
|
1603
|
+
/**
|
|
1604
|
+
* Create ANY IFC element type with an extruded profile at a placement.
|
|
1605
|
+
*
|
|
1606
|
+
* This is the low-level foundation that all high-level methods (addIfcWall,
|
|
1607
|
+
* addIfcBeam, etc.) are built on. Use it when you need an IFC type that
|
|
1608
|
+
* doesn't have a dedicated method, or when you need full control.
|
|
1609
|
+
*
|
|
1610
|
+
* ```ts
|
|
1611
|
+
* // Pipe segment with circular profile
|
|
1612
|
+
* creator.addElement(storeyId, {
|
|
1613
|
+
* IfcType: 'IFCFLOWSEGMENT',
|
|
1614
|
+
* Placement: { Location: [0, 0, 3] },
|
|
1615
|
+
* Profile: { ProfileType: 'AREA', Radius: 0.05 },
|
|
1616
|
+
* Depth: 5,
|
|
1617
|
+
* PredefinedType: '.RIGIDSEGMENT.',
|
|
1618
|
+
* Name: 'Pipe-001',
|
|
1619
|
+
* });
|
|
1620
|
+
*
|
|
1621
|
+
* // Distribution element with L-profile
|
|
1622
|
+
* creator.addElement(storeyId, {
|
|
1623
|
+
* IfcType: 'IFCDISTRIBUTIONELEMENT',
|
|
1624
|
+
* Placement: { Location: [2, 0, 0], Axis: [0, 0, 1], RefDirection: [1, 0, 0] },
|
|
1625
|
+
* Profile: { ProfileType: 'AREA', Depth: 0.1, Width: 0.1, Thickness: 0.01 },
|
|
1626
|
+
* Depth: 3,
|
|
1627
|
+
* });
|
|
1628
|
+
* ```
|
|
1629
|
+
*/
|
|
1630
|
+
addElement(storeyId, params) {
|
|
1631
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, params.Placement);
|
|
1632
|
+
const profileId = this.createProfile(params.Profile);
|
|
1633
|
+
// Handle custom extrusion direction
|
|
1634
|
+
let extrusionDirId;
|
|
1635
|
+
if (params.ExtrusionDirection) {
|
|
1636
|
+
extrusionDirId = this.addDirection(params.ExtrusionDirection);
|
|
1637
|
+
}
|
|
1638
|
+
const solidId = this.addExtrudedAreaSolid(profileId, params.Depth, extrusionDirId);
|
|
1639
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
1640
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
1641
|
+
const elementId = this.id();
|
|
1642
|
+
const globalId = newGlobalId();
|
|
1643
|
+
const name = params.Name ?? params.IfcType;
|
|
1644
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
1645
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
1646
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
1647
|
+
const ifcType = params.IfcType.toUpperCase();
|
|
1648
|
+
if (NON_ELEMENT_TYPES.has(ifcType)) {
|
|
1649
|
+
// Non-element types: GlobalId, OwnerHistory, Name, Description, ObjectType, ObjectPlacement, Representation
|
|
1650
|
+
this.line(elementId, params.IfcType, `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId}`);
|
|
1651
|
+
}
|
|
1652
|
+
else {
|
|
1653
|
+
// IfcElement subtypes: ...Tag, PredefinedType
|
|
1654
|
+
const predefinedType = params.PredefinedType ? `.${params.PredefinedType}.` : '.NOTDEFINED.';
|
|
1655
|
+
this.line(elementId, params.IfcType, `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},${predefinedType}`);
|
|
1656
|
+
}
|
|
1657
|
+
this.elementSolids.set(elementId, [solidId]);
|
|
1658
|
+
this.trackElement(storeyId, elementId);
|
|
1659
|
+
this.entities.push({ expressId: elementId, type: params.IfcType, Name: name });
|
|
1660
|
+
return elementId;
|
|
1661
|
+
}
|
|
1662
|
+
/**
|
|
1663
|
+
* Create ANY IFC element type extruded along an axis (Start → End).
|
|
1664
|
+
*
|
|
1665
|
+
* The profile is placed at Start and extruded along the direction to End.
|
|
1666
|
+
* The extrusion length equals the distance between Start and End.
|
|
1667
|
+
*
|
|
1668
|
+
* ```ts
|
|
1669
|
+
* // Pipe segment along an axis
|
|
1670
|
+
* creator.addAxisElement(storeyId, {
|
|
1671
|
+
* IfcType: 'IFCPIPESEGMENT',
|
|
1672
|
+
* Start: [0, 0, 3],
|
|
1673
|
+
* End: [5, 0, 3],
|
|
1674
|
+
* Profile: { ProfileType: 'AREA', Radius: 0.05 },
|
|
1675
|
+
* Name: 'Pipe-001',
|
|
1676
|
+
* });
|
|
1677
|
+
*
|
|
1678
|
+
* // Cable tray with rectangle profile
|
|
1679
|
+
* creator.addAxisElement(storeyId, {
|
|
1680
|
+
* IfcType: 'IFCCABLETRAYSEGMENT',
|
|
1681
|
+
* Start: [0, 0, 2.5],
|
|
1682
|
+
* End: [10, 0, 2.5],
|
|
1683
|
+
* Profile: { ProfileType: 'AREA', XDim: 0.3, YDim: 0.1 },
|
|
1684
|
+
* });
|
|
1685
|
+
* ```
|
|
1686
|
+
*/
|
|
1687
|
+
addAxisElement(storeyId, params) {
|
|
1688
|
+
const dx = params.End[0] - params.Start[0];
|
|
1689
|
+
const dy = params.End[1] - params.Start[1];
|
|
1690
|
+
const dz = params.End[2] - params.Start[2];
|
|
1691
|
+
const length = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
1692
|
+
const dir = vecNorm([dx, dy, dz]);
|
|
1693
|
+
// Compute a perpendicular vector for the profile plane
|
|
1694
|
+
const up = [0, 0, 1];
|
|
1695
|
+
let perp = vecCross(dir, up);
|
|
1696
|
+
if (vecLen(perp) < 1e-6) {
|
|
1697
|
+
// dir is parallel to Z, use X as reference
|
|
1698
|
+
perp = vecCross(dir, [1, 0, 0]);
|
|
1699
|
+
}
|
|
1700
|
+
perp = vecNorm(perp);
|
|
1701
|
+
const placementId = this.addLocalPlacement(this.worldPlacementId, {
|
|
1702
|
+
Location: params.Start,
|
|
1703
|
+
Axis: dir, // local Z = along axis (extrusion direction)
|
|
1704
|
+
RefDirection: perp, // local X = perpendicular to axis
|
|
1705
|
+
});
|
|
1706
|
+
const profileId = this.createProfile(params.Profile);
|
|
1707
|
+
const solidId = this.addExtrudedAreaSolid(profileId, length);
|
|
1708
|
+
const shapeId = this.addShapeRepresentation('Body', [solidId]);
|
|
1709
|
+
const prodShapeId = this.addProductDefinitionShape([shapeId]);
|
|
1710
|
+
const elementId = this.id();
|
|
1711
|
+
const globalId = newGlobalId();
|
|
1712
|
+
const name = params.Name ?? params.IfcType;
|
|
1713
|
+
const desc = params.Description ? `'${esc(params.Description)}'` : '$';
|
|
1714
|
+
const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
|
|
1715
|
+
const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
|
|
1716
|
+
const ifcType = params.IfcType.toUpperCase();
|
|
1717
|
+
if (NON_ELEMENT_TYPES.has(ifcType)) {
|
|
1718
|
+
this.line(elementId, params.IfcType, `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId}`);
|
|
1719
|
+
}
|
|
1720
|
+
else {
|
|
1721
|
+
const predefinedType = params.PredefinedType ? `.${params.PredefinedType}.` : '.NOTDEFINED.';
|
|
1722
|
+
this.line(elementId, params.IfcType, `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${tag},${predefinedType}`);
|
|
1723
|
+
}
|
|
1724
|
+
this.elementSolids.set(elementId, [solidId]);
|
|
1725
|
+
this.trackElement(storeyId, elementId);
|
|
1726
|
+
this.entities.push({ expressId: elementId, type: params.IfcType, Name: name });
|
|
1727
|
+
return elementId;
|
|
1728
|
+
}
|
|
1729
|
+
// ============================================================================
|
|
709
1730
|
// Internal — Openings
|
|
710
1731
|
// ============================================================================
|
|
711
1732
|
/**
|
|
@@ -835,6 +1856,11 @@ ENDSEC;
|
|
|
835
1856
|
const refs = elementIds.map(id => `#${id}`).join(',');
|
|
836
1857
|
this.line(relId, 'IFCRELCONTAINEDINSPATIALSTRUCTURE', `'${globalId}',#${this.ownerHistoryId},$,$,(${refs}),#${storeyId}`);
|
|
837
1858
|
}
|
|
1859
|
+
addIfcRelFillsElement(openingId, fillingId) {
|
|
1860
|
+
const relId = this.id();
|
|
1861
|
+
const globalId = newGlobalId();
|
|
1862
|
+
this.line(relId, 'IFCRELFILLSELEMENT', `'${globalId}',#${this.ownerHistoryId},$,$,#${openingId},#${fillingId}`);
|
|
1863
|
+
}
|
|
838
1864
|
// ============================================================================
|
|
839
1865
|
// Internal — Utilities
|
|
840
1866
|
// ============================================================================
|
|
@@ -844,12 +1870,35 @@ ENDSEC;
|
|
|
844
1870
|
line(id, type, args) {
|
|
845
1871
|
this.lines.push(stepLine(id, type, args));
|
|
846
1872
|
}
|
|
1873
|
+
getHostedWallInfo(wallId) {
|
|
1874
|
+
const storeyId = this.elementStoreys.get(wallId);
|
|
1875
|
+
const placementId = this.wallPlacements.get(wallId);
|
|
1876
|
+
const wallThickness = this.wallThicknesses.get(wallId);
|
|
1877
|
+
if (storeyId === undefined || placementId === undefined || wallThickness === undefined) {
|
|
1878
|
+
throw new Error(`Unknown wallId #${wallId} — call addIfcWall() first`);
|
|
1879
|
+
}
|
|
1880
|
+
return { storeyId, placementId, wallThickness };
|
|
1881
|
+
}
|
|
1882
|
+
addHostedWallFillPlacement(hostPlacementId, position, wallThickness) {
|
|
1883
|
+
const originId = this.addCartesianPoint([
|
|
1884
|
+
position[0],
|
|
1885
|
+
wallThickness / 2,
|
|
1886
|
+
position[2],
|
|
1887
|
+
]);
|
|
1888
|
+
const axisId = this.addDirection([0, -1, 0]);
|
|
1889
|
+
const refDirId = this.addDirection([1, 0, 0]);
|
|
1890
|
+
const axis2Id = this.addAxis2Placement3D(originId, axisId, refDirId);
|
|
1891
|
+
const placementId = this.id();
|
|
1892
|
+
this.line(placementId, 'IFCLOCALPLACEMENT', `#${hostPlacementId},#${axis2Id}`);
|
|
1893
|
+
return placementId;
|
|
1894
|
+
}
|
|
847
1895
|
trackElement(storeyId, elementId) {
|
|
848
1896
|
const elements = this.storeyElements.get(storeyId);
|
|
849
1897
|
if (!elements) {
|
|
850
1898
|
throw new Error(`Unknown storeyId #${storeyId} — call addIfcBuildingStorey() first`);
|
|
851
1899
|
}
|
|
852
1900
|
elements.push(elementId);
|
|
1901
|
+
this.elementStoreys.set(elementId, storeyId);
|
|
853
1902
|
}
|
|
854
1903
|
/** Compute a stable RefDirection perpendicular to a given Axis */
|
|
855
1904
|
computeRefDirection(axis) {
|