isard 1.5.0 → 1.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.
Files changed (108) hide show
  1. package/CHANGELOG.md +165 -1
  2. package/README.md +3 -2
  3. package/dist/basemap/furniture.d.ts +8 -0
  4. package/dist/basemap/furniture.js +26 -0
  5. package/dist/basemap/furniture.js.map +1 -0
  6. package/dist/basemap/trees.d.ts +35 -8
  7. package/dist/basemap/trees.js +105 -52
  8. package/dist/basemap/trees.js.map +1 -1
  9. package/dist/basemap/vectorstyle.d.ts +9 -3
  10. package/dist/basemap/vectorstyle.js +5 -6
  11. package/dist/basemap/vectorstyle.js.map +1 -1
  12. package/dist/basemap/vectortiles/context.d.ts +3 -0
  13. package/dist/basemap/vectortiles/context.js.map +1 -1
  14. package/dist/basemap/vectortiles/extrusions.d.ts +4 -0
  15. package/dist/basemap/vectortiles/extrusions.js +17 -2
  16. package/dist/basemap/vectortiles/extrusions.js.map +1 -1
  17. package/dist/basemap/vectortiles/labels.d.ts +3 -0
  18. package/dist/basemap/vectortiles/labels.js +42 -17
  19. package/dist/basemap/vectortiles/labels.js.map +1 -1
  20. package/dist/basemap/vectortiles/lamps.d.ts +23 -0
  21. package/dist/basemap/vectortiles/lamps.js +90 -0
  22. package/dist/basemap/vectortiles/lamps.js.map +1 -0
  23. package/dist/basemap/vectortiles/requests.d.ts +4 -0
  24. package/dist/basemap/vectortiles/requests.js +15 -2
  25. package/dist/basemap/vectortiles/requests.js.map +1 -1
  26. package/dist/basemap/vectortiles/trees.d.ts +9 -0
  27. package/dist/basemap/vectortiles/trees.js +114 -29
  28. package/dist/basemap/vectortiles/trees.js.map +1 -1
  29. package/dist/basemap/vectortiles/vectors.d.ts +17 -0
  30. package/dist/basemap/vectortiles/vectors.js +72 -8
  31. package/dist/basemap/vectortiles/vectors.js.map +1 -1
  32. package/dist/basemap/vectortiles.d.ts +26 -6
  33. package/dist/basemap/vectortiles.js +43 -10
  34. package/dist/basemap/vectortiles.js.map +1 -1
  35. package/dist/basemap.worker.js +2 -2
  36. package/dist/basemap.worker.js.map +12 -10
  37. package/dist/bind/layers.js +1 -0
  38. package/dist/bind/layers.js.map +1 -1
  39. package/dist/data/collada.d.ts +19 -0
  40. package/dist/data/collada.js +319 -0
  41. package/dist/data/collada.js.map +1 -0
  42. package/dist/data/ifc.d.ts +100 -0
  43. package/dist/data/ifc.js +372 -0
  44. package/dist/data/ifc.js.map +1 -0
  45. package/dist/decoder.worker.js +5 -5
  46. package/dist/decoder.worker.js.map +3 -3
  47. package/dist/formats.d.ts +7 -2
  48. package/dist/formats.js +7 -2
  49. package/dist/formats.js.map +1 -1
  50. package/dist/index.d.ts +8 -1
  51. package/dist/index.js +6 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/isard_bg.wasm +0 -0
  54. package/dist/isard_core_bg.wasm +0 -0
  55. package/dist/isard_grid_bg.wasm +0 -0
  56. package/dist/isard_tess_bg.wasm +0 -0
  57. package/dist/layers/extrusions.d.ts +10 -0
  58. package/dist/layers/extrusions.js +28 -2
  59. package/dist/layers/extrusions.js.map +1 -1
  60. package/dist/layers/layers.d.ts +12 -0
  61. package/dist/layers/layers.js +44 -1
  62. package/dist/layers/layers.js.map +1 -1
  63. package/dist/layers/photos.d.ts +48 -0
  64. package/dist/layers/photos.js +140 -0
  65. package/dist/layers/photos.js.map +1 -0
  66. package/dist/layers/symbols.d.ts +5 -1
  67. package/dist/layers/symbols.js +111 -32
  68. package/dist/layers/symbols.js.map +1 -1
  69. package/dist/layers/trees.d.ts +28 -0
  70. package/dist/layers/trees.js +51 -0
  71. package/dist/layers/trees.js.map +1 -0
  72. package/dist/layers/vectors.d.ts +28 -2
  73. package/dist/layers/vectors.js +81 -7
  74. package/dist/layers/vectors.js.map +1 -1
  75. package/dist/map/layer-spec.d.ts +3 -0
  76. package/dist/map/layer-spec.js +2 -0
  77. package/dist/map/layer-spec.js.map +1 -1
  78. package/dist/map.d.ts +8 -0
  79. package/dist/map.js +15 -2
  80. package/dist/map.js.map +1 -1
  81. package/dist/panoramax.d.ts +49 -0
  82. package/dist/panoramax.js +71 -0
  83. package/dist/panoramax.js.map +1 -0
  84. package/dist/react/Layer.d.ts +1 -0
  85. package/dist/react/Layer.js.map +1 -1
  86. package/dist/react/VectorGeometry.d.ts +5 -0
  87. package/dist/react/VectorGeometry.js +12 -4
  88. package/dist/react/VectorGeometry.js.map +1 -1
  89. package/dist/util/gltf.d.ts +9 -0
  90. package/dist/util/gltf.js +27 -0
  91. package/dist/util/gltf.js.map +1 -0
  92. package/dist/util/roofs.d.ts +10 -0
  93. package/dist/util/roofs.js +30 -0
  94. package/dist/util/roofs.js.map +1 -0
  95. package/dist/wasm/isard.d.ts +56 -17
  96. package/dist/wasm/isard.js +128 -18
  97. package/dist/wasm/isard_core.d.ts +44 -17
  98. package/dist/wasm/isard_core.js +104 -18
  99. package/dist/wasm/isard_tess.d.ts +6 -3
  100. package/dist/wasm/isard_tess.js +27 -4
  101. package/dist/workers/basemap/geometry.d.ts +38 -22
  102. package/dist/workers/basemap/labels.d.ts +14 -1
  103. package/dist/workers/basemap/lamps.d.ts +10 -0
  104. package/dist/workers/basemap/trees.d.ts +3 -1
  105. package/dist/workers/basemap.worker.d.ts +43 -3
  106. package/dist/workers/basemap.worker.js +14 -2
  107. package/dist/workers/basemap.worker.js.map +1 -1
  108. package/package.json +1 -1
