@ifc-lite/create 2.4.0 → 2.6.0

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.
@@ -1,28 +1,12 @@
1
1
  /* This Source Code Form is subject to the terms of the Mozilla Public
2
2
  * License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
- import { esc, stepLine, num, vecLen, vecNorm, vecCross, NON_ELEMENT_TYPES, assertPositiveFinite, assertFinitePoint3, } from './ifc-creator-math.js';
4
+ import { esc, stepLine, num, vecLen, vecNorm, vecCross, optStr, optEnum, NON_ELEMENT_TYPES, assertPositiveFinite, assertFinitePoint3, } from './ifc-creator-math.js';
5
+ import { emitWorkCalendar, emitTaskTime } from './ifc-creator-scheduling.js';
6
+ import { assertCostSchema, emitCostItem, emitCostSchedule, emitCostValue, emitMeasureWithUnit, emitMonetaryUnit, emitPhysicalQuantity, emitRelAssignsToProduct, emitSIUnit, } from './ifc-creator-cost.js';
7
+ import { emitElementQuantity, emitPropertySet } from './ifc-creator-definitions.js';
5
8
  import { generateIfcGuid, isValidIfcGuid } from '@ifc-lite/encoding';
6
9
  // ============================================================================
7
- // STEP attribute helpers (scheduling — optional strings / enums / numbers)
8
- // ============================================================================
9
- /** Emit an optional STEP string: `'value'` when present, `$` otherwise. */
10
- function optStr(v) {
11
- return v === undefined || v === null || v === '' ? '$' : `'${esc(v)}'`;
12
- }
13
- /** Emit an optional STEP enum: `.VALUE.` when present, `$` otherwise. */
14
- function optEnum(v) {
15
- return v === undefined || v === null || v === '' ? '$' : `.${v}.`;
16
- }
17
- /** Emit an optional STEP boolean: `.T.`/`.F.`/`$`. */
18
- function optBool(v) {
19
- return v === undefined || v === null ? '$' : v ? '.T.' : '.F.';
20
- }
21
- /** Emit an optional STEP real number; `$` when absent. */
22
- function optReal(v) {
23
- return v === undefined || v === null || !Number.isFinite(v) ? '$' : num(v);
24
- }
25
- // ============================================================================
26
10
  // IfcCreator
27
11
  // ============================================================================
28
12
  /**
@@ -854,7 +838,6 @@ export class IfcCreator {
854
838
  const name = params.Name ?? 'Space';
855
839
  const desc = params.Description ? `'${esc(params.Description)}'` : '$';
856
840
  const objType = params.ObjectType ? `'${esc(params.ObjectType)}'` : '$';
857
- const tag = params.Tag ? `'${esc(params.Tag)}'` : '$';
858
841
  const longName = params.LongName ? `'${esc(params.LongName)}'` : '$';
859
842
  this.line(spaceId, 'IFCSPACE', `'${globalId}',#${this.ownerHistoryId},'${esc(name)}',${desc},${objType},#${placementId},#${prodShapeId},${longName},.ELEMENT.,.INTERNAL.,$`);
860
843
  this.elementSolids.set(spaceId, [solidId]);
@@ -1198,39 +1181,16 @@ export class IfcCreator {
1198
1181
  // ============================================================================
1199
1182
  /** Attach a property set to an element */
