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