@ifc-lite/viewer 1.11.5 → 1.14.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.
- package/CHANGELOG.md +91 -0
- package/dist/assets/{Arrow.dom-HFGUoQyp.js → Arrow.dom-CNguvlQi.js} +1 -1
- package/dist/assets/{browser-CllJKxsx.js → browser-D6lgLpkA.js} +1 -1
- package/dist/assets/{index-CQd80vMv.js → index-BMwpw264.js} +4 -4
- package/dist/assets/index-Qp8stcGO.css +1 -0
- package/dist/assets/{index-B69WAU-m.js → index-UaDsJsCR.js} +26434 -23023
- package/dist/assets/{native-bridge-Bu4SptAa.js → native-bridge-DqELq4X0.js} +1 -1
- package/dist/assets/{wasm-bridge-CR2KvcQN.js → wasm-bridge-CVWvHlfH.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +19 -19
- package/src/App.tsx +2 -0
- package/src/components/ui/toast.tsx +121 -0
- package/src/components/viewer/BulkPropertyEditor.tsx +8 -1
- package/src/components/viewer/DataConnector.tsx +8 -1
- package/src/components/viewer/ExportChangesButton.tsx +11 -2
- package/src/components/viewer/ExportDialog.tsx +224 -132
- package/src/components/viewer/MainToolbar.tsx +9 -2
- package/src/components/viewer/PropertiesPanel.tsx +300 -15
- package/src/components/viewer/properties/BsddCard.tsx +507 -0
- package/src/components/viewer/properties/QuantitySetCard.tsx +1 -0
- package/src/components/viewer/useGeometryStreaming.ts +4 -4
- package/src/index.css +7 -0
- package/src/lib/scripts/templates/bim-globals.d.ts +33 -0
- package/src/lib/scripts/templates/create-building.ts +491 -0
- package/src/lib/scripts/templates.ts +8 -0
- package/src/sdk/adapters/export-adapter.ts +84 -0
- package/src/sdk/adapters/lens-adapter.ts +1 -1
- package/src/sdk/adapters/model-adapter.ts +8 -0
- package/src/sdk/adapters/viewer-adapter.ts +1 -1
- package/src/services/bsdd.ts +262 -0
- package/src/store/index.ts +2 -2
- package/src/store/slices/measurementSlice.test.ts +22 -22
- package/src/store/slices/modelSlice.test.ts +2 -0
- package/src/store/slices/mutationSlice.ts +155 -1
- package/vite.config.ts +7 -0
- package/dist/assets/index-BoYyWYAu.css +0 -1
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
|
|
5
|
+
export {} // module boundary for type checking
|
|
6
|
+
/**
|
|
7
|
+
* Create IFC from scratch — generate a fully attributed building
|
|
8
|
+
*
|
|
9
|
+
* Demonstrates the bim.create API: build a complete IFC file with
|
|
10
|
+
* walls, slab, columns, beams, stair, and a parametric timber gridshell
|
|
11
|
+
* roof — each with material colours, standard IFC property sets, and
|
|
12
|
+
* base quantities. The roof is a doubly-curved diamond lattice of
|
|
13
|
+
* small-diameter timber laths generated from a mathematical surface.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ─── 1. Project ─────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const h = bim.create.project({
|
|
19
|
+
Name: 'Sample Building',
|
|
20
|
+
Description: 'Demonstration of ifc-lite IFC creation from scratch',
|
|
21
|
+
Author: 'ifc-lite',
|
|
22
|
+
Organization: 'ifc-lite',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ─── 2. Storey ──────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
const gf = bim.create.addIfcBuildingStorey(h, { Name: 'Ground Floor', Elevation: 0 });
|
|
28
|
+
|
|
29
|
+
// ─── 3. Walls — 5 × 8 m footprint, 3 m high, 0.2 m thick ─────────────
|
|
30
|
+
|
|
31
|
+
const southWall = bim.create.addIfcWall(h, gf, {
|
|
32
|
+
Name: 'South Wall', Description: 'Exterior south façade', ObjectType: 'Basic Wall:Exterior - 200mm',
|
|
33
|
+
Start: [0, 0, 0], End: [5, 0, 0], Thickness: 0.2, Height: 3,
|
|
34
|
+
Openings: [
|
|
35
|
+
{ Name: 'W-01 Window', Width: 1.2, Height: 1.5, Position: [1.5, 0, 0.9] },
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
const eastWall = bim.create.addIfcWall(h, gf, {
|
|
39
|
+
Name: 'East Wall', Description: 'Exterior east façade', ObjectType: 'Basic Wall:Exterior - 200mm',
|
|
40
|
+
Start: [5, 0, 0], End: [5, 8, 0], Thickness: 0.2, Height: 3,
|
|
41
|
+
Openings: [
|
|
42
|
+
{ Name: 'D-01 Entrance Door', Width: 0.9, Height: 2.1, Position: [3, 0, 0] },
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
const northWall = bim.create.addIfcWall(h, gf, {
|
|
46
|
+
Name: 'North Wall', Description: 'Exterior north façade', ObjectType: 'Basic Wall:Exterior - 200mm',
|
|
47
|
+
Start: [5, 8, 0], End: [0, 8, 0], Thickness: 0.2, Height: 3,
|
|
48
|
+
Openings: [
|
|
49
|
+
{ Name: 'W-02 Window', Width: 1.4, Height: 1.5, Position: [1.8, 0, 0.9] },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
const westWall = bim.create.addIfcWall(h, gf, {
|
|
53
|
+
Name: 'West Wall', Description: 'Exterior west façade', ObjectType: 'Basic Wall:Exterior - 200mm',
|
|
54
|
+
Start: [0, 8, 0], End: [0, 0, 0], Thickness: 0.2, Height: 3,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Wall colours and materials
|
|
58
|
+
for (const wId of [southWall, eastWall, northWall, westWall]) {
|
|
59
|
+
bim.create.setColor(h, wId, 'Plaster - Beige', [0.92, 0.88, 0.80]);
|
|
60
|
+
bim.create.addIfcMaterial(h, wId, {
|
|
61
|
+
Name: 'Exterior Wall Assembly',
|
|
62
|
+
Layers: [
|
|
63
|
+
{ Name: 'Gypsum Board', Thickness: 0.013, Category: 'Finish' },
|
|
64
|
+
{ Name: 'Mineral Wool Insulation', Thickness: 0.08, Category: 'Insulation' },
|
|
65
|
+
{ Name: 'Concrete C30/37', Thickness: 0.2, Category: 'Structural' },
|
|
66
|
+
{ Name: 'External Render', Thickness: 0.015, Category: 'Finish' },
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Wall properties & quantities (all walls share the same spec)
|
|
72
|
+
for (const [wId, wName, wLen] of [
|
|
73
|
+
[southWall, 'South Wall', 5],
|
|
74
|
+
[eastWall, 'East Wall', 8],
|
|
75
|
+
[northWall, 'North Wall', 5],
|
|
76
|
+
[westWall, 'West Wall', 8],
|
|
77
|
+
] as [number, string, number][]) {
|
|
78
|
+
bim.create.addIfcPropertySet(h, wId, {
|
|
79
|
+
Name: 'Pset_WallCommon',
|
|
80
|
+
Properties: [
|
|
81
|
+
{ Name: 'Reference', NominalValue: 'Exterior - 200mm', Type: 'IfcIdentifier' },
|
|
82
|
+
{ Name: 'IsExternal', NominalValue: true, Type: 'IfcBoolean' },
|
|
83
|
+
{ Name: 'LoadBearing', NominalValue: true, Type: 'IfcBoolean' },
|
|
84
|
+
{ Name: 'FireRating', NominalValue: 'REI60', Type: 'IfcLabel' },
|
|
85
|
+
{ Name: 'AcousticRating', NominalValue: 'STC 45', Type: 'IfcLabel' },
|
|
86
|
+
{ Name: 'ThermalTransmittance', NominalValue: 0.25, Type: 'IfcReal' },
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
bim.create.addIfcElementQuantity(h, wId, {
|
|
90
|
+
Name: 'Qto_WallBaseQuantities',
|
|
91
|
+
Quantities: [
|
|
92
|
+
{ Name: 'Length', Value: wLen, Kind: 'IfcQuantityLength' },
|
|
93
|
+
{ Name: 'Height', Value: 3, Kind: 'IfcQuantityLength' },
|
|
94
|
+
{ Name: 'Width', Value: 0.2, Kind: 'IfcQuantityLength' },
|
|
95
|
+
{ Name: 'GrossSideArea', Value: wLen * 3, Kind: 'IfcQuantityArea' },
|
|
96
|
+
{ Name: 'GrossVolume', Value: wLen * 3 * 0.2, Kind: 'IfcQuantityVolume' },
|
|
97
|
+
],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── 4. Floor slab ──────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
const slab = bim.create.addIfcSlab(h, gf, {
|
|
104
|
+
Name: 'Ground Floor Slab', Description: 'Reinforced concrete floor slab', ObjectType: 'Floor:Concrete - 300mm',
|
|
105
|
+
Position: [0, 0, -0.3], Thickness: 0.3, Width: 5, Depth: 8,
|
|
106
|
+
});
|
|
107
|
+
bim.create.setColor(h, slab, 'Concrete - Grey', [0.65, 0.65, 0.65]);
|
|
108
|
+
bim.create.addIfcMaterial(h, slab, {
|
|
109
|
+
Name: 'Floor Slab Assembly',
|
|
110
|
+
Layers: [
|
|
111
|
+
{ Name: 'Ceramic Tile', Thickness: 0.01, Category: 'Finish' },
|
|
112
|
+
{ Name: 'Screed', Thickness: 0.05, Category: 'Finish' },
|
|
113
|
+
{ Name: 'Reinforced Concrete C30/37', Thickness: 0.24, Category: 'Structural' },
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
bim.create.addIfcPropertySet(h, slab, {
|
|
117
|
+
Name: 'Pset_SlabCommon',
|
|
118
|
+
Properties: [
|
|
119
|
+
{ Name: 'Reference', NominalValue: 'Concrete - 300mm', Type: 'IfcIdentifier' },
|
|
120
|
+
{ Name: 'IsExternal', NominalValue: false, Type: 'IfcBoolean' },
|
|
121
|
+
{ Name: 'LoadBearing', NominalValue: true, Type: 'IfcBoolean' },
|
|
122
|
+
{ Name: 'FireRating', NominalValue: 'REI90', Type: 'IfcLabel' },
|
|
123
|
+
{ Name: 'AcousticRating', NominalValue: 'STC 52', Type: 'IfcLabel' },
|
|
124
|
+
{ Name: 'Combustible', NominalValue: false, Type: 'IfcBoolean' },
|
|
125
|
+
],
|
|
126
|
+
});
|
|
127
|
+
bim.create.addIfcElementQuantity(h, slab, {
|
|
128
|
+
Name: 'Qto_SlabBaseQuantities',
|
|
129
|
+
Quantities: [
|
|
130
|
+
{ Name: 'Width', Value: 0.3, Kind: 'IfcQuantityLength' },
|
|
131
|
+
{ Name: 'GrossArea', Value: 40, Kind: 'IfcQuantityArea' },
|
|
132
|
+
{ Name: 'NetArea', Value: 40, Kind: 'IfcQuantityArea' },
|
|
133
|
+
{ Name: 'GrossVolume', Value: 12, Kind: 'IfcQuantityVolume' },
|
|
134
|
+
{ Name: 'GrossWeight', Value: 28800, Kind: 'IfcQuantityWeight' },
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// ─── 5. Columns at corners ──────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
const columnPositions: [string, number, number][] = [
|
|
141
|
+
['C-01 SW', 0.1, 0.1],
|
|
142
|
+
['C-02 SE', 4.7, 0.1],
|
|
143
|
+
['C-03 NE', 4.7, 7.7],
|
|
144
|
+
['C-04 NW', 0.1, 7.7],
|
|
145
|
+
];
|
|
146
|
+
for (const [cName, cx, cy] of columnPositions) {
|
|
147
|
+
const colId = bim.create.addIfcColumn(h, gf, {
|
|
148
|
+
Name: cName, Description: 'Reinforced concrete column', ObjectType: 'Column:Concrete 300x300',
|
|
149
|
+
Position: [cx, cy, 0], Width: 0.3, Depth: 0.3, Height: 3,
|
|
150
|
+
});
|
|
151
|
+
bim.create.setColor(h, colId, 'Concrete - Light', [0.72, 0.72, 0.74]);
|
|
152
|
+
bim.create.addIfcMaterial(h, colId, { Name: 'Reinforced Concrete C30/37', Category: 'Concrete' });
|
|
153
|
+
bim.create.addIfcPropertySet(h, colId, {
|
|
154
|
+
Name: 'Pset_ColumnCommon',
|
|
155
|
+
Properties: [
|
|
156
|
+
{ Name: 'Reference', NominalValue: 'Concrete 300x300', Type: 'IfcIdentifier' },
|
|
157
|
+
{ Name: 'LoadBearing', NominalValue: true, Type: 'IfcBoolean' },
|
|
158
|
+
{ Name: 'IsExternal', NominalValue: false, Type: 'IfcBoolean' },
|
|
159
|
+
{ Name: 'FireRating', NominalValue: 'R120', Type: 'IfcLabel' },
|
|
160
|
+
{ Name: 'Slope', NominalValue: 0, Type: 'IfcInteger' },
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
bim.create.addIfcElementQuantity(h, colId, {
|
|
164
|
+
Name: 'Qto_ColumnBaseQuantities',
|
|
165
|
+
Quantities: [
|
|
166
|
+
{ Name: 'Length', Value: 3, Kind: 'IfcQuantityLength' },
|
|
167
|
+
{ Name: 'CrossSectionArea', Value: 0.09, Kind: 'IfcQuantityArea' },
|
|
168
|
+
{ Name: 'GrossVolume', Value: 0.27, Kind: 'IfcQuantityVolume' },
|
|
169
|
+
{ Name: 'GrossWeight', Value: 648, Kind: 'IfcQuantityWeight' },
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ─── 6. Beams along the top ─────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
const beamDefs: [string, [number, number, number], [number, number, number]][] = [
|
|
177
|
+
['B-01 South Beam', [0, 0, 3], [5, 0, 3]],
|
|
178
|
+
['B-02 North Beam', [0, 8, 3], [5, 8, 3]],
|
|
179
|
+
];
|
|
180
|
+
for (const [bName, bStart, bEnd] of beamDefs) {
|
|
181
|
+
const bLen = Math.sqrt(
|
|
182
|
+
(bEnd[0] - bStart[0]) ** 2 + (bEnd[1] - bStart[1]) ** 2 + (bEnd[2] - bStart[2]) ** 2,
|
|
183
|
+
);
|
|
184
|
+
const beamId = bim.create.addIfcBeam(h, gf, {
|
|
185
|
+
Name: bName, Description: 'Steel I-beam', ObjectType: 'Beam:IPE 200',
|
|
186
|
+
Start: bStart, End: bEnd, Width: 0.2, Height: 0.4,
|
|
187
|
+
});
|
|
188
|
+
bim.create.setColor(h, beamId, 'Steel - Grey', [0.55, 0.55, 0.58]);
|
|
189
|
+
bim.create.addIfcMaterial(h, beamId, { Name: 'Structural Steel S235', Category: 'Steel' });
|
|
190
|
+
bim.create.addIfcPropertySet(h, beamId, {
|
|
191
|
+
Name: 'Pset_BeamCommon',
|
|
192
|
+
Properties: [
|
|
193
|
+
{ Name: 'Reference', NominalValue: 'IPE 200', Type: 'IfcIdentifier' },
|
|
194
|
+
{ Name: 'LoadBearing', NominalValue: true, Type: 'IfcBoolean' },
|
|
195
|
+
{ Name: 'IsExternal', NominalValue: false, Type: 'IfcBoolean' },
|
|
196
|
+
{ Name: 'FireRating', NominalValue: 'R60', Type: 'IfcLabel' },
|
|
197
|
+
{ Name: 'Span', NominalValue: bLen, Type: 'IfcReal' },
|
|
198
|
+
],
|
|
199
|
+
});
|
|
200
|
+
bim.create.addIfcElementQuantity(h, beamId, {
|
|
201
|
+
Name: 'Qto_BeamBaseQuantities',
|
|
202
|
+
Quantities: [
|
|
203
|
+
{ Name: 'Length', Value: bLen, Kind: 'IfcQuantityLength' },
|
|
204
|
+
{ Name: 'CrossSectionArea', Value: 0.08, Kind: 'IfcQuantityArea' },
|
|
205
|
+
{ Name: 'GrossVolume', Value: bLen * 0.08, Kind: 'IfcQuantityVolume' },
|
|
206
|
+
{ Name: 'GrossWeight', Value: bLen * 0.08 * 7850, Kind: 'IfcQuantityWeight' },
|
|
207
|
+
],
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── 7. Stair ───────────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
const numRisers = 17;
|
|
214
|
+
const riserH = 0.176;
|
|
215
|
+
const treadL = 0.28;
|
|
216
|
+
const stairW = 1.0;
|
|
217
|
+
// Run along +Y (the 8 m axis) so the 4.76 m run fits comfortably.
|
|
218
|
+
// Position is stair origin; with Direction = PI/2 the width extends toward −X.
|
|
219
|
+
// Place at x = stairW so the stair sits flush against the west wall (x = 0).
|
|
220
|
+
const stairId = bim.create.addIfcStair(h, gf, {
|
|
221
|
+
Name: 'ST-01 Main Stair', Description: 'Straight-run concrete stair', ObjectType: 'Stair:Concrete - Straight Run',
|
|
222
|
+
Position: [stairW, 1, 0], Direction: Math.PI / 2,
|
|
223
|
+
NumberOfRisers: numRisers, RiserHeight: riserH, TreadLength: treadL, Width: stairW,
|
|
224
|
+
});
|
|
225
|
+
bim.create.setColor(h, stairId, 'Concrete - Warm', [0.80, 0.78, 0.74]);
|
|
226
|
+
bim.create.addIfcMaterial(h, stairId, { Name: 'Reinforced Concrete C25/30', Category: 'Concrete' });
|
|
227
|
+
bim.create.addIfcPropertySet(h, stairId, {
|
|
228
|
+
Name: 'Pset_StairCommon',
|
|
229
|
+
Properties: [
|
|
230
|
+
{ Name: 'Reference', NominalValue: 'Concrete - Straight Run', Type: 'IfcIdentifier' },
|
|
231
|
+
{ Name: 'FireRating', NominalValue: 'REI60', Type: 'IfcLabel' },
|
|
232
|
+
{ Name: 'NumberOfRiser', NominalValue: numRisers, Type: 'IfcInteger' },
|
|
233
|
+
{ Name: 'NumberOfTreads', NominalValue: numRisers - 1, Type: 'IfcInteger' },
|
|
234
|
+
{ Name: 'RiserHeight', NominalValue: riserH, Type: 'IfcReal' },
|
|
235
|
+
{ Name: 'TreadLength', NominalValue: treadL, Type: 'IfcReal' },
|
|
236
|
+
{ Name: 'IsExternal', NominalValue: false, Type: 'IfcBoolean' },
|
|
237
|
+
{ Name: 'HandicapAccessible', NominalValue: false, Type: 'IfcBoolean' },
|
|
238
|
+
],
|
|
239
|
+
});
|
|
240
|
+
bim.create.addIfcElementQuantity(h, stairId, {
|
|
241
|
+
Name: 'Qto_StairBaseQuantities',
|
|
242
|
+
Quantities: [
|
|
243
|
+
{ Name: 'Length', Value: numRisers * treadL, Kind: 'IfcQuantityLength' },
|
|
244
|
+
{ Name: 'GrossVolume', Value: numRisers * treadL * stairW * riserH * 0.5, Kind: 'IfcQuantityVolume' },
|
|
245
|
+
],
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ─── 8. Second floor ─────────────────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
const ff = bim.create.addIfcBuildingStorey(h, { Name: 'First Floor', Elevation: 3 });
|
|
251
|
+
|
|
252
|
+
// Floor slab at z = 3 with stair opening
|
|
253
|
+
// Opening must be long enough for headroom (≥ 2.1 m) over the ascending stair.
|
|
254
|
+
// At the slab level the stair is still rising, so the opening needs to extend
|
|
255
|
+
// back far enough that a person standing on a lower step has full clearance.
|
|
256
|
+
const stairRunEnd = 1 + numRisers * treadL; // y ≈ 5.76
|
|
257
|
+
const stairOpenLen = 4.2; // ~1.5 × run ensures headroom
|
|
258
|
+
const stairOpenY = stairRunEnd - stairOpenLen / 2; // center near arrival end
|
|
259
|
+
const ffSlab = bim.create.addIfcSlab(h, ff, {
|
|
260
|
+
Name: 'First Floor Slab', Description: 'Reinforced concrete floor slab', ObjectType: 'Floor:Concrete - 300mm',
|
|
261
|
+
Position: [0, 0, 2.7], Thickness: 0.3, Width: 5, Depth: 8,
|
|
262
|
+
Openings: [
|
|
263
|
+
{ Name: 'Stair Opening', Width: stairW + 0.2, Height: stairOpenLen, Position: [stairW / 2, stairOpenY, 0] },
|
|
264
|
+
],
|
|
265
|
+
});
|
|
266
|
+
bim.create.setColor(h, ffSlab, 'Concrete - Grey', [0.65, 0.65, 0.65]);
|
|
267
|
+
bim.create.addIfcMaterial(h, ffSlab, {
|
|
268
|
+
Name: 'Floor Slab Assembly',
|
|
269
|
+
Layers: [
|
|
270
|
+
{ Name: 'Ceramic Tile', Thickness: 0.01, Category: 'Finish' },
|
|
271
|
+
{ Name: 'Screed', Thickness: 0.05, Category: 'Finish' },
|
|
272
|
+
{ Name: 'Reinforced Concrete C30/37', Thickness: 0.24, Category: 'Structural' },
|
|
273
|
+
],
|
|
274
|
+
});
|
|
275
|
+
bim.create.addIfcPropertySet(h, ffSlab, {
|
|
276
|
+
Name: 'Pset_SlabCommon',
|
|
277
|
+
Properties: [
|
|
278
|
+
{ Name: 'Reference', NominalValue: 'Concrete - 300mm', Type: 'IfcIdentifier' },
|
|
279
|
+
{ Name: 'IsExternal', NominalValue: false, Type: 'IfcBoolean' },
|
|
280
|
+
{ Name: 'LoadBearing', NominalValue: true, Type: 'IfcBoolean' },
|
|
281
|
+
{ Name: 'FireRating', NominalValue: 'REI90', Type: 'IfcLabel' },
|
|
282
|
+
],
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Second floor walls — same footprint, window on east instead of door
|
|
286
|
+
const ffSouthWall = bim.create.addIfcWall(h, ff, {
|
|
287
|
+
Name: 'South Wall', Description: 'Exterior south façade', ObjectType: 'Basic Wall:Exterior - 200mm',
|
|
288
|
+
Start: [0, 0, 3], End: [5, 0, 3], Thickness: 0.2, Height: 3,
|
|
289
|
+
Openings: [
|
|
290
|
+
{ Name: 'W-03 Window', Width: 1.2, Height: 1.5, Position: [1.5, 0, 0.9] },
|
|
291
|
+
],
|
|
292
|
+
});
|
|
293
|
+
const ffEastWall = bim.create.addIfcWall(h, ff, {
|
|
294
|
+
Name: 'East Wall', Description: 'Exterior east façade', ObjectType: 'Basic Wall:Exterior - 200mm',
|
|
295
|
+
Start: [5, 0, 3], End: [5, 8, 3], Thickness: 0.2, Height: 3,
|
|
296
|
+
Openings: [
|
|
297
|
+
{ Name: 'W-04 Window', Width: 1.4, Height: 1.5, Position: [3, 0, 0.9] },
|
|
298
|
+
],
|
|
299
|
+
});
|
|
300
|
+
const ffNorthWall = bim.create.addIfcWall(h, ff, {
|
|
301
|
+
Name: 'North Wall', Description: 'Exterior north façade', ObjectType: 'Basic Wall:Exterior - 200mm',
|
|
302
|
+
Start: [5, 8, 3], End: [0, 8, 3], Thickness: 0.2, Height: 3,
|
|
303
|
+
Openings: [
|
|
304
|
+
{ Name: 'W-05 Window', Width: 1.4, Height: 1.5, Position: [1.8, 0, 0.9] },
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
const ffWestWall = bim.create.addIfcWall(h, ff, {
|
|
308
|
+
Name: 'West Wall', Description: 'Exterior west façade', ObjectType: 'Basic Wall:Exterior - 200mm',
|
|
309
|
+
Start: [0, 8, 3], End: [0, 0, 3], Thickness: 0.2, Height: 3,
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
for (const wId of [ffSouthWall, ffEastWall, ffNorthWall, ffWestWall]) {
|
|
313
|
+
bim.create.setColor(h, wId, 'Plaster - Beige', [0.92, 0.88, 0.80]);
|
|
314
|
+
bim.create.addIfcMaterial(h, wId, {
|
|
315
|
+
Name: 'Exterior Wall Assembly',
|
|
316
|
+
Layers: [
|
|
317
|
+
{ Name: 'Gypsum Board', Thickness: 0.013, Category: 'Finish' },
|
|
318
|
+
{ Name: 'Mineral Wool Insulation', Thickness: 0.08, Category: 'Insulation' },
|
|
319
|
+
{ Name: 'Concrete C30/37', Thickness: 0.2, Category: 'Structural' },
|
|
320
|
+
{ Name: 'External Render', Thickness: 0.015, Category: 'Finish' },
|
|
321
|
+
],
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
for (const [wId, wName, wLen] of [
|
|
326
|
+
[ffSouthWall, 'South Wall', 5],
|
|
327
|
+
[ffEastWall, 'East Wall', 8],
|
|
328
|
+
[ffNorthWall, 'North Wall', 5],
|
|
329
|
+
[ffWestWall, 'West Wall', 8],
|
|
330
|
+
] as [number, string, number][]) {
|
|
331
|
+
bim.create.addIfcPropertySet(h, wId, {
|
|
332
|
+
Name: 'Pset_WallCommon',
|
|
333
|
+
Properties: [
|
|
334
|
+
{ Name: 'Reference', NominalValue: 'Exterior - 200mm', Type: 'IfcIdentifier' },
|
|
335
|
+
{ Name: 'IsExternal', NominalValue: true, Type: 'IfcBoolean' },
|
|
336
|
+
{ Name: 'LoadBearing', NominalValue: true, Type: 'IfcBoolean' },
|
|
337
|
+
{ Name: 'FireRating', NominalValue: 'REI60', Type: 'IfcLabel' },
|
|
338
|
+
{ Name: 'ThermalTransmittance', NominalValue: 0.25, Type: 'IfcReal' },
|
|
339
|
+
],
|
|
340
|
+
});
|
|
341
|
+
bim.create.addIfcElementQuantity(h, wId, {
|
|
342
|
+
Name: 'Qto_WallBaseQuantities',
|
|
343
|
+
Quantities: [
|
|
344
|
+
{ Name: 'Length', Value: wLen, Kind: 'IfcQuantityLength' },
|
|
345
|
+
{ Name: 'Height', Value: 3, Kind: 'IfcQuantityLength' },
|
|
346
|
+
{ Name: 'Width', Value: 0.2, Kind: 'IfcQuantityLength' },
|
|
347
|
+
{ Name: 'GrossSideArea', Value: wLen * 3, Kind: 'IfcQuantityArea' },
|
|
348
|
+
{ Name: 'GrossVolume', Value: wLen * 3 * 0.2, Kind: 'IfcQuantityVolume' },
|
|
349
|
+
],
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Second floor columns
|
|
354
|
+
for (const [cName, cx, cy] of columnPositions) {
|
|
355
|
+
const colId = bim.create.addIfcColumn(h, ff, {
|
|
356
|
+
Name: cName, Description: 'Reinforced concrete column', ObjectType: 'Column:Concrete 300x300',
|
|
357
|
+
Position: [cx, cy, 3], Width: 0.3, Depth: 0.3, Height: 3,
|
|
358
|
+
});
|
|
359
|
+
bim.create.setColor(h, colId, 'Concrete - Light', [0.72, 0.72, 0.74]);
|
|
360
|
+
bim.create.addIfcMaterial(h, colId, { Name: 'Reinforced Concrete C30/37', Category: 'Concrete' });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Second floor beams
|
|
364
|
+
for (const [bName, bStart, bEnd] of beamDefs) {
|
|
365
|
+
const beamId = bim.create.addIfcBeam(h, ff, {
|
|
366
|
+
Name: bName, Description: 'Steel I-beam', ObjectType: 'Beam:IPE 200',
|
|
367
|
+
Start: [bStart[0], bStart[1], 6], End: [bEnd[0], bEnd[1], 6], Width: 0.2, Height: 0.4,
|
|
368
|
+
});
|
|
369
|
+
bim.create.setColor(h, beamId, 'Steel - Grey', [0.55, 0.55, 0.58]);
|
|
370
|
+
bim.create.addIfcMaterial(h, beamId, { Name: 'Structural Steel S235', Category: 'Steel' });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ─── 9. Parametric timber gridshell roof ─────────────────────────────
|
|
374
|
+
//
|
|
375
|
+
// A doubly-curved gridshell spanning the 5 × 8 m footprint.
|
|
376
|
+
// Two families of diagonal timber laths form a diamond lattice.
|
|
377
|
+
// Three surface shapes are provided — uncomment one at a time.
|
|
378
|
+
|
|
379
|
+
const ROOF_W = 5; // building width (X)
|
|
380
|
+
const ROOF_D = 8; // building depth (Y)
|
|
381
|
+
const WALL_H = 6; // wall-top elevation (2 storeys × 3 m)
|
|
382
|
+
const CROWN = 1.8; // rise above walls
|
|
383
|
+
const NU = 10; // grid divisions along X
|
|
384
|
+
const NV = 16; // grid divisions along Y
|
|
385
|
+
const LATH_W = 0.06; // lath width 60 mm
|
|
386
|
+
const LATH_D = 0.08; // lath depth 80 mm
|
|
387
|
+
|
|
388
|
+
// ── Shape A: sinusoidal dome — smooth symmetric shell ──────────────
|
|
389
|
+
// function roofZ(u: number, v: number): number {
|
|
390
|
+
// return WALL_H + CROWN * Math.sin(Math.PI * u) * Math.sin(Math.PI * v);
|
|
391
|
+
// }
|
|
392
|
+
|
|
393
|
+
// ── Shape B: rolling dunes — three asymmetric peaks ────────────────
|
|
394
|
+
// function roofZ(u: number, v: number): number {
|
|
395
|
+
// const env = Math.sin(Math.PI * u) * Math.sin(Math.PI * v);
|
|
396
|
+
// const p1 = 1.0 * Math.exp(-((u - 0.3) ** 2 + (v - 0.30) ** 2) / 0.04);
|
|
397
|
+
// const p2 = 0.65 * Math.exp(-((u - 0.72) ** 2 + (v - 0.55) ** 2) / 0.06);
|
|
398
|
+
// const p3 = 0.45 * Math.exp(-((u - 0.40) ** 2 + (v - 0.80) ** 2) / 0.03);
|
|
399
|
+
// return WALL_H + CROWN * env * (0.25 + p1 + p2 + p3);
|
|
400
|
+
// }
|
|
401
|
+
|
|
402
|
+
// ── Shape C: twisted hypar — saddle with raised diagonal corners ───
|
|
403
|
+
function roofZ(u: number, v: number): number {
|
|
404
|
+
const env = Math.sin(Math.PI * u) * Math.sin(Math.PI * v);
|
|
405
|
+
const twist = (2 * u - 1) * (2 * v - 1);
|
|
406
|
+
return WALL_H + CROWN * env * (1.0 + 0.8 * twist);
|
|
407
|
+
}
|
|
408
|
+
// Build grid of surface points
|
|
409
|
+
const grid: [number, number, number][][] = [];
|
|
410
|
+
for (let i = 0; i <= NU; i++) {
|
|
411
|
+
const row: [number, number, number][] = [];
|
|
412
|
+
for (let j = 0; j <= NV; j++) {
|
|
413
|
+
const u = i / NU, v = j / NV;
|
|
414
|
+
row.push([u * ROOF_W, v * ROOF_D, roofZ(u, v)]);
|
|
415
|
+
}
|
|
416
|
+
grid.push(row);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
let lathCount = 0;
|
|
420
|
+
|
|
421
|
+
// Family A — diagonal (i+1, j+1): warm larch tone
|
|
422
|
+
for (let d = -NV; d <= NU; d++) {
|
|
423
|
+
const i0 = Math.max(0, d);
|
|
424
|
+
const i1 = Math.min(NU, NV + d);
|
|
425
|
+
for (let i = i0; i < i1; i++) {
|
|
426
|
+
const j = i - d;
|
|
427
|
+
if (j < 0 || j >= NV) continue;
|
|
428
|
+
const s = grid[i][j], e = grid[i + 1][j + 1];
|
|
429
|
+
const id = bim.create.addIfcBeam(h, ff, {
|
|
430
|
+
Name: `GL-A${(d + NV).toString().padStart(2, '0')}/${i - i0}`,
|
|
431
|
+
Description: 'Gridshell lath family A', ObjectType: 'Timber Lath:GL24h 60x80',
|
|
432
|
+
Start: s, End: e, Width: LATH_W, Height: LATH_D,
|
|
433
|
+
});
|
|
434
|
+
bim.create.setColor(h, id, 'Timber - Larch', [0.82, 0.62, 0.38]);
|
|
435
|
+
bim.create.addIfcMaterial(h, id, { Name: 'Glulam GL24h Spruce', Category: 'Timber' });
|
|
436
|
+
lathCount++;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Family B — diagonal (i+1, j-1): darker oak tone
|
|
441
|
+
for (let s = 0; s <= NU + NV; s++) {
|
|
442
|
+
const i0 = Math.max(0, s - NV);
|
|
443
|
+
const i1 = Math.min(NU, s);
|
|
444
|
+
for (let i = i0; i < i1; i++) {
|
|
445
|
+
const j = s - i;
|
|
446
|
+
if (j <= 0 || j > NV) continue;
|
|
447
|
+
const p0 = grid[i][j], p1 = grid[i + 1][j - 1];
|
|
448
|
+
const id = bim.create.addIfcBeam(h, ff, {
|
|
449
|
+
Name: `GL-B${s.toString().padStart(2, '0')}/${i - i0}`,
|
|
450
|
+
Description: 'Gridshell lath family B', ObjectType: 'Timber Lath:GL24h 60x80',
|
|
451
|
+
Start: p0, End: p1, Width: LATH_W, Height: LATH_D,
|
|
452
|
+
});
|
|
453
|
+
bim.create.setColor(h, id, 'Timber - Oak', [0.72, 0.52, 0.30]);
|
|
454
|
+
bim.create.addIfcMaterial(h, id, { Name: 'Glulam GL24h Spruce', Category: 'Timber' });
|
|
455
|
+
lathCount++;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Perimeter ring beam at wall top — ties laths together
|
|
460
|
+
const RING_N = 2 * (NU + NV);
|
|
461
|
+
const ringPts: [number, number, number][] = [];
|
|
462
|
+
for (let i = 0; i <= NU; i++) ringPts.push(grid[i][0]); // south edge
|
|
463
|
+
for (let j = 1; j <= NV; j++) ringPts.push(grid[NU][j]); // east edge
|
|
464
|
+
for (let i = NU - 1; i >= 0; i--) ringPts.push(grid[i][NV]); // north edge
|
|
465
|
+
for (let j = NV - 1; j >= 1; j--) ringPts.push(grid[0][j]); // west edge
|
|
466
|
+
for (let k = 0; k < ringPts.length; k++) {
|
|
467
|
+
const rStart = ringPts[k];
|
|
468
|
+
const rEnd = ringPts[(k + 1) % ringPts.length];
|
|
469
|
+
const rId = bim.create.addIfcBeam(h, ff, {
|
|
470
|
+
Name: `GL-Ring/${k.toString().padStart(2, '0')}`,
|
|
471
|
+
Description: 'Gridshell perimeter ring beam', ObjectType: 'Beam:GL28h 120x200',
|
|
472
|
+
Start: rStart, End: rEnd, Width: 0.12, Height: 0.20,
|
|
473
|
+
});
|
|
474
|
+
bim.create.setColor(h, rId, 'Timber - Dark', [0.45, 0.32, 0.20]);
|
|
475
|
+
bim.create.addIfcMaterial(h, rId, { Name: 'Glulam GL28h Larch', Category: 'Timber' });
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
console.log(`Gridshell roof: ${lathCount} laths + ${ringPts.length} ring segments`);
|
|
479
|
+
|
|
480
|
+
// ─── 10. Generate, preview, download ────────────────────────────────────
|
|
481
|
+
|
|
482
|
+
const result = bim.create.toIfc(h);
|
|
483
|
+
|
|
484
|
+
console.log(
|
|
485
|
+
`Created ${result.entities.length} entities, ` +
|
|
486
|
+
`${result.stats.entityCount} STEP lines, ` +
|
|
487
|
+
`${(result.stats.fileSize / 1024).toFixed(1)} KB`,
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
bim.model.loadIfc(result.content, 'sample-building.ifc');
|
|
491
|
+
bim.export.download(result.content, 'sample-building.ifc', 'application/x-step');
|
|
@@ -23,6 +23,8 @@ import mepEquipmentSchedule from './templates/mep-equipment-schedule.ts?raw';
|
|
|
23
23
|
import spaceValidation from './templates/space-validation.ts?raw';
|
|
24
24
|
import federationCompare from './templates/federation-compare.ts?raw';
|
|
25
25
|
import resetView from './templates/reset-view.ts?raw';
|
|
26
|
+
import createBuilding from './templates/create-building.ts?raw';
|
|
27
|
+
|
|
26
28
|
|
|
27
29
|
export interface ScriptTemplate {
|
|
28
30
|
name: string;
|
|
@@ -78,6 +80,12 @@ export const SCRIPT_TEMPLATES: ScriptTemplate[] = [
|
|
|
78
80
|
'Project Manager — compare multiple loaded models side by side: entity counts, type coverage, naming consistency, coordination issues',
|
|
79
81
|
code: stripModuleLine(federationCompare),
|
|
80
82
|
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Create building (IFC from scratch)',
|
|
85
|
+
description:
|
|
86
|
+
'Developer — generate a complete IFC file with walls, slab, columns, beams, stair, and parametric timber gridshell roof with diamond lattice',
|
|
87
|
+
code: stripModuleLine(createBuilding),
|
|
88
|
+
},
|
|
81
89
|
{
|
|
82
90
|
name: 'Reset view',
|
|
83
91
|
description: 'Utility — remove all color overrides and show all entities',
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import type { StoreApi } from './types.js';
|
|
6
6
|
import type { EntityRef, EntityData, PropertySetData, QuantitySetData, ExportBackendMethods } from '@ifc-lite/sdk';
|
|
7
7
|
import { EntityNode } from '@ifc-lite/query';
|
|
8
|
+
import { StepExporter, type StepExportOptions } from '@ifc-lite/export';
|
|
8
9
|
import { getModelForRef } from './model-compat.js';
|
|
9
10
|
|
|
10
11
|
/** Options for CSV export */
|
|
@@ -14,6 +15,26 @@ interface CsvOptions {
|
|
|
14
15
|
filename?: string;
|
|
15
16
|
}
|
|
16
17
|
|
|
18
|
+
|
|
19
|
+
/** Options for IFC STEP export */
|
|
20
|
+
interface IfcExportOptions {
|
|
21
|
+
schema?: 'IFC2X3' | 'IFC4' | 'IFC4X3';
|
|
22
|
+
filename?: string;
|
|
23
|
+
includeMutations?: boolean;
|
|
24
|
+
visibleOnly?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Validate that a value is an IfcExportOptions object. */
|
|
28
|
+
function isIfcExportOptions(v: unknown): v is IfcExportOptions {
|
|
29
|
+
if (v === null || typeof v !== 'object') return false;
|
|
30
|
+
const options = v as IfcExportOptions;
|
|
31
|
+
if (options.schema !== undefined && options.schema !== 'IFC2X3' && options.schema !== 'IFC4' && options.schema !== 'IFC4X3') return false;
|
|
32
|
+
if (options.filename !== undefined && typeof options.filename !== 'string') return false;
|
|
33
|
+
if (options.includeMutations !== undefined && typeof options.includeMutations !== 'boolean') return false;
|
|
34
|
+
if (options.visibleOnly !== undefined && typeof options.visibleOnly !== 'boolean') return false;
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
17
38
|
/**
|
|
18
39
|
* Validate that a value is a CsvOptions object.
|
|
19
40
|
*/
|
|
@@ -264,6 +285,66 @@ export function createExportAdapter(store: StoreApi): ExportBackendMethods {
|
|
|
264
285
|
return result;
|
|
265
286
|
},
|
|
266
287
|
|
|
288
|
+
ifc(rawRefs: unknown, rawOptions: unknown) {
|
|
289
|
+
const candidateOptions = rawOptions ?? {};
|
|
290
|
+
if (!isEntityRefArray(rawRefs)) {
|
|
291
|
+
throw new Error('export.ifc: first argument must be an array of entity references');
|
|
292
|
+
}
|
|
293
|
+
if (!isIfcExportOptions(candidateOptions)) {
|
|
294
|
+
throw new Error('export.ifc: second argument must be { schema?: IFC2X3|IFC4|IFC4X3, filename?: string, includeMutations?: boolean, visibleOnly?: boolean }');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const refs = normalizeRefs(rawRefs);
|
|
298
|
+
if (refs.length === 0) {
|
|
299
|
+
throw new Error('export.ifc: expected at least one entity reference');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const modelIds = new Set(refs.map(ref => ref.modelId));
|
|
303
|
+
if (modelIds.size !== 1) {
|
|
304
|
+
throw new Error('export.ifc: all entity references must belong to the same model');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const modelId = refs[0].modelId;
|
|
308
|
+
const state = store.getState();
|
|
309
|
+
const model = getModelForRef(state, modelId);
|
|
310
|
+
if (!model?.ifcDataStore) {
|
|
311
|
+
throw new Error(`export.ifc: model '${modelId}' is not loaded`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (model.ifcDataStore.schemaVersion === 'IFC5') {
|
|
315
|
+
throw new Error('export.ifc: IFC5 export is not supported by STEP exporter, use IFC2X3/IFC4/IFC4X3 models');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const options = candidateOptions;
|
|
319
|
+
const selectedExpressIds = new Set(refs.map(ref => ref.expressId));
|
|
320
|
+
const modelHidden = state.hiddenEntitiesByModel.get(modelId);
|
|
321
|
+
const modelIsolated = state.isolatedEntitiesByModel.get(modelId) ?? null;
|
|
322
|
+
|
|
323
|
+
const shouldLimitToSelection = selectedExpressIds.size < model.ifcDataStore.entityCount;
|
|
324
|
+
const visibleOnly = options.visibleOnly === true || shouldLimitToSelection;
|
|
325
|
+
const hiddenEntityIds = shouldLimitToSelection
|
|
326
|
+
? new Set<number>()
|
|
327
|
+
: new Set<number>(modelHidden ?? []);
|
|
328
|
+
const isolatedEntityIds = shouldLimitToSelection
|
|
329
|
+
? selectedExpressIds
|
|
330
|
+
: modelIsolated;
|
|
331
|
+
|
|
332
|
+
const exporter = new StepExporter(model.ifcDataStore);
|
|
333
|
+
const exportOptions: StepExportOptions = {
|
|
334
|
+
schema: options.schema ?? model.ifcDataStore.schemaVersion,
|
|
335
|
+
includeGeometry: true,
|
|
336
|
+
includeProperties: true,
|
|
337
|
+
includeQuantities: true,
|
|
338
|
+
includeRelationships: true,
|
|
339
|
+
applyMutations: options.includeMutations ?? true,
|
|
340
|
+
visibleOnly,
|
|
341
|
+
hiddenEntityIds,
|
|
342
|
+
isolatedEntityIds,
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
return exporter.export(exportOptions).content;
|
|
346
|
+
},
|
|
347
|
+
|
|
267
348
|
download(content: string, filename: string, mimeType?: string) {
|
|
268
349
|
triggerDownload(content, filename, mimeType ?? 'text/plain');
|
|
269
350
|
return undefined;
|
|
@@ -273,6 +354,9 @@ export function createExportAdapter(store: StoreApi): ExportBackendMethods {
|
|
|
273
354
|
|
|
274
355
|
/** Trigger a browser file download */
|
|
275
356
|
function triggerDownload(content: string, filename: string, mimeType: string): void {
|
|
357
|
+
if (typeof document === 'undefined') {
|
|
358
|
+
throw new Error('download() requires a browser environment (document is unavailable)');
|
|
359
|
+
}
|
|
276
360
|
const blob = new Blob([content], { type: mimeType });
|
|
277
361
|
const url = URL.createObjectURL(blob);
|
|
278
362
|
const a = document.createElement('a');
|
|
@@ -14,7 +14,7 @@ function isLensConfig(v: unknown): v is Record<string, unknown> {
|
|
|
14
14
|
export function createLensAdapter(store: StoreApi): LensBackendMethods {
|
|
15
15
|
return {
|
|
16
16
|
presets() {
|
|
17
|
-
return BUILTIN_LENSES;
|
|
17
|
+
return [...BUILTIN_LENSES];
|
|
18
18
|
},
|
|
19
19
|
create(config: unknown) {
|
|
20
20
|
if (!isLensConfig(config)) {
|
|
@@ -28,5 +28,13 @@ export function createModelAdapter(store: StoreApi): ModelBackendMethods {
|
|
|
28
28
|
// For legacy single-model, return the sentinel ID when no active model is set
|
|
29
29
|
return state.activeModelId ?? (state.models.size === 0 && state.ifcDataStore ? LEGACY_MODEL_ID : null);
|
|
30
30
|
},
|
|
31
|
+
|
|
32
|
+
loadIfc(content: string, filename: string) {
|
|
33
|
+
// Create a File from IFC content and dispatch the standard load event.
|
|
34
|
+
// MainToolbar listens for 'ifc-lite:load-file' and routes to loadFile().
|
|
35
|
+
const blob = new Blob([content], { type: 'application/x-step' });
|
|
36
|
+
const file = new File([blob], filename || 'created.ifc', { type: 'application/x-step' });
|
|
37
|
+
window.dispatchEvent(new CustomEvent('ifc-lite:load-file', { detail: file }));
|
|
38
|
+
},
|
|
31
39
|
};
|
|
32
40
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
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
4
|
|
|
5
|
-
import type { EntityRef, SectionPlane, CameraState, ViewerBackendMethods
|
|
5
|
+
import type { EntityRef, SectionPlane, CameraState, ViewerBackendMethods } from '@ifc-lite/sdk';
|
|
6
6
|
import type { StoreApi } from './types.js';
|
|
7
7
|
import { getModelForRef } from './model-compat.js';
|
|
8
8
|
|