1200
1183
  addIfcPropertySet(elementId, pset) {
1201
- const propIds = [];
1202
- for (const prop of pset.Properties) {
1203
- const propId = this.id();
1204
- const valueStr = this.serializePropertyValue(prop);
1205
- this.line(propId, 'IFCPROPERTYSINGLEVALUE', `'${esc(prop.Name)}',$,${valueStr},$`);
1206
- propIds.push(propId);
1207
- }
1208
- const psetId = this.id();
1209
- const globalId = this.newGlobalId();
1210
- const refs = propIds.map(id => `#${id}`).join(',');
1211
- this.line(psetId, 'IFCPROPERTYSET', `'${globalId}',#${this.ownerHistoryId},'${esc(pset.Name)}',$,(${refs})`);
1212
- const relId = this.id();
1213
- const relGlobalId = this.newGlobalId();
1214
- this.line(relId, 'IFCRELDEFINESBYPROPERTIES', `'${relGlobalId}',#${this.ownerHistoryId},$,$,(#${elementId}),#${psetId}`);
1215
- return psetId;
1184
+ return emitPropertySet(elementId, pset, this.definitionContext());
1216
1185
  }
1217
1186
  /** Attach element quantities to an element */
1218
1187
  addIfcElementQuantity(elementId, qset) {
1219
- const qtyIds = [];
1220
- for (const qty of qset.Quantities) {
1221
- const qtyId = this.id();
1222
- const valueField = this.quantityValueField(qty);
1223
- this.line(qtyId, qty.Kind.toUpperCase(), `'${esc(qty.Name)}',$,${valueField}${this.ifc4Only('$')}`);
1224
- qtyIds.push(qtyId);
1225
- }
1226
- const qsetId = this.id();
1227
- const globalId = this.newGlobalId();
1228
- const refs = qtyIds.map(id => `#${id}`).join(',');
1229
- this.line(qsetId, 'IFCELEMENTQUANTITY', `'${globalId}',#${this.ownerHistoryId},'${esc(qset.Name)}',$,$,(${refs})`);
1230
- const relId = this.id();
1231
- const relGlobalId = this.newGlobalId();
1232
- this.line(relId, 'IFCRELDEFINESBYPROPERTIES', `'${relGlobalId}',#${this.ownerHistoryId},$,$,(#${elementId}),#${qsetId}`);
1233
- return qsetId;
1188
+ return emitElementQuantity(elementId, qset, this.definitionContext());
1189
+ }
1190
+ /** The creator hooks `ifc-creator-definitions.ts`'s emitters need. */
1191
+ definitionContext() {
1192
+ return { emit: this.emitEntity, newGlobalId: () => this.newGlobalId(),
1193
+ ownerRef: `#${this.ownerHistoryId}`, ifc4Only: (v) => this.ifc4Only(v) };
1234
1194
  }
1235
1195
  // ============================================================================
1236
1196
  // Public API — Styling
@@ -1283,7 +1243,7 @@ export class IfcCreator {
1283
1243
  this.elementMaterials.set(elementId, materialRefId);
1284
1244
  }
1285
1245
  // ============================================================================
1286
- // Public API — Scheduling / 4D (IfcWorkSchedule, IfcTask, IfcRelSequence)
1246
+ // Public API — Scheduling / 4D (IfcWorkSchedule, IfcTask, IfcRelSequence, IfcWorkCalendar)
1287
1247
  // ============================================================================