@@ -0,0 +1,372 @@
1
+ /**
2
+ * IFC models (the BIM tools' own format: Revit, ArchiCAD, Tekla, Allplan, Bonsai…) read by web-ifc (ThatOpen,
3
+ * MPL-2.0) in a worker of their own, web-ifc fetched on first use from a CDN (or a folder of yours): the model's
4
+ * storeys and elements at once, each storey's geometry when asked (a glTF binary for `map.addModels({ data })`, each
5
+ * element a feature of it, picked by the map: `hit.index` is the element's), the elements' properties on demand, and
6
+ * where the model stands (its map conversion in a projected CRS, else its site's latitude and longitude).
7
+ */
8
+ import { bounds, glbOf } from "../util/gltf.js";
9
+ import { getCrs } from "./crs.js";
10
+ /** web-ifc's build used by default: its module (`web-ifc-api.js`) and its WebAssembly (`web-ifc.wasm`) */
11
+ export const WEB_IFC = 'https://cdn.jsdelivr.net/npm/web-ifc@0.0.78/';
12
+ /**
13
+ * The worker's code: web-ifc imported from `base`, the file opened; its storeys, its elements (the spatial structure's
14
+ * products) and what places it (its map conversion, its site, its true north) given at once, a storey's meshes when
15
+ * asked (positions put in the model's frame, colors), the properties asked for
16
+ */
17
+ const WORKER = `
18
+ let api, model, W;
19
+ const text = (v) => (v && typeof v.value !== 'undefined' ? v.value : null);
20
+ const degrees = (v) => {
21
+ const parts = text(v);
22
+ if (!Array.isArray(parts)) return undefined;
23
+ const [d = 0, m = 0, s = 0, u = 0] = parts.map((p) => text(p) ?? p);
24
+ return d + m / 60 + s / 3600 + u / 3600e6;
25
+ };
26
+ const plain = (line) => Object.fromEntries(
27
+ Object.entries(line ?? {})
28
+ // Values, not references to other lines (type 5) nor lists
29
+ .filter(([k, v]) => v && typeof v === 'object' && 'value' in v && v.type !== 5 && typeof v.value !== 'object' && k !== 'expressID')
30
+ .map(([k, v]) => [k, v.value]),
31
+ );
32
+ const ids = (type) => { const v = api.GetLineIDsWithType(model, type); return Array.from({ length: v.size() }, (_, i) => v.get(i)); };
33
+ const line = (ref) => (ref && ref.value !== undefined ? api.GetLine(model, ref.value) : undefined);
34
+ // The spatial structure's containers, and the products drawn by no element of their own (the site is one when it has
35
+ // a shape: its terrain)
36
+ const SPATIAL = new Set(['IFCPROJECT', 'IFCBUILDING', 'IFCBUILDINGSTOREY', 'IFCSPACE', 'IFCOPENINGELEMENT']);
37
+ const PREFIXES = { EXA: 1e18, PETA: 1e15, TERA: 1e12, GIGA: 1e9, MEGA: 1e6, KILO: 1e3, HECTO: 100, DECA: 10, DECI: 0.1, CENTI: 0.01, MILLI: 0.001, MICRO: 1e-6 };
38
+ /** Metres per length unit of the project */
39
+ const unitScale = () => {
40
+ const project = ids(W.IFCPROJECT).map((id) => api.GetLine(model, id))[0];
41
+ const units = line(project?.UnitsInContext)?.Units ?? [];
42
+ for (const ref of units) {
43
+ const unit = line(ref);
44
+ if (text(unit?.UnitType) !== 'LENGTHUNIT') continue;
45
+ if (unit.Prefix !== undefined || text(unit.Name) === 'METRE') return PREFIXES[text(unit.Prefix)] ?? 1;
46
+ // A conversion-based unit (the foot): its factor, in metres when its unit is
47
+ const factor = line(unit.ConversionFactor);
48
+ const value = Number(text(factor?.ValueComponent) ?? NaN);
49
+ if (Number.isFinite(value)) return value * (PREFIXES[text(line(factor?.UnitComponent)?.Prefix)] ?? 1);
50
+ }
51
+ return 1;
52
+ };
53
+ onmessage = async ({ data }) => {
54
+ try {
55
+ if (data.type === 'open') {
56
+ W = await import(data.base + 'web-ifc-api.js');
57
+ api = new W.IfcAPI();
58
+ api.SetWasmPath(data.base, true);
59
+ await api.Init();
60
+ model = api.OpenModel(new Uint8Array(data.bytes), { COORDINATE_TO_ORIGIN: true });
61
+ const storeys = ids(W.IFCBUILDINGSTOREY).map((id) => {
62
+ const l = api.GetLine(model, id);
63
+ return { id, name: text(l.Name) ?? '', elevation: Number(text(l.Elevation) ?? 0) * unitScale() };
64
+ });
65
+ // The products of the spatial structure and their storeys
66
+ const elements = [];
67
+ const walk = (node, storey) => {
68
+ const type = String(node.type);
69
+ const here = type.toUpperCase() === 'IFCBUILDINGSTOREY' ? node.expressID : storey;
70
+ const drawn = type.toUpperCase() !== 'IFCSITE' || api.GetLine(model, node.expressID)?.Representation;
71
+ if (!SPATIAL.has(type.toUpperCase()) && drawn) {
72
+ elements.push({ id: node.expressID, type, name: text(api.GetLine(model, node.expressID)?.Name) ?? '', storey: here ?? -1 });
73
+ }
74
+ (node.children ?? []).forEach((c) => walk(c, here));
75
+ };
76
+ walk(await api.properties.getSpatialStructure(model), undefined);
77
+ // What places it: the shift web-ifc made to bring it near the origin (metres, its y-up frame), the map
78
+ // conversion and its CRS, the site's latitude and longitude, the true north
79
+ const shift = api.GetCoordinationMatrix(model).slice(12, 15);
80
+ const conversion = ids(W.IFCMAPCONVERSION).map((id) => api.GetLine(model, id))[0];
81
+ const site = ids(W.IFCSITE).map((id) => api.GetLine(model, id))[0];
82
+ const north = ids(W.IFCGEOMETRICREPRESENTATIONCONTEXT)
83
+ .map((id) => line(api.GetLine(model, id).TrueNorth))
84
+ .find((d) => d)?.DirectionRatios?.map((r) => text(r) ?? r);
85
+ const [lat, lon] = [degrees(site?.RefLatitude), degrees(site?.RefLongitude)];
86
+ postMessage({
87
+ type: 'model',
88
+ storeys,
89
+ elements,
90
+ place: {
91
+ shift,
92
+ unit: unitScale(),
93
+ ...(conversion && {
94
+ map: ['Eastings', 'Northings', 'OrthogonalHeight', 'XAxisAbscissa', 'XAxisOrdinate', 'Scale'].map((k) => Number(text(conversion[k]) ?? (k === 'Scale' || k === 'XAxisAbscissa' ? 1 : 0))),
95
+ crs: text(line(conversion.TargetCRS)?.Name),
96
+ }),
97
+ ...(lat !== undefined && lon !== undefined && { site: [lon, lat, Number(text(site.RefElevation) ?? 0) * unitScale()] }),
98
+ ...(north && { north }),
99
+ },
100
+ });
101
+ } else if (data.type === 'storey') {
102
+ const parts = [];
103
+ const feature = new Map(data.ids.map((id, i) => [id, data.features[i]]));
104
+ // web-ifc 0.0.78's browser build takes three arguments where its API passes four: called directly then
105
+ const stream = (callback) => {
106
+ try {
107
+ api.StreamMeshes(model, data.ids, callback);
108
+ } catch (error) {
109
+ if (!/arguments/.test(String(error))) throw error;
110
+ api.wasmModule.StreamMeshes(model, data.ids, callback);
111
+ }
112
+ };
113
+ stream((mesh) => {
114
+ for (let i = 0; i < mesh.geometries.size(); i++) {
115
+ const placed = mesh.geometries.get(i);
116
+ const geometry = api.GetGeometry(model, placed.geometryExpressID);
117
+ const v = api.GetVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize());
118
+ const indices = api.GetIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize()).slice();
119
+ const m = placed.flatTransformation;
120
+ const positions = new Float32Array((v.length / 6) * 3);
121
+ for (let k = 0, p = 0; k < v.length; k += 6, p += 3) {
122
+ const [x, y, z] = [v[k], v[k + 1], v[k + 2]];
123
+ // Turned half a turn round y: the model's north (-z of web-ifc's y-up frame) toward +z, glTF's front
124
+ positions[p] = -(m[0] * x + m[4] * y + m[8] * z + m[12]);
125
+ positions[p + 1] = m[1] * x + m[5] * y + m[9] * z + m[13];
126
+ positions[p + 2] = -(m[2] * x + m[6] * y + m[10] * z + m[14]);
127
+ }
128
+ const c = placed.color;
129
+ parts.push({ feature: feature.get(mesh.expressID) ?? 0, color: [c.x, c.y, c.z, c.w], positions, indices });
130
+ geometry.delete();
131
+ }
132
+ });
133
+ postMessage({ type: 'storey', request: data.request, parts }, parts.flatMap((p) => [p.positions.buffer, p.indices.buffer]));
134
+ } else if (data.type === 'properties') {
135
+ const properties = { Attributes: plain(api.GetLine(model, data.id)) };
136
+ const sets = await api.properties.getPropertySets(model, data.id, true);
137
+ for (const set of sets) {
138
+ const values = {};
139
+ for (const p of [...(set.HasProperties ?? []), ...(set.Quantities ?? [])]) {
140
+ const name = text(p.Name);
141
+ if (!name) continue;
142
+ const value = p.NominalValue ?? p.LengthValue ?? p.AreaValue ?? p.VolumeValue ?? p.CountValue ?? p.WeightValue;
143
+ values[name] = text(value);
144
+ }
145
+ properties[text(set.Name) ?? 'Set ' + set.expressID] = values;
146
+ }
147
+ postMessage({ type: 'properties', request: data.request, properties });
148
+ }
149
+ } catch (error) {
150
+ postMessage({ type: 'error', request: data.request, message: String(error?.message ?? error) });
151
+ }
152
+ };
153
+ `;
154
+ /** A storey's parts as a glTF binary: a primitive per color, each vertex its element (`_BATCHID`) */
155
+ export const storeyModel = (parts) => {
156
+ const byColor = new Map();
157
+ parts.forEach((p) => {
158
+ const key = p.color.map((c) => c.toFixed(3)).join(',');
159
+ byColor.set(key, [...(byColor.get(key) ?? []), p]);
160
+ });
161
+ const chunks = [];
162
+ const views = [];
163
+ const accessors = [];
164
+ let offset = 0;
165
+ const append = (bytes, target) => {
166
+ views.push({ buffer: 0, byteOffset: offset, byteLength: bytes.byteLength, target });
167
+ chunks.push(bytes);
168
+ offset += Math.ceil(bytes.byteLength / 4) * 4;
169
+ return views.length - 1;
170
+ };
171
+ const groups = [...byColor.values()];
172
+ const primitives = groups.map((group, material) => {
173
+ const count = group.reduce((n, p) => n + p.positions.length / 3, 0);
174
+ const positions = new Float32Array(count * 3);
175
+ const features = new Float32Array(count);
176
+ const indices = new Uint32Array(group.reduce((n, p) => n + p.indices.length, 0));
177
+ let [vertex, index] = [0, 0];
178
+ group.forEach((p) => {
179
+ positions.set(p.positions, vertex * 3);
180
+ features.fill(p.feature, vertex, vertex + p.positions.length / 3);
181
+ p.indices.forEach((i, k) => {
182
+ indices[index + k] = i + vertex;
183
+ });
184
+ vertex += p.positions.length / 3;
185
+ index += p.indices.length;
186
+ });
187
+ accessors.push({
188
+ bufferView: append(new Uint8Array(positions.buffer), 34962),
189
+ componentType: 5126,
190
+ count,
191
+ type: 'VEC3',
192
+ ...bounds([...positions]),
193
+ }, { bufferView: append(new Uint8Array(features.buffer), 34962), componentType: 5126, count, type: 'SCALAR' }, {
194
+ bufferView: append(new Uint8Array(indices.buffer), 34963),
195
+ componentType: 5125,
196
+ count: indices.length,
197
+ type: 'SCALAR',
198
+ });
199
+ const at = accessors.length - 3;
200
+ return { attributes: { POSITION: at, _BATCHID: at + 1 }, indices: at + 2, material };
201
+ });
202
+ const bin = new Uint8Array(offset);
203
+ let at = 0;
204
+ chunks.forEach((c) => {
205
+ bin.set(c, at);
206
+ at += Math.ceil(c.byteLength / 4) * 4;
207
+ });
208
+ return glbOf({
209
+ asset: { version: '2.0', generator: 'isard (IFC)' },
210
+ scene: 0,
211
+ scenes: [{ nodes: [0] }],
212
+ nodes: [{ mesh: 0 }],
213
+ meshes: [{ primitives }],
214
+ materials: groups.map((group) => {
215
+ const color = group[0]?.color ?? [0.8, 0.8, 0.8, 1];
216
+ return {
217
+ pbrMetallicRoughness: { baseColorFactor: color, metallicFactor: 0, roughnessFactor: 1 },
218
+ ...(color[3] < 1 && { alphaMode: 'BLEND' }),
219
+ doubleSided: true,
220
+ };
221
+ }),
222
+ accessors,
223
+ bufferViews: views,
224
+ buffers: [{ byteLength: bin.byteLength }],
225
+ }, bin);
226
+ };
227
+ /** Degrees clockwise from the north of the true north to (x, y) in a plan whose y would be north */
228
+ const bearingOf = (x, y) => (Math.atan2(x, y) * 180) / Math.PI;
229
+ /**
230
+ * Where a model's origin (its glTF's) stands: by its map conversion when its CRS is known (the origin in the file's
231
+ * frame, before web-ifc's shift, taken to the map's eastings and northings, then to longitude and latitude; its north
232
+ * turned by the conversion's x axis and the grid's convergence there), else by its site's latitude and longitude
233
+ * turned by its true north
234
+ */
235
+ export const placeIfc = ({ shift, unit, map, crs, site, north }) => {
236
+ // The origin in the file's frame (metres, z up): web-ifc's y-up shift undone
237
+ const [sx = 0, sy = 0, sz = 0] = shift;
238
+ const [x, y, z] = [-sx, sz, -sy];
239
+ if (map && crs) {
240
+ const projection = (() => {
241
+ try {
242
+ return getCrs(crs);
243
+ }
244
+ catch {
245
+ return undefined;
246
+ }
247
+ })();
248
+ const [e = 0, n = 0, h = 0, ax = 1, ay = 0, scale = 1] = map;
249
+ if (projection) {
250
+ const length = Math.hypot(ax, ay) || 1;
251
+ const [a, b] = [ax / length, ay / length];
252
+ // The conversion's scale takes the project's units to the map's; the geometry is in metres already
253
+ const k = scale / unit;
254
+ const [east, northing] = [e + k * (a * x - b * y), n + k * (b * x + a * y)];
255
+ const [lon = 0, lat = 0] = projection.inverse([east, northing]);
256
+ const [lon2 = 0, lat2 = 0] = projection.inverse([east, northing + 100]);
257
+ // The grid's north from the true north there, and the file's north (its y) from the grid's
258
+ const convergence = bearingOf((lon2 - lon) * Math.cos((lat * Math.PI) / 180), lat2 - lat);
259
+ return {
260
+ lonLat: [lon, lat],
261
+ elevation: h + k * z,
262
+ heading: bearingOf(-b, a) + convergence,
263
+ from: 'map',
264
+ crs,
265
+ };
266
+ }
267
+ }
268
+ if (!site)
269
+ return undefined;
270
+ // The true north in the plan: the plan's y turned from it the other way
271
+ const [nx = 0, ny = 1] = north ?? [];
272
+ const heading = -bearingOf(nx, ny);
273
+ const [lon, lat, elevation] = site;
274
+ // The origin's offset from the site's (metres, the plan turned to the north)
275
+ const turn = (heading * Math.PI) / 180;
276
+ const [east, northing] = [x * Math.cos(turn) + y * Math.sin(turn), -x * Math.sin(turn) + y * Math.cos(turn)];
277
+ return {
278
+ lonLat: [lon + east / (111320 * Math.cos((lat * Math.PI) / 180)), lat + northing / 111320],
279
+ elevation: elevation + z,
280
+ heading,
281
+ from: 'site',
282
+ };
283
+ };
284
+ /**
285
+ * An IFC file (its URL, or its bytes) opened by web-ifc in a worker: its storeys and elements, each storey's geometry
286
+ * when asked, the elements' properties on demand, where it stands
287
+ */
288
+ export const loadIfc = async (source, options = {}) => {
289
+ const bytes = typeof source === 'string'
290
+ ? await fetch(source, options.init).then((r) => {
291
+ if (!r.ok)
292
+ throw new Error(`isard: ${source}: ${r.status}`);
293
+ return r.arrayBuffer();
294
+ })
295
+ : source;
296
+ const script = URL.createObjectURL(new Blob([WORKER], { type: 'text/javascript' }));
297
+ const worker = new Worker(script, { type: 'module' });
298
+ /** The answers awaited, by request */
299
+ const pending = new Map();
300
+ let request = 0;
301
+ const ask = (message) => new Promise((resolve, reject) => {
302
+ request += 1;
303
+ pending.set(request, { resolve: (answer) => resolve(answer), reject });
304
+ worker.postMessage({ ...message, request });
305
+ });
306
+ URL.revokeObjectURL(script);
307
+ const opened = await new Promise((resolve, reject) => {
308
+ worker.onmessage = ({ data }) => {
309
+ if (data.type === 'model')
310
+ resolve(data);
311
+ else if (data.request === undefined)
312
+ reject(new Error(`isard: IFC: ${data.type === 'error' ? data.message : ''}`));
313
+ else {
314
+ const waiting = pending.get(data.request);
315
+ pending.delete(data.request);
316
+ if (data.type === 'error')
317
+ waiting?.reject(new Error(`isard: IFC: ${data.message}`));
318
+ else
319
+ waiting?.resolve(data);
320
+ }
321
+ };
322
+ worker.onerror = (e) => reject(new Error(`isard: IFC worker: ${e.message}`));
323
+ worker.postMessage({ type: 'open', base: options.webIfc ?? WEB_IFC, bytes }, [bytes]);
324
+ }).catch((error) => {
325
+ worker.terminate();
326
+ throw error;
327
+ });
328
+ // The storeys holding elements from the lowest up, the elements outside any in one of their own first (at the
329
+ // lowest storey's elevation)
330
+ const used = new Set(opened.elements.map((e) => e.storey));
331
+ const sorted = opened.storeys.filter((s) => used.has(s.id)).sort((a, b) => a.elevation - b.elevation);
332
+ const outside = opened.elements.some((e) => !sorted.some((s) => s.id === e.storey));
333
+ const list = [...(outside ? [{ id: -1, name: 'Site', elevation: sorted[0]?.elevation ?? 0 }] : []), ...sorted];
334
+ const storeyIndex = new Map(list.map((s, i) => [s.id, i]));
335
+ const elements = opened.elements.map((e) => ({ ...e, storey: storeyIndex.get(e.storey) ?? 0 }));
336
+ const storeys = list.map((s, i) => {
337
+ const members = elements.flatMap((e, k) => (e.storey === i ? [k] : []));
338
+ let loaded;
339
+ return {
340
+ ...s,
341
+ elements: members,
342
+ load: () => {
343
+ loaded ??= ask({
344
+ type: 'storey',
345
+ ids: members.map((k) => elements[k]?.id ?? 0),
346
+ features: members,
347
+ }).then(({ parts }) => storeyModel(parts));
348
+ return loaded;
349
+ },
350
+ };
351
+ });
352
+ const place = placeIfc(opened.place);
353
+ return {
354
+ storeys,
355
+ elements,
356
+ ...(place && { place }),
357
+ properties: (element) => {
358
+ const id = elements[element]?.id;
359
+ if (id === undefined)
360
+ return Promise.reject(new Error(`isard: IFC: no element ${element}`));
361
+ return ask({ type: 'properties', id }).then((a) => a.properties);
362
+ },
363
+ close: () => {
364
+ worker.terminate();
365
+ pending.forEach(({ reject }) => {
366
+ reject(new Error('isard: IFC model closed'));
367
+ });
368
+ pending.clear();
369
+ },
370
+ };
371
+ };
372
+ //# sourceMappingURL=ifc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ifc.js","sourceRoot":"","sources":["../../src/data/ifc.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * IFC models (the BIM tools' own format: Revit, ArchiCAD, Tekla, Allplan, Bonsai…) read by web-ifc (ThatOpen,\n * MPL-2.0) in a worker of their own, web-ifc fetched on first use from a CDN (or a folder of yours): the model's\n * storeys and elements at once, each storey's geometry when asked (a glTF binary for `map.addModels({ data })`, each\n * element a feature of it, picked by the map: `hit.index` is the element's), the elements' properties on demand, and\n * where the model stands (its map conversion in a projected CRS, else its site's latitude and longitude).\n */\n\nimport { bounds, glbOf } from '../util/gltf.ts';\nimport { getCrs } from './crs.ts';\n\n/** web-ifc's build used by default: its module (`web-ifc-api.js`) and its WebAssembly (`web-ifc.wasm`) */\nexport const WEB_IFC = 'https://cdn.jsdelivr.net/npm/web-ifc@0.0.78/';\n\nexport interface IfcOptions {\n /** Folder of `web-ifc-api.js` and `web-ifc.wasm` (ending with `/`; `WEB_IFC` by default): self-host them for offline\n * or locked-down networks. The worker imports the module from a `blob:` URL: a Content Security Policy needs\n * `blob:` in `worker-src` */\n webIfc?: string;\n /** Request options of the file's fetch (headers of a private source) */\n init?: RequestInit;\n}\n\n/** An element of the model (a wall, a slab, a window, a stair…) */\nexport interface IfcElement {\n /** Its IFC id (STEP line number) */\n id: number;\n /** Its IFC class (`IfcWall`, `IfcSlab`, `IfcWindow`…) */\n type: string;\n name: string;\n /** Its storey in `IfcModel.storeys` */\n storey: number;\n}\n\n/** A storey of the model: its elements, its geometry when asked */\nexport interface IfcStorey {\n id: number;\n name: string;\n /** Metres above the model's origin */\n elevation: number;\n /** Its elements (`IfcModel.elements`' indices) */\n elements: number[];\n /**\n * Its elements' geometry as a glTF binary (their features: `IfcModel.elements`' indices) in the model's frame: y up,\n * its north toward +z (`IfcModel.place.heading` turns it), its origin at the model's; meshed in the worker on the\n * first call (a large model's storeys one at a time, those in view only)\n */\n load(): Promise<Uint8Array<ArrayBuffer>>;\n}\n\n/** An element's properties: its attributes, then its property and quantity sets, by name */\nexport type IfcProperties = Record<string, Record<string, string | number | boolean | null>>;\n\n/** Where a model stands: its origin's position and height, the way its north faces */\nexport interface IfcPlace {\n lonLat: [number, number];\n /** Metres above the sea (its map conversion's height, its site's `RefElevation`) */\n elevation: number;\n /** Degrees clockwise from the north of the model's north (an instance's `heading`) */\n heading: number;\n /** How it is known: its map conversion (exact, `crs` its projected CRS), or its site's latitude and longitude */\n from: 'map' | 'site';\n crs?: string;\n}\n\nexport interface IfcModel {\n /** Its storeys from the lowest up, elements outside any storey (the site's) in one of their own at the start */\n storeys: IfcStorey[];\n /** Its elements, the features of the storeys' models */\n elements: IfcElement[];\n /** Where it stands, if the file says (`IfcMapConversion` in a CRS isard knows, else `IfcSite`'s position) */\n place?: IfcPlace;\n /** An element's properties, read from the file kept open in the worker */\n properties(element: number): Promise<IfcProperties>;\n /** Closes the file and its worker */\n close(): void;\n}\n\n/**\n * The worker's code: web-ifc imported from `base`, the file opened; its storeys, its elements (the spatial structure's\n * products) and what places it (its map conversion, its site, its true north) given at once, a storey's meshes when\n * asked (positions put in the model's frame, colors), the properties asked for\n */\nconst WORKER = `\nlet api, model, W;\nconst text = (v) => (v && typeof v.value !== 'undefined' ? v.value : null);\nconst degrees = (v) => {\n const parts = text(v);\n if (!Array.isArray(parts)) return undefined;\n const [d = 0, m = 0, s = 0, u = 0] = parts.map((p) => text(p) ?? p);\n return d + m / 60 + s / 3600 + u / 3600e6;\n};\nconst plain = (line) => Object.fromEntries(\n Object.entries(line ?? {})\n // Values, not references to other lines (type 5) nor lists\n .filter(([k, v]) => v && typeof v === 'object' && 'value' in v && v.type !== 5 && typeof v.value !== 'object' && k !== 'expressID')\n .map(([k, v]) => [k, v.value]),\n);\nconst ids = (type) => { const v = api.GetLineIDsWithType(model, type); return Array.from({ length: v.size() }, (_, i) => v.get(i)); };\nconst line = (ref) => (ref && ref.value !== undefined ? api.GetLine(model, ref.value) : undefined);\n// The spatial structure's containers, and the products drawn by no element of their own (the site is one when it has\n// a shape: its terrain)\nconst SPATIAL = new Set(['IFCPROJECT', 'IFCBUILDING', 'IFCBUILDINGSTOREY', 'IFCSPACE', 'IFCOPENINGELEMENT']);\nconst PREFIXES = { EXA: 1e18, PETA: 1e15, TERA: 1e12, GIGA: 1e9, MEGA: 1e6, KILO: 1e3, HECTO: 100, DECA: 10, DECI: 0.1, CENTI: 0.01, MILLI: 0.001, MICRO: 1e-6 };\n/** Metres per length unit of the project */\nconst unitScale = () => {\n const project = ids(W.IFCPROJECT).map((id) => api.GetLine(model, id))[0];\n const units = line(project?.UnitsInContext)?.Units ?? [];\n for (const ref of units) {\n const unit = line(ref);\n if (text(unit?.UnitType) !== 'LENGTHUNIT') continue;\n if (unit.Prefix !== undefined || text(unit.Name) === 'METRE') return PREFIXES[text(unit.Prefix)] ?? 1;\n // A conversion-based unit (the foot): its factor, in metres when its unit is\n const factor = line(unit.ConversionFactor);\n const value = Number(text(factor?.ValueComponent) ?? NaN);\n if (Number.isFinite(value)) return value * (PREFIXES[text(line(factor?.UnitComponent)?.Prefix)] ?? 1);\n }\n return 1;\n};\nonmessage = async ({ data }) => {\n try {\n if (data.type === 'open') {\n W = await import(data.base + 'web-ifc-api.js');\n api = new W.IfcAPI();\n api.SetWasmPath(data.base, true);\n await api.Init();\n model = api.OpenModel(new Uint8Array(data.bytes), { COORDINATE_TO_ORIGIN: true });\n const storeys = ids(W.IFCBUILDINGSTOREY).map((id) => {\n const l = api.GetLine(model, id);\n return { id, name: text(l.Name) ?? '', elevation: Number(text(l.Elevation) ?? 0) * unitScale() };\n });\n // The products of the spatial structure and their storeys\n const elements = [];\n const walk = (node, storey) => {\n const type = String(node.type);\n const here = type.toUpperCase() === 'IFCBUILDINGSTOREY' ? node.expressID : storey;\n const drawn = type.toUpperCase() !== 'IFCSITE' || api.GetLine(model, node.expressID)?.Representation;\n if (!SPATIAL.has(type.toUpperCase()) && drawn) {\n elements.push({ id: node.expressID, type, name: text(api.GetLine(model, node.expressID)?.Name) ?? '', storey: here ?? -1 });\n }\n (node.children ?? []).forEach((c) => walk(c, here));\n };\n walk(await api.properties.getSpatialStructure(model), undefined);\n // What places it: the shift web-ifc made to bring it near the origin (metres, its y-up frame), the map\n // conversion and its CRS, the site's latitude and longitude, the true north\n const shift = api.GetCoordinationMatrix(model).slice(12, 15);\n const conversion = ids(W.IFCMAPCONVERSION).map((id) => api.GetLine(model, id))[0];\n const site = ids(W.IFCSITE).map((id) => api.GetLine(model, id))[0];\n const north = ids(W.IFCGEOMETRICREPRESENTATIONCONTEXT)\n .map((id) => line(api.GetLine(model, id).TrueNorth))\n .find((d) => d)?.DirectionRatios?.map((r) => text(r) ?? r);\n const [lat, lon] = [degrees(site?.RefLatitude), degrees(site?.RefLongitude)];\n postMessage({\n type: 'model',\n storeys,\n elements,\n place: {\n shift,\n unit: unitScale(),\n ...(conversion && {\n map: ['Eastings', 'Northings', 'OrthogonalHeight', 'XAxisAbscissa', 'XAxisOrdinate', 'Scale'].map((k) => Number(text(conversion[k]) ?? (k === 'Scale' || k === 'XAxisAbscissa' ? 1 : 0))),\n crs: text(line(conversion.TargetCRS)?.Name),\n }),\n ...(lat !== undefined && lon !== undefined && { site: [lon, lat, Number(text(site.RefElevation) ?? 0) * unitScale()] }),\n ...(north && { north }),\n },\n });\n } else if (data.type === 'storey') {\n const parts = [];\n const feature = new Map(data.ids.map((id, i) => [id, data.features[i]]));\n // web-ifc 0.0.78's browser build takes three arguments where its API passes four: called directly then\n const stream = (callback) => {\n try {\n api.StreamMeshes(model, data.ids, callback);\n } catch (error) {\n if (!/arguments/.test(String(error))) throw error;\n api.wasmModule.StreamMeshes(model, data.ids, callback);\n }\n };\n stream((mesh) => {\n for (let i = 0; i < mesh.geometries.size(); i++) {\n const placed = mesh.geometries.get(i);\n const geometry = api.GetGeometry(model, placed.geometryExpressID);\n const v = api.GetVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize());\n const indices = api.GetIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize()).slice();\n const m = placed.flatTransformation;\n const positions = new Float32Array((v.length / 6) * 3);\n for (let k = 0, p = 0; k < v.length; k += 6, p += 3) {\n const [x, y, z] = [v[k], v[k + 1], v[k + 2]];\n // Turned half a turn round y: the model's north (-z of web-ifc's y-up frame) toward +z, glTF's front\n positions[p] = -(m[0] * x + m[4] * y + m[8] * z + m[12]);\n positions[p + 1] = m[1] * x + m[5] * y + m[9] * z + m[13];\n positions[p + 2] = -(m[2] * x + m[6] * y + m[10] * z + m[14]);\n }\n const c = placed.color;\n parts.push({ feature: feature.get(mesh.expressID) ?? 0, color: [c.x, c.y, c.z, c.w], positions, indices });\n geometry.delete();\n }\n });\n postMessage({ type: 'storey', request: data.request, parts }, parts.flatMap((p) => [p.positions.buffer, p.indices.buffer]));\n } else if (data.type === 'properties') {\n const properties = { Attributes: plain(api.GetLine(model, data.id)) };\n const sets = await api.properties.getPropertySets(model, data.id, true);\n for (const set of sets) {\n const values = {};\n for (const p of [...(set.HasProperties ?? []), ...(set.Quantities ?? [])]) {\n const name = text(p.Name);\n if (!name) continue;\n const value = p.NominalValue ?? p.LengthValue ?? p.AreaValue ?? p.VolumeValue ?? p.CountValue ?? p.WeightValue;\n values[name] = text(value);\n }\n properties[text(set.Name) ?? 'Set ' + set.expressID] = values;\n }\n postMessage({ type: 'properties', request: data.request, properties });\n }\n } catch (error) {\n postMessage({ type: 'error', request: data.request, message: String(error?.message ?? error) });\n }\n};\n`;\n\n/** A part of the model: an element's geometry in one color */\ninterface Part {\n feature: number;\n color: [number, number, number, number];\n positions: Float32Array;\n indices: Uint32Array;\n}\n\n/** A storey's parts as a glTF binary: a primitive per color, each vertex its element (`_BATCHID`) */\nexport const storeyModel = (parts: readonly Part[]): Uint8Array<ArrayBuffer> => {\n const byColor = new Map<string, Part[]>();\n\n parts.forEach((p) => {\n const key = p.color.map((c) => c.toFixed(3)).join(',');\n\n byColor.set(key, [...(byColor.get(key) ?? []), p]);\n });\n\n const chunks: Uint8Array[] = [];\n const views: { buffer: 0; byteOffset: number; byteLength: number; target: number }[] = [];\n const accessors: Record<string, unknown>[] = [];\n let offset = 0;\n const append = (bytes: Uint8Array, target: number) => {\n views.push({ buffer: 0, byteOffset: offset, byteLength: bytes.byteLength, target });\n chunks.push(bytes);\n offset += Math.ceil(bytes.byteLength / 4) * 4;\n\n return views.length - 1;\n };\n const groups = [...byColor.values()];\n const primitives = groups.map((group, material) => {\n const count = group.reduce((n, p) => n + p.positions.length / 3, 0);\n const positions = new Float32Array(count * 3);\n const features = new Float32Array(count);\n const indices = new Uint32Array(group.reduce((n, p) => n + p.indices.length, 0));\n let [vertex, index] = [0, 0];\n\n group.forEach((p) => {\n positions.set(p.positions, vertex * 3);\n features.fill(p.feature, vertex, vertex + p.positions.length / 3);\n p.indices.forEach((i, k) => {\n indices[index + k] = i + vertex;\n });\n vertex += p.positions.length / 3;\n index += p.indices.length;\n });\n accessors.push(\n {\n bufferView: append(new Uint8Array(positions.buffer), 34962),\n componentType: 5126,\n count,\n type: 'VEC3',\n ...bounds([...positions]),\n },\n { bufferView: append(new Uint8Array(features.buffer), 34962), componentType: 5126, count, type: 'SCALAR' },\n {\n bufferView: append(new Uint8Array(indices.buffer), 34963),\n componentType: 5125,\n count: indices.length,\n type: 'SCALAR',\n },\n );\n\n const at = accessors.length - 3;\n\n return { attributes: { POSITION: at, _BATCHID: at + 1 }, indices: at + 2, material };\n });\n const bin = new Uint8Array(offset);\n let at = 0;\n\n chunks.forEach((c) => {\n bin.set(c, at);\n at += Math.ceil(c.byteLength / 4) * 4;\n });\n\n return glbOf(\n {\n asset: { version: '2.0', generator: 'isard (IFC)' },\n scene: 0,\n scenes: [{ nodes: [0] }],\n nodes: [{ mesh: 0 }],\n meshes: [{ primitives }],\n materials: groups.map((group) => {\n const color = group[0]?.color ?? [0.8, 0.8, 0.8, 1];\n\n return {\n pbrMetallicRoughness: { baseColorFactor: color, metallicFactor: 0, roughnessFactor: 1 },\n ...(color[3] < 1 && { alphaMode: 'BLEND' }),\n doubleSided: true,\n };\n }),\n accessors,\n bufferViews: views,\n buffers: [{ byteLength: bin.byteLength }],\n },\n bin,\n );\n};\n\n/** What places a model, as the worker reads it: web-ifc's shift of it (metres, its y-up frame), its project's metres\n * per unit, its map conversion (eastings, northings, height, x axis' abscissa and ordinate, scale) and its CRS, its\n * site's longitude, latitude and elevation, its true north (in its plan) */\nexport interface IfcPlacing {\n shift: number[];\n unit: number;\n map?: number[];\n crs?: string | null;\n site?: [number, number, number];\n north?: number[];\n}\n\n/** Degrees clockwise from the north of the true north to (x, y) in a plan whose y would be north */\nconst bearingOf = (x: number, y: number): number => (Math.atan2(x, y) * 180) / Math.PI;\n\n/**\n * Where a model's origin (its glTF's) stands: by its map conversion when its CRS is known (the origin in the file's\n * frame, before web-ifc's shift, taken to the map's eastings and northings, then to longitude and latitude; its north\n * turned by the conversion's x axis and the grid's convergence there), else by its site's latitude and longitude\n * turned by its true north\n */\nexport const placeIfc = ({ shift, unit, map, crs, site, north }: IfcPlacing): IfcPlace | undefined => {\n // The origin in the file's frame (metres, z up): web-ifc's y-up shift undone\n const [sx = 0, sy = 0, sz = 0] = shift;\n const [x, y, z] = [-sx, sz, -sy];\n\n if (map && crs) {\n const projection = (() => {\n try {\n return getCrs(crs);\n } catch {\n return undefined;\n }\n })();\n const [e = 0, n = 0, h = 0, ax = 1, ay = 0, scale = 1] = map;\n\n if (projection) {\n const length = Math.hypot(ax, ay) || 1;\n const [a, b] = [ax / length, ay / length];\n // The conversion's scale takes the project's units to the map's; the geometry is in metres already\n const k = scale / unit;\n const [east, northing] = [e + k * (a * x - b * y), n + k * (b * x + a * y)];\n const [lon = 0, lat = 0] = projection.inverse([east, northing]);\n const [lon2 = 0, lat2 = 0] = projection.inverse([east, northing + 100]);\n // The grid's north from the true north there, and the file's north (its y) from the grid's\n const convergence = bearingOf((lon2 - lon) * Math.cos((lat * Math.PI) / 180), lat2 - lat);\n\n return {\n lonLat: [lon, lat],\n elevation: h + k * z,\n heading: bearingOf(-b, a) + convergence,\n from: 'map',\n crs,\n };\n }\n }\n if (!site) return undefined;\n\n // The true north in the plan: the plan's y turned from it the other way\n const [nx = 0, ny = 1] = north ?? [];\n const heading = -bearingOf(nx, ny);\n const [lon, lat, elevation] = site;\n // The origin's offset from the site's (metres, the plan turned to the north)\n const turn = (heading * Math.PI) / 180;\n const [east, northing] = [x * Math.cos(turn) + y * Math.sin(turn), -x * Math.sin(turn) + y * Math.cos(turn)];\n\n return {\n lonLat: [lon + east / (111320 * Math.cos((lat * Math.PI) / 180)), lat + northing / 111320],\n elevation: elevation + z,\n heading,\n from: 'site',\n };\n};\n\n/** The answer of the worker to a message */\ntype Answer =\n | { type: 'model'; storeys: Omit<IfcStorey, 'elements' | 'load'>[]; elements: IfcElement[]; place: IfcPlacing }\n | { type: 'storey'; request: number; parts: Part[] }\n | { type: 'properties'; request: number; properties: IfcProperties }\n | { type: 'error'; request?: number; message: string };\n\n/**\n * An IFC file (its URL, or its bytes) opened by web-ifc in a worker: its storeys and elements, each storey's geometry\n * when asked, the elements' properties on demand, where it stands\n */\nexport const loadIfc = async (source: string | ArrayBuffer, options: IfcOptions = {}): Promise<IfcModel> => {\n const bytes =\n typeof source === 'string'\n ? await fetch(source, options.init).then((r) => {\n if (!r.ok) throw new Error(`isard: ${source}: ${r.status}`);\n\n return r.arrayBuffer();\n })\n : source;\n const script = URL.createObjectURL(new Blob([WORKER], { type: 'text/javascript' }));\n const worker = new Worker(script, { type: 'module' });\n /** The answers awaited, by request */\n const pending = new Map<number, { resolve: (answer: Answer) => void; reject: (e: Error) => void }>();\n let request = 0;\n const ask = <A extends Answer>(message: Record<string, unknown>): Promise<A> =>\n new Promise((resolve, reject) => {\n request += 1;\n pending.set(request, { resolve: (answer) => resolve(answer as A), reject });\n worker.postMessage({ ...message, request });\n });\n\n URL.revokeObjectURL(script);\n\n const opened = await new Promise<Extract<Answer, { type: 'model' }>>((resolve, reject) => {\n worker.onmessage = ({ data }: MessageEvent<Answer>) => {\n if (data.type === 'model') resolve(data);\n else if (data.request === undefined)\n reject(new Error(`isard: IFC: ${data.type === 'error' ? data.message : ''}`));\n else {\n const waiting = pending.get(data.request);\n\n pending.delete(data.request);\n if (data.type === 'error') waiting?.reject(new Error(`isard: IFC: ${data.message}`));\n else waiting?.resolve(data);\n }\n };\n worker.onerror = (e) => reject(new Error(`isard: IFC worker: ${e.message}`));\n worker.postMessage({ type: 'open', base: options.webIfc ?? WEB_IFC, bytes }, [bytes]);\n }).catch((error: unknown) => {\n worker.terminate();\n throw error;\n });\n\n // The storeys holding elements from the lowest up, the elements outside any in one of their own first (at the\n // lowest storey's elevation)\n const used = new Set(opened.elements.map((e) => e.storey));\n const sorted = opened.storeys.filter((s) => used.has(s.id)).sort((a, b) => a.elevation - b.elevation);\n const outside = opened.elements.some((e) => !sorted.some((s) => s.id === e.storey));\n const list = [...(outside ? [{ id: -1, name: 'Site', elevation: sorted[0]?.elevation ?? 0 }] : []), ...sorted];\n const storeyIndex = new Map(list.map((s, i) => [s.id, i]));\n const elements: IfcElement[] = opened.elements.map((e) => ({ ...e, storey: storeyIndex.get(e.storey) ?? 0 }));\n const storeys: IfcStorey[] = list.map((s, i) => {\n const members = elements.flatMap((e, k) => (e.storey === i ? [k] : []));\n let loaded: Promise<Uint8Array<ArrayBuffer>> | undefined;\n\n return {\n ...s,\n elements: members,\n load: () => {\n loaded ??= ask<Extract<Answer, { type: 'storey' }>>({\n type: 'storey',\n ids: members.map((k) => elements[k]?.id ?? 0),\n features: members,\n }).then(({ parts }) => storeyModel(parts));\n\n return loaded;\n },\n };\n });\n const place = placeIfc(opened.place);\n\n return {\n storeys,\n elements,\n ...(place && { place }),\n properties: (element: number) => {\n const id = elements[element]?.id;\n\n if (id === undefined) return Promise.reject(new Error(`isard: IFC: no element ${element}`));\n\n return ask<Extract<Answer, { type: 'properties' }>>({ type: 'properties', id }).then((a) => a.properties);\n },\n close: () => {\n worker.terminate();\n pending.forEach(({ reject }) => {\n reject(new Error('isard: IFC model closed'));\n });\n pending.clear();\n },\n };\n};\n"]}