1288
1248
  /**
1289
1249
  * Create an IfcWorkSchedule. Returns its expressId for assigning tasks
@@ -1299,6 +1259,32 @@ export class IfcCreator {
1299
1259
  addIfcWorkPlan(params) {
1300
1260
  return this.buildWorkControl('IFCWORKPLAN', params);
1301
1261
  }
1262
+ /**
1263
+ * Create an IfcWorkCalendar — the working / non-working time calendar an
1264
+ * IfcTask or IfcWorkSchedule can be assigned to. `WorkingTimes` /
1265
+ * `ExceptionTimes` entries become nested IfcWorkTime entities (with their
1266
+ * IfcRecurrencePattern / IfcTimePeriod when the entry carries a
1267
+ * recurrence), the same way `addIfcTask` emits its IfcTaskTime. Assign it
1268
+ * with `assignCalendarToTasks`. Returns the calendar expressId.
1269
+ */
1270
+ addIfcWorkCalendar(params) {
1271
+ if (this.schema === 'IFC2X3') {
1272
+ throw new Error('addIfcWorkCalendar is not supported for IFC2X3');
1273
+ }
1274
+ const id = emitWorkCalendar(params, this.newGlobalId(), `#${this.ownerHistoryId}`, this.emitEntity);
1275
+ this.entities.push({ expressId: id, type: 'IfcWorkCalendar', Name: params.Name });
1276
+ return id;
1277
+ }
1278
+ /**
1279
+ * Allocate an express id, write its STEP line, and hand the id back — the
1280
+ * one hook `ifc-creator-scheduling.ts`'s emitters need from the creator.
1281
+ * An arrow property so it can be passed by reference without binding.
1282
+ */
1283
+ emitEntity = (type, attrs) => {
1284
+ const entityId = this.id();
1285
+ this.line(entityId, type, attrs);
1286
+ return entityId;
1287
+ };
1302
1288
  /**
1303
1289
  * Create an IfcTask. If any Schedule/Actual/Early/Late time field, IsCritical,
1304
1290
  * or Completion attribute is present, an IfcTaskTime is created and linked.
@@ -1322,7 +1308,7 @@ export class IfcCreator {
1322
1308
  params.IsCritical !== undefined ||
1323
1309
  params.DurationType !== undefined ||
1324
1310
  params.Completion !== undefined;
1325
- const taskTimeId = hasTaskTime ? this.addIfcTaskTime(params) : 0;
1311
+ const taskTimeId = hasTaskTime ? emitTaskTime(params, this.emitEntity) : 0;
1326
1312
  const taskId = this.id();
1327
1313
  const globalId = this.newGlobalId();
1328
1314
  const name = params.Name;
@@ -1340,43 +1326,6 @@ export class IfcCreator {
1340
1326
  this.entities.push({ expressId: taskId, type: 'IfcTask', Name: name });
1341
1327
  return taskId;
1342
1328
  }
1343
- /**
1344
- * Build an IfcTaskTime from the task params (internal helper — prefer
1345
- * setting the time fields on the task itself via `addIfcTask`).
1346
- */
1347
- addIfcTaskTime(params) {
1348
- const ttId = this.id();
1349
- // [0] Name, [1] DataOrigin, [2] UserDefinedDataOrigin,
1350
- // [3] DurationType, [4] ScheduleDuration, [5] ScheduleStart, [6] ScheduleFinish,
1351
- // [7..10] Early/Late Start/Finish, [11] FreeFloat, [12] TotalFloat,
1352
- // [13] IsCritical, [14] StatusTime,
1353
- // [15] ActualDuration, [16] ActualStart, [17] ActualFinish,
1354
- // [18] RemainingTime, [19] Completion
1355
- const attrs = [
1356
- '$', // Name
1357
- '$', // DataOrigin
1358
- '$', // UserDefinedDataOrigin
1359
- optEnum(params.DurationType), // DurationType
1360
- optStr(params.ScheduleDuration), // ScheduleDuration
1361
- optStr(params.ScheduleStart), // ScheduleStart
1362
- optStr(params.ScheduleFinish), // ScheduleFinish
1363
- optStr(params.EarlyStart), // EarlyStart
1364
- optStr(params.EarlyFinish), // EarlyFinish
1365
- optStr(params.LateStart), // LateStart
1366
- optStr(params.LateFinish), // LateFinish
1367
- optStr(params.FreeFloat), // FreeFloat
1368
- optStr(params.TotalFloat), // TotalFloat
1369
- optBool(params.IsCritical), // IsCritical
1370
- optStr(params.StatusTime), // StatusTime
1371
- optStr(params.ActualDuration), // ActualDuration
1372
- optStr(params.ActualStart), // ActualStart
1373
- optStr(params.ActualFinish), // ActualFinish
1374
- optStr(params.RemainingTime), // RemainingTime
1375
- optReal(params.Completion), // Completion
1376
- ];
1377
- this.line(ttId, 'IFCTASKTIME', attrs.join(','));
1378
- return ttId;
1379
- }
1380
1329
  /**
1381
1330
  * Create an IfcRelSequence between two tasks. The predecessor is `RelatingProcess`
1382
1331
  * and the successor is `RelatedProcess`. SequenceType defaults to FINISH_START.
@@ -1427,6 +1376,15 @@ export class IfcCreator {
1427
1376
  assignSchedulesToWorkPlan(planId, scheduleIds) {
1428
1377
  return this.addIfcRelAssignsToControl(planId, scheduleIds);
1429
1378
  }
1379
+ /**
1380
+ * Ergonomic alias — assign an IfcWorkCalendar to tasks (or work
1381
+ * schedules; same relation either way). Delegates to
1382
+ * {@link addIfcRelAssignsToControl}, which accepts a calendar because
1383
+ * IfcWorkCalendar is an IfcControl.
1384
+ */
1385
+ assignCalendarToTasks(calendarId, taskIds) {
1386
+ return this.addIfcRelAssignsToControl(calendarId, taskIds);
1387
+ }
1430
1388
  /**
1431
1389
  * Emit an IfcRelAssignsToProcess — binds products (walls, slabs, ...) to an
1432
1390
  * IfcProcess (usually an IfcTask). These drive the Gantt 4D animation.
@@ -1462,6 +1420,68 @@ export class IfcCreator {
1462
1420
  nestTasks(parentTaskId, childTaskIds) {
1463
1421
  return this.addIfcRelNests(parentTaskId, childTaskIds);
1464
1422
  }
1423
+ // Public API — Cost / 5D. Every method delegates to `ifc-creator-cost.ts` and refuses
1424
+ // IFC2X3 by name (its cost entity layout differs, so IFC4 records would parse and mislead).
1425
+ /** Create an IfcCostSchedule (IFC4 / IFC4X3 only). Returns its expressId. */
1426
+ addIfcCostSchedule(params) {
1427
+ assertCostSchema(this.schema, 'addIfcCostSchedule');
1428
+ const id = emitCostSchedule(params, this.newGlobalId(), `#${this.ownerHistoryId}`, this.emitEntity);
1429
+ this.entities.push({ expressId: id, type: 'IfcCostSchedule', Name: params.Name });
1430
+ return id;
1431
+ }
1432
+ /** Create an IfcCostItem (IFC4 / IFC4X3 only). Returns its expressId. */
1433
+ addIfcCostItem(params) {
1434
+ assertCostSchema(this.schema, 'addIfcCostItem');
1435
+ const id = emitCostItem(params, this.newGlobalId(), `#${this.ownerHistoryId}`, this.emitEntity);
1436
+ this.entities.push({ expressId: id, type: 'IfcCostItem', Name: params.Name });
1437
+ return id;
1438
+ }
1439
+ /** Create an IfcCostValue (IFC4 / IFC4X3 only). Returns its expressId. */
1440
+ addIfcCostValue(params) {
1441
+ assertCostSchema(this.schema, 'addIfcCostValue');
1442
+ const id = emitCostValue(params, this.schema, this.emitEntity);
1443
+ this.entities.push({ expressId: id, type: 'IfcCostValue', Name: params.Name });
1444
+ return id;
1445
+ }
1446
+ /** Create an IfcMonetaryUnit for `currency`. No default — omit it to leave the currency unstated. */
1447
+ addIfcMonetaryUnit(currency) {
1448
+ assertCostSchema(this.schema, 'addIfcMonetaryUnit');
1449
+ return emitMonetaryUnit(currency, this.emitEntity);
1450
+ }
1451
+ /** Create an IfcSIUnit, for use as an IfcMeasureWithUnit or quantity unit. */
1452
+ addIfcSIUnit(params) {
1453
+ assertCostSchema(this.schema, 'addIfcSIUnit');
1454
+ return emitSIUnit(params, this.emitEntity);
1455
+ }
1456
+ /** Create an IfcMeasureWithUnit — what a UnitBasis or entity-valued AppliedValue points at. */
1457
+ addIfcMeasureWithUnit(value, unitId) {
1458
+ assertCostSchema(this.schema, 'addIfcMeasureWithUnit');
1459
+ return emitMeasureWithUnit(value, unitId, this.schema, this.emitEntity);
1460
+ }
1461
+ /** Create a standalone IfcPhysicalSimpleQuantity for IfcCostItem.CostQuantities. */
1462
+ addIfcPhysicalQuantity(params) {
1463
+ assertCostSchema(this.schema, 'addIfcPhysicalQuantity');
1464
+ return emitPhysicalQuantity(params, this.schema, this.emitEntity);
1465
+ }
1466
+ /** Emit an IfcRelAssignsToProduct — see {@link emitRelAssignsToProduct} for the direction rule. */
1467
+ addIfcRelAssignsToProduct(relatingProductId, relatedObjectIds) {
1468
+ return emitRelAssignsToProduct(relatingProductId, relatedObjectIds, () => this.newGlobalId(), `#${this.ownerHistoryId}`, this.emitEntity);
1469
+ }
1470
+ // Ergonomic aliases over the canonical relationships: cost items under a
1471
+ // schedule, cost items pricing a product, tasks bound to a cost item (an
1472
+ // IfcCostItem is an IfcControl), and the cost breakdown hierarchy.
1473
+ assignCostItemsToSchedule(scheduleId, costItemIds) {
1474
+ return this.addIfcRelAssignsToControl(scheduleId, costItemIds);
1475
+ }
1476
+ assignCostItemsToProduct(productId, costItemIds) {
1477
+ return this.addIfcRelAssignsToProduct(productId, costItemIds);
1478
+ }
1479
+ assignTasksToCostItem(costItemId, taskIds) {
1480
+ return this.addIfcRelAssignsToControl(costItemId, taskIds);
1481
+ }
1482
+ nestCostItems(parentCostItemId, childCostItemIds) {
1483
+ return this.addIfcRelNests(parentCostItemId, childCostItemIds);
1484
+ }
1465
1485
  /**
1466
1486
  * Internal — emit an IfcWorkSchedule or IfcWorkPlan (identical attribute
1467
1487
  * layout). Separate helpers are exposed publicly for ergonomics.
@@ -1559,7 +1579,7 @@ ENDSEC;
1559
1579
  this.subContextAxis = this.id();
1560
1580
  this.line(this.subContextAxis, 'IFCGEOMETRICREPRESENTATIONSUBCONTEXT', `$,'Axis',*,*,*,*,#${this.contextId},$,.GRAPH_VIEW.,$`);
1561
1581
  // Units
1562
- this.unitAssignmentId = this.buildUnits(params.LengthUnit ?? 'METRE');
1582
+ this.unitAssignmentId = this.buildUnits(params.LengthUnit ?? 'METRE', params.Currency);
1563
1583
  // Default surface style — light grey with some specularity
1564
1584
  this.defaultStyleId = this.buildDefaultStyle();
1565
1585
  // IfcProject
@@ -1580,7 +1600,7 @@ ENDSEC;
1580
1600
  this.line(this.buildingId, 'IFCBUILDING', `'${buildingGlobalId}',#${this.ownerHistoryId},'Building',$,$,#${this.worldPlacementId},$,$,.ELEMENT.,$,$,$`);
1581
1601
  this.entities.push({ expressId: this.buildingId, type: 'IfcBuilding', Name: 'Building' });
1582
1602
  }
1583
- buildUnits(lengthUnit) {
1603
+ buildUnits(lengthUnit, currency) {
1584
1604
  const dimExpId = this.id();
1585
1605
  this.line(dimExpId, 'IFCDIMENSIONALEXPONENTS', '0,0,0,0,0,0,0');
1586
1606
  const siLengthId = this.id();
@@ -1597,8 +1617,15 @@ ENDSEC;
1597
1617
  this.line(siVolumeId, 'IFCSIUNIT', `*,.VOLUMEUNIT.,$,.CUBIC_METRE.`);
1598
1618
  const siAngleId = this.id();
1599
1619
  this.line(siAngleId, 'IFCSIUNIT', `*,.PLANEANGLEUNIT.,$,.RADIAN.`);
1620
+ // Absent currency stays absent: no IfcMonetaryUnit is written at all, so
1621
+ // the cost read model reports no project currency rather than a guess.
1622
+ const units = [lengthUnitId, siAreaId, siVolumeId, siAngleId];
1623
+ if (currency !== undefined) {
1624
+ assertCostSchema(this.schema, 'ProjectParams.Currency'); // IFC2X3 Currency is an enum
1625
+ units.push(emitMonetaryUnit(currency, this.emitEntity));
1626
+ }
1600
1627
  const assignmentId = this.id();
1601
- this.line(assignmentId, 'IFCUNITASSIGNMENT', `(#${lengthUnitId},#${siAreaId},#${siVolumeId},#${siAngleId})`);
1628
+ this.line(assignmentId, 'IFCUNITASSIGNMENT', `(${units.map(id => `#${id}`).join(',')})`);
1602
1629
  return assignmentId;
1603
1630
  }
1604
1631
  /** Create a default IfcSurfaceStyle with a neutral colour (RGB 0.75, 0.73, 0.68) */
@@ -2138,40 +2165,8 @@ ENDSEC;
2138
2165
  this.entities.push({ expressId: openingId, type: 'IfcOpeningElement', Name: name });
2139
2166
  return openingId;
2140
2167
  }
2141
- // ============================================================================
2142
- // Internal — Property/quantity serialization
2143
- // ============================================================================
2144
- serializePropertyValue(prop) {
2145
- const val = prop.NominalValue;
2146
- if (typeof val === 'string') {
2147
- const typeName = prop.Type ?? 'IfcLabel';
2148
- return `${typeName.toUpperCase()}('${esc(val)}')`;
2149
- }
2150
- if (typeof val === 'number') {
2151
- const typeName = prop.Type ?? (Number.isInteger(val) ? 'IfcInteger' : 'IfcReal');
2152
- return typeName === 'IfcInteger' ? `IFCINTEGER(${Math.round(val)})` : `IFCREAL(${num(val)})`;
2153
- }
2154
- if (typeof val === 'boolean') {
2155
- // `Type: 'IfcLogical'` (tri-state) must not be downgraded to IFCBOOLEAN.
2156
- const typeName = prop.Type === 'IfcLogical' ? 'IFCLOGICAL' : 'IFCBOOLEAN';
2157
- return `${typeName}(${val ? '.T.' : '.F.'})`;
2158
- }
2159
- return '$';
2160
- }
2168
+ // `serializePropertyValue` / `quantityValueField` moved to `ifc-creator-math.ts`.
2161
2169
  ifc4Only(v) { return this.schema === 'IFC2X3' ? '' : `,${v}`; } // trailing IFC4/4X3-only attribute
2162
- quantityValueField(qty) {
2163
- switch (qty.Kind) {
2164
- case 'IfcQuantityLength':
2165
- case 'IfcQuantityArea':
2166
- case 'IfcQuantityVolume':
2167
- case 'IfcQuantityWeight':
2168
- return `$,${num(qty.Value)}`;
2169
- case 'IfcQuantityCount':
2170
- return `$,${Math.round(qty.Value)}`;
2171
- default:
2172
- return `$,${num(qty.Value)}`;
2173
- }
2174
- }
2175
2170
  // ============================================================================
2176
2171
  // Internal — Relationship finalization
2177
2172
  // ============================================================================