@bldrs-ai/conway 1.516.1463 → 1.519.1471
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/compiled/examples/browser-bundled.cjs +1 -1
- package/compiled/examples/cli-bundled.cjs +1 -1
- package/compiled/examples/cli-step-bundled.cjs +1 -1
- package/compiled/examples/validator-bundled.cjs +1 -1
- package/compiled/src/compat/web-ifc/spatial_imposter.d.ts.map +1 -1
- package/compiled/src/compat/web-ifc/spatial_imposter.js +265 -56
- package/compiled/src/compat/web-ifc/spatial_imposter.test.js +179 -4
- package/compiled/src/compat/web-ifc/store_preview_channel.d.ts +106 -8
- package/compiled/src/compat/web-ifc/store_preview_channel.d.ts.map +1 -1
- package/compiled/src/compat/web-ifc/store_preview_channel.js +205 -13
- package/compiled/src/compat/web-ifc/store_preview_channel.test.js +160 -7
- package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts +12 -0
- package/compiled/src/compat/web-ifc/streamed_preview_channel.d.ts.map +1 -1
- package/compiled/src/version/version.js +1 -1
- package/compiled/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -30,7 +30,7 @@ var import_node_process = require("node:process");
|
|
|
30
30
|
var readline = __toESM(require("node:readline"), 1);
|
|
31
31
|
|
|
32
32
|
// compiled/src/version/version.js
|
|
33
|
-
var versionString = "Conway v1.
|
|
33
|
+
var versionString = "Conway v1.519.1471";
|
|
34
34
|
|
|
35
35
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
36
36
|
var wasmType = "";
|
|
@@ -14965,7 +14965,7 @@ ${t5.join("\n")}` : "";
|
|
|
14965
14965
|
var import_process = require("process");
|
|
14966
14966
|
|
|
14967
14967
|
// compiled/src/version/version.js
|
|
14968
|
-
var versionString = "Conway v1.
|
|
14968
|
+
var versionString = "Conway v1.519.1471";
|
|
14969
14969
|
|
|
14970
14970
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
14971
14971
|
function pThreadsAllowed() {
|
|
@@ -15943,7 +15943,7 @@ var ParsingBuffer = class {
|
|
|
15943
15943
|
};
|
|
15944
15944
|
|
|
15945
15945
|
// compiled/src/version/version.js
|
|
15946
|
-
var versionString = "Conway v1.
|
|
15946
|
+
var versionString = "Conway v1.519.1471";
|
|
15947
15947
|
|
|
15948
15948
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
15949
15949
|
function pThreadsAllowed() {
|
|
@@ -944,7 +944,7 @@ var EntityTypesIfcCount = 909;
|
|
|
944
944
|
var entity_types_ifc_gen_default = EntityTypesIfc;
|
|
945
945
|
|
|
946
946
|
// compiled/src/version/version.js
|
|
947
|
-
var versionString = "Conway v1.
|
|
947
|
+
var versionString = "Conway v1.519.1471";
|
|
948
948
|
|
|
949
949
|
// compiled/dependencies/conway-geom/interface/conway_geometry.js
|
|
950
950
|
var wasmType = "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spatial_imposter.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/spatial_imposter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,0BAA0B,CAAA;AAgBxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAKpE,gEAAgE;AAChE,eAAO,MAAM,sBAAsB;;;;;CAA+B,CAAA;AAqBlE,+DAA+D;AAC/D,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9B;AA0BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAE,IAAI,EAAE,KAAK,GAAI,MAAM,EAAE,CAerD;
|
|
1
|
+
{"version":3,"file":"spatial_imposter.d.ts","sourceRoot":"","sources":["../../../../src/compat/web-ifc/spatial_imposter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,YAAY,MAAM,0BAA0B,CAAA;AAgBxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAA;AAKpE,gEAAgE;AAChE,eAAO,MAAM,sBAAsB;;;;;CAA+B,CAAA;AAqBlE,+DAA+D;AAC/D,MAAM,WAAW,KAAK;IACpB,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9B;AA0BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAE,IAAI,EAAE,KAAK,GAAI,MAAM,EAAE,CAerD;AA6DD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAE,QAAQ,EAAE,MAAM,GAAI,MAAM,CAGlE;AAGD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACjC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GAAI,OAAO,CAW5B;AAGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAC3B,CAAC,EAAE,KAAK,EACR,CAAC,EAAE,KAAK,EACR,GAAG,GAAE,MAAqB,EAC1B,OAAO,GAAE,MAAmB,GAAI,OAAO,CAiB1C;AAGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAE,GAAG,KAAK,EAAE,CAAE,KAAK,GAAG,SAAS,CAAE,EAAE,GAAI,KAAK,GAAG,SAAS,CA4BhF;AAoWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuDG;AACH,wBAAsB,6BAA6B,CAC/C,KAAK,EAAE,YAAY,EACnB,MAAM,EAAE,CAAE,IAAI,EAAE,kBAAkB,KAAM,IAAI,EAC5C,kBAAkB,CAAC,EAAE,SAAS,CAAE,MAAM,CAAE,EACxC,mBAAmB,GAAE,MAAU,GAAI,OAAO,CAAE,MAAM,CAAE,CAkevD"}
|
|
@@ -83,7 +83,33 @@ const PRODUCT_PLACEMENT = [5, 5, 3];
|
|
|
83
83
|
const LOCAL_PLACEMENT_REL_TO = [0, 0, 1];
|
|
84
84
|
const LOCAL_PLACEMENT_RELATIVE = [1, 0, 1];
|
|
85
85
|
const PLACEMENT_LOCATION = [0, 0, 2];
|
|
86
|
+
const PLACEMENT_AXIS = [1, 1, 3];
|
|
87
|
+
const PLACEMENT_REF_DIRECTION = [2, 1, 3];
|
|
86
88
|
const STOREY_ELEVATION = [9, 9, 6];
|
|
89
|
+
const IDENTITY_PLACEMENT = [
|
|
90
|
+
1,
|
|
91
|
+
0,
|
|
92
|
+
0,
|
|
93
|
+
0,
|
|
94
|
+
0,
|
|
95
|
+
1,
|
|
96
|
+
0,
|
|
97
|
+
0,
|
|
98
|
+
0,
|
|
99
|
+
0,
|
|
100
|
+
1,
|
|
101
|
+
0,
|
|
102
|
+
0,
|
|
103
|
+
0,
|
|
104
|
+
0,
|
|
105
|
+
1,
|
|
106
|
+
];
|
|
107
|
+
/** Column-major indices of a 4x4's translation component. */
|
|
108
|
+
const TRANSLATION = [12, 13, 14];
|
|
109
|
+
/** Column-major index of the local +Z axis' world Z component. */
|
|
110
|
+
const LOCAL_Z_WORLD_Z = 10;
|
|
111
|
+
/** Cross-product magnitude below which two unit axes are parallel. */
|
|
112
|
+
const MIN_AXIS_CROSS = 1e-9;
|
|
87
113
|
const SPACE_TYPES = new Set([
|
|
88
114
|
EntityTypesIfc.IFCSPACE,
|
|
89
115
|
EntityTypesIfc.IFCSPATIALZONE,
|
|
@@ -192,15 +218,188 @@ async function ensure_(model, localID) {
|
|
|
192
218
|
await model.ensureResidentByLocalID(localID);
|
|
193
219
|
}
|
|
194
220
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
221
|
+
* @param v A vector.
|
|
222
|
+
* @return {[number, number, number] | undefined} `v` scaled to unit
|
|
223
|
+
* length, or undefined when it has none (zero / non-finite input).
|
|
224
|
+
*/
|
|
225
|
+
function normalize3_(v) {
|
|
226
|
+
const length = Math.hypot(v[0], v[1], v[2]);
|
|
227
|
+
return Number.isFinite(length) && length > 0 ?
|
|
228
|
+
[v[0] / length, v[1] / length, v[2] / length] : void 0;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* @param a Left vector.
|
|
232
|
+
* @param b Right vector.
|
|
233
|
+
* @return {[number, number, number]} `a` x `b`.
|
|
234
|
+
*/
|
|
235
|
+
function cross3_(a, b) {
|
|
236
|
+
return [
|
|
237
|
+
a[1] * b[2] - a[2] * b[1],
|
|
238
|
+
a[2] * b[0] - a[0] * b[2],
|
|
239
|
+
a[0] * b[1] - a[1] * b[0],
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The `DirectionRatios` of an IfcDirection referenced from `field` of
|
|
244
|
+
* `entity`, or undefined when the field is absent.
|
|
245
|
+
*
|
|
246
|
+
* @param model The model.
|
|
247
|
+
* @param entity The referring entity.
|
|
248
|
+
* @param field The (index, min, max) field triple.
|
|
249
|
+
* @return {Promise<[number, number, number] | undefined>} The ratios.
|
|
250
|
+
*/
|
|
251
|
+
async function directionRatios_(model, entity, field) {
|
|
252
|
+
const directionID = entity.extractReferenceLocalID(field[0], field[1], field[2], true);
|
|
253
|
+
if (directionID === null) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
await ensure_(model, directionID);
|
|
257
|
+
const direction = model.getElementByLocalID(directionID);
|
|
258
|
+
const ratios = direction?.DirectionRatios;
|
|
259
|
+
if (ratios === void 0 || ratios.length < 2) {
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
return [ratios[0], ratios[1], ratios[2] ?? 0];
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* The local +X reference to use when a placement states an `Axis` but no
|
|
266
|
+
* `RefDirection`: world +X, unless that is (nearly) parallel to the
|
|
267
|
+
* stated axis, in which case world +Y.
|
|
268
|
+
*
|
|
269
|
+
* IFC's own `IfcFirstProjAxis` makes exactly this substitution, and
|
|
270
|
+
* without it a lie-down placement — `Axis = (1,0,0)`, RefDirection
|
|
271
|
+
* omitted, which is how a wall-mounted or rotated-into-plan element is
|
|
272
|
+
* commonly written — has its Axis cross the defaulted (1,0,0) to zero
|
|
273
|
+
* and falls all the way back to the world frame, discarding a perfectly
|
|
274
|
+
* well-formed rotation. conway-geom divides by that zero cross product
|
|
275
|
+
* and returns NaN, so matching the C++ settles nothing here; keeping the
|
|
276
|
+
* stated Axis is strictly better than dropping it.
|
|
277
|
+
*
|
|
278
|
+
* @param zAxis The normalized local +Z.
|
|
279
|
+
* @return {[number, number, number]} A reference not parallel to it.
|
|
280
|
+
*/
|
|
281
|
+
function defaultRefDirection_(zAxis) {
|
|
282
|
+
// |cross(zAxis, worldX)| — world +X stays the default (which is what
|
|
283
|
+
// the IFC default says, and what the overwhelmingly common
|
|
284
|
+
// zAxis = (0,0,1) needs) until it degenerates.
|
|
285
|
+
return Math.hypot(zAxis[1], zAxis[2]) > MIN_AXIS_CROSS ?
|
|
286
|
+
[1, 0, 0] : [0, 1, 0];
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The 4x4 an IfcAxis2Placement denotes: Location as the translation,
|
|
290
|
+
* and — on the 3D form only — Axis as local +Z and RefDirection as local
|
|
291
|
+
* +X, both defaulting to the world axes when absent per the IFC
|
|
292
|
+
* defaults.
|
|
293
|
+
*
|
|
294
|
+
* The 2D form has to be handled, not assumed away.
|
|
295
|
+
* `IfcLocalPlacement.RelativePlacement` is
|
|
296
|
+
* `IfcAxis2Placement2D | IfcAxis2Placement3D`, and the two disagree on
|
|
297
|
+
* every field past Location: offset 1 is `Axis` on the 3D entity but
|
|
298
|
+
* `RefDirection` on the 2D one, and offset 2 does not exist there at
|
|
299
|
+
* all, so reading it throws "too few fields in record". That throw would
|
|
300
|
+
* escape to {@link placementTransform_}'s catch, which memoizes
|
|
301
|
+
* `undefined` for the placement — and a memoized undefined is not merely
|
|
302
|
+
* a lost sample: every descendant placement then takes the
|
|
303
|
+
* no-parent branch and is composed in LOCAL coordinates, silently
|
|
304
|
+
* reparenting a whole subtree to the origin. So the direction fields are
|
|
305
|
+
* read only once the type is confirmed 3D, which also restores the
|
|
306
|
+
* pre-#517 behaviour for 2D links (translation only).
|
|
307
|
+
*
|
|
308
|
+
* Deliberately the same construction as conway-geom's
|
|
309
|
+
* `GetAxis2Placement3D` (ConwayGeometryProcessor.cpp) — normalize the
|
|
310
|
+
* two references, `y = normalize(z x x)`, then re-derive
|
|
311
|
+
* `x = normalize(y x z)` so a non-perpendicular RefDirection is
|
|
312
|
+
* projected rather than skewing the frame. It has to match: the plates
|
|
313
|
+
* are previewing meshes the durable pipeline places with that function,
|
|
314
|
+
* and a second convention here (Gram-Schmidt on x, say) would rotate
|
|
315
|
+
* them apart on any file whose RefDirection is off-perpendicular.
|
|
316
|
+
*
|
|
317
|
+
* One deviation, on degenerate input: where the C++ divides by a zero
|
|
318
|
+
* cross product and yields NaN, this falls back to the identity axes. A
|
|
319
|
+
* NaN transform reaching Share is an invisible or scene-destroying box,
|
|
320
|
+
* and the imposter path's standing rule is that a preview must never
|
|
321
|
+
* break open.
|
|
322
|
+
*
|
|
323
|
+
* @param model The model.
|
|
324
|
+
* @param axisLocalID The IfcAxis2Placement3D's local ID.
|
|
325
|
+
* @return {Promise<Mat4>} The placement, identity if unreadable.
|
|
326
|
+
*/
|
|
327
|
+
async function axisPlacementMatrix_(model, axisLocalID) {
|
|
328
|
+
await ensure_(model, axisLocalID);
|
|
329
|
+
const placement = model.getElementByLocalID(axisLocalID);
|
|
330
|
+
if (placement === void 0) {
|
|
331
|
+
return IDENTITY_PLACEMENT.slice();
|
|
332
|
+
}
|
|
333
|
+
let translation = [0, 0, 0];
|
|
334
|
+
const locationID = placement.extractReferenceLocalID(PLACEMENT_LOCATION[0], PLACEMENT_LOCATION[1], PLACEMENT_LOCATION[2], false);
|
|
335
|
+
if (locationID !== null) {
|
|
336
|
+
await ensure_(model, locationID);
|
|
337
|
+
const point = model.getElementByLocalID(locationID);
|
|
338
|
+
const coords = point?.Coordinates;
|
|
339
|
+
if (coords !== void 0 && coords.length >= 2) {
|
|
340
|
+
translation = [coords[0], coords[1], coords[2] ?? 0];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const is3D = model.typeIDOf(axisLocalID) === EntityTypesIfc.IFCAXIS2PLACEMENT3D;
|
|
344
|
+
const axisRatios = is3D ?
|
|
345
|
+
await directionRatios_(model, placement, PLACEMENT_AXIS) : void 0;
|
|
346
|
+
const refRatios = is3D ?
|
|
347
|
+
await directionRatios_(model, placement, PLACEMENT_REF_DIRECTION) : void 0;
|
|
348
|
+
let zAxis = (axisRatios !== void 0 ? normalize3_(axisRatios) : void 0) ?? [0, 0, 1];
|
|
349
|
+
let refAxis = (refRatios !== void 0 ? normalize3_(refRatios) : void 0) ??
|
|
350
|
+
defaultRefDirection_(zAxis);
|
|
351
|
+
let yAxis = normalize3_(cross3_(zAxis, refAxis));
|
|
352
|
+
if (yAxis === void 0) {
|
|
353
|
+
// Axis and RefDirection are parallel (or garbage): nothing here
|
|
354
|
+
// determines a frame, so keep the translation and drop back to world
|
|
355
|
+
// axes. Only reachable with an explicit RefDirection now — an Axis
|
|
356
|
+
// on its own gets a perpendicular default instead of being thrown
|
|
357
|
+
// away with it (see defaultRefDirection_).
|
|
358
|
+
zAxis = [0, 0, 1];
|
|
359
|
+
refAxis = [1, 0, 0];
|
|
360
|
+
yAxis = [0, 1, 0];
|
|
361
|
+
}
|
|
362
|
+
const xAxis = normalize3_(cross3_(yAxis, zAxis)) ?? refAxis;
|
|
363
|
+
return [
|
|
364
|
+
xAxis[0],
|
|
365
|
+
xAxis[1],
|
|
366
|
+
xAxis[2],
|
|
367
|
+
0,
|
|
368
|
+
yAxis[0],
|
|
369
|
+
yAxis[1],
|
|
370
|
+
yAxis[2],
|
|
371
|
+
0,
|
|
372
|
+
zAxis[0],
|
|
373
|
+
zAxis[1],
|
|
374
|
+
zAxis[2],
|
|
375
|
+
0,
|
|
376
|
+
translation[0],
|
|
377
|
+
translation[1],
|
|
378
|
+
translation[2],
|
|
379
|
+
1,
|
|
380
|
+
];
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* The composed world transform of an IfcLocalPlacement chain: every
|
|
384
|
+
* link's IfcAxis2Placement3D built out in full and multiplied
|
|
385
|
+
* `parent * local`, which is exactly conway-geom's `GetLocalPlacement`.
|
|
386
|
+
*
|
|
387
|
+
* Rotation used to be dropped here — the chain summed Location
|
|
388
|
+
* translations and nothing else (conway#514's shortcut). That is correct
|
|
389
|
+
* only while every ancestor placement is axis-aligned, and Revit
|
|
390
|
+
* routinely rotates the site or building placement to true north, so on
|
|
391
|
+
* a real export every sampled origin landed unrotated in raw model space
|
|
392
|
+
* while the durable meshes beside it did not: the plate cloud read as a
|
|
393
|
+
* rotated ghost of the building (conway#517).
|
|
197
394
|
*
|
|
198
395
|
* @param model The model.
|
|
199
396
|
* @param placementLocalID IfcObjectPlacement local ID.
|
|
200
|
-
* @param cache Memo.
|
|
201
|
-
*
|
|
397
|
+
* @param cache Memo of composed transforms, keyed by placement local ID.
|
|
398
|
+
* A placement re-entered while it is being composed (a cyclic RelativeTo)
|
|
399
|
+
* reads back undefined and stops the recursion.
|
|
400
|
+
* @return {Promise<Mat4 | undefined>} The world transform.
|
|
202
401
|
*/
|
|
203
|
-
async function
|
|
402
|
+
async function placementTransform_(model, placementLocalID, cache) {
|
|
204
403
|
if (cache.has(placementLocalID)) {
|
|
205
404
|
return cache.get(placementLocalID);
|
|
206
405
|
}
|
|
@@ -216,37 +415,16 @@ async function placementOrigin_(model, placementLocalID, cache) {
|
|
|
216
415
|
return;
|
|
217
416
|
}
|
|
218
417
|
const relativeID = entity.extractReferenceLocalID(LOCAL_PLACEMENT_RELATIVE[0], LOCAL_PLACEMENT_RELATIVE[1], LOCAL_PLACEMENT_RELATIVE[2], false);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const relative = model.getElementByLocalID(relativeID);
|
|
223
|
-
if (relative !== void 0) {
|
|
224
|
-
const locationID = relative.extractReferenceLocalID(PLACEMENT_LOCATION[0], PLACEMENT_LOCATION[1], PLACEMENT_LOCATION[2], false);
|
|
225
|
-
if (locationID !== null) {
|
|
226
|
-
await ensure_(model, locationID);
|
|
227
|
-
const point = model.getElementByLocalID(locationID);
|
|
228
|
-
const coords = point?.Coordinates;
|
|
229
|
-
if (coords !== void 0 && coords.length >= 2) {
|
|
230
|
-
local = [coords[0], coords[1], coords[2] ?? 0];
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
}
|
|
418
|
+
const local = relativeID !== null ?
|
|
419
|
+
await axisPlacementMatrix_(model, relativeID) :
|
|
420
|
+
IDENTITY_PLACEMENT.slice();
|
|
235
421
|
const parentID = entity.extractReferenceLocalID(LOCAL_PLACEMENT_REL_TO[0], LOCAL_PLACEMENT_REL_TO[1], LOCAL_PLACEMENT_REL_TO[2], true);
|
|
236
422
|
if (parentID === null) {
|
|
237
423
|
cache.set(placementLocalID, local);
|
|
238
424
|
return local;
|
|
239
425
|
}
|
|
240
|
-
const parent = await
|
|
241
|
-
|
|
242
|
-
cache.set(placementLocalID, local);
|
|
243
|
-
return local;
|
|
244
|
-
}
|
|
245
|
-
const world = [
|
|
246
|
-
parent[0] + local[0],
|
|
247
|
-
parent[1] + local[1],
|
|
248
|
-
parent[2] + local[2],
|
|
249
|
-
];
|
|
426
|
+
const parent = await placementTransform_(model, parentID, cache);
|
|
427
|
+
const world = parent === void 0 ? local : mat4MultiplyF64(parent, local);
|
|
250
428
|
cache.set(placementLocalID, world);
|
|
251
429
|
return world;
|
|
252
430
|
}
|
|
@@ -254,7 +432,19 @@ async function placementOrigin_(model, placementLocalID, cache) {
|
|
|
254
432
|
return;
|
|
255
433
|
}
|
|
256
434
|
}
|
|
257
|
-
|
|
435
|
+
/**
|
|
436
|
+
* @param transform A composed world transform.
|
|
437
|
+
* @return {[number, number, number]} Its translation column, i.e. where
|
|
438
|
+
* the placement's own origin lands in world space.
|
|
439
|
+
*/
|
|
440
|
+
function originOf_(transform) {
|
|
441
|
+
return [
|
|
442
|
+
transform[TRANSLATION[0]],
|
|
443
|
+
transform[TRANSLATION[1]],
|
|
444
|
+
transform[TRANSLATION[2]],
|
|
445
|
+
];
|
|
446
|
+
}
|
|
447
|
+
async function productTransform_(model, localID, cache) {
|
|
258
448
|
await ensure_(model, localID);
|
|
259
449
|
const entity = model.getElementByLocalID(localID);
|
|
260
450
|
if (entity === void 0) {
|
|
@@ -264,7 +454,7 @@ async function productOrigin_(model, localID, cache) {
|
|
|
264
454
|
if (placementID === null) {
|
|
265
455
|
return;
|
|
266
456
|
}
|
|
267
|
-
return
|
|
457
|
+
return placementTransform_(model, placementID, cache);
|
|
268
458
|
}
|
|
269
459
|
/**
|
|
270
460
|
* Walk the IFC spatial tree and emit AABB imposters for Share.
|
|
@@ -463,13 +653,16 @@ export async function emitSpatialStructureImposters(model, onMesh, coordinationM
|
|
|
463
653
|
queue.push(childID);
|
|
464
654
|
}
|
|
465
655
|
}
|
|
466
|
-
const
|
|
656
|
+
const placementCache = new Map();
|
|
467
657
|
for (const node of nodes.values()) {
|
|
468
658
|
try {
|
|
469
659
|
// IfcProject is an IfcContext, not an IfcProduct — field 5 is
|
|
470
660
|
// not ObjectPlacement.
|
|
471
661
|
if (node.typeID !== EntityTypesIfc.IFCPROJECT) {
|
|
472
|
-
node.
|
|
662
|
+
node.placement =
|
|
663
|
+
await productTransform_(model, node.localID, placementCache);
|
|
664
|
+
node.origin = node.placement !== void 0 ?
|
|
665
|
+
originOf_(node.placement) : void 0;
|
|
473
666
|
}
|
|
474
667
|
if (STOREY_TYPES.has(node.typeID)) {
|
|
475
668
|
await ensure_(model, node.localID);
|
|
@@ -492,9 +685,9 @@ export async function emitSpatialStructureImposters(model, onMesh, coordinationM
|
|
|
492
685
|
const origins = [];
|
|
493
686
|
for (let i = 0; i < products.length && origins.length < PRODUCT_SAMPLE; i += stride) {
|
|
494
687
|
try {
|
|
495
|
-
const
|
|
496
|
-
if (
|
|
497
|
-
origins.push(
|
|
688
|
+
const transform = await productTransform_(model, products[i], placementCache);
|
|
689
|
+
if (transform !== void 0) {
|
|
690
|
+
origins.push(originOf_(transform));
|
|
498
691
|
}
|
|
499
692
|
}
|
|
500
693
|
catch {
|
|
@@ -564,7 +757,7 @@ export async function emitSpatialStructureImposters(model, onMesh, coordinationM
|
|
|
564
757
|
// own placement Z; the two only part on files that leave every
|
|
565
758
|
// storey placement on the datum.
|
|
566
759
|
const storeyZ0 = STOREY_TYPES.has(node.typeID) && node.elevation !== void 0 ?
|
|
567
|
-
|
|
760
|
+
storeyFloorZ_(localID, node.elevation, nodes, parentOf) : void 0;
|
|
568
761
|
if (node.origin !== void 0) {
|
|
569
762
|
// The placement point contributes this node's XY footprint. For
|
|
570
763
|
// a storey with an Elevation its Z is the DATUM, not its
|
|
@@ -597,7 +790,12 @@ export async function emitSpatialStructureImposters(model, onMesh, coordinationM
|
|
|
597
790
|
// elevation), but a file that parks every storey placement on
|
|
598
791
|
// the building datum and carries the height only in Elevation
|
|
599
792
|
// would then collapse all of its plates onto one Z. Storey
|
|
600
|
-
// HEIGHTS stay on elevation deltas — datum-invariant either way
|
|
793
|
+
// HEIGHTS stay on elevation deltas — datum-invariant either way,
|
|
794
|
+
// though on a datum tilted out of plumb they over-thicken the
|
|
795
|
+
// plate by 1/cos(tilt) (the floor Z is tilt-correct; see
|
|
796
|
+
// storeyFloorZ_). Left alone: the sample-derived height fallback
|
|
797
|
+
// is already a world-Z span, so one scale cannot serve both, and
|
|
798
|
+
// a tilted building has no horizontal storeys to plate anyway.
|
|
601
799
|
const z0 = storeyZ0 ?? node.origin?.[2] ?? box.min[2];
|
|
602
800
|
const z1 = height !== void 0 ? z0 + height : box.max[2];
|
|
603
801
|
box = {
|
|
@@ -659,33 +857,44 @@ export async function emitSpatialStructureImposters(model, onMesh, coordinationM
|
|
|
659
857
|
return emitted;
|
|
660
858
|
}
|
|
661
859
|
/**
|
|
662
|
-
* World Z of
|
|
663
|
-
* from
|
|
664
|
-
* IfcBuilding in a
|
|
860
|
+
* World Z of an IfcBuildingStorey's floor: `Elevation` measured up the
|
|
861
|
+
* datum's own +Z from the datum's world origin, where the datum is the
|
|
862
|
+
* nearest ancestor with a resolved placement (the IfcBuilding in a
|
|
863
|
+
* well-formed file).
|
|
864
|
+
*
|
|
865
|
+
* Elevation is a length along the datum's Z, not along world Z, so the
|
|
866
|
+
* datum's whole transform is needed and not just its height — on a
|
|
867
|
+
* building tilted out of plumb the two differ by `cos(tilt)`. Only the
|
|
868
|
+
* Z component is taken, because that is all the banding uses: the plate
|
|
869
|
+
* keeps the XY footprint its placement and contained samples give it.
|
|
870
|
+
* A tilted building's storeys are not horizontal anyway, so the
|
|
871
|
+
* axis-aligned plate is an approximation there by construction — this
|
|
872
|
+
* just stops it being an approximation on the *height* as well.
|
|
665
873
|
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
* nothing better to anchor on.
|
|
874
|
+
* Falls back to a world-relative Elevation (datum at zero, +Z up) when
|
|
875
|
+
* nothing up the chain resolved a placement: the pre-fix behaviour, and
|
|
876
|
+
* the only thing left to anchor on.
|
|
670
877
|
*
|
|
671
878
|
* @param localID The storey.
|
|
879
|
+
* @param elevation The storey's `Elevation`, in source units.
|
|
672
880
|
* @param nodes All walked nodes.
|
|
673
881
|
* @param parentOf Child -> parent.
|
|
674
|
-
* @return {number} The
|
|
882
|
+
* @return {number} The floor's world Z.
|
|
675
883
|
*/
|
|
676
|
-
function
|
|
884
|
+
function storeyFloorZ_(localID, elevation, nodes, parentOf) {
|
|
677
885
|
let id = parentOf.get(localID);
|
|
678
886
|
// Bounded rather than while-truthy: parentOf is built by a BFS and so
|
|
679
887
|
// should be acyclic, but this module treats the aggregate graph as
|
|
680
888
|
// untrusted everywhere else too (see `computing` in aabbOf).
|
|
681
889
|
for (let hops = 0; id !== void 0 && hops <= nodes.size; ++hops) {
|
|
682
|
-
const
|
|
683
|
-
if (
|
|
684
|
-
return
|
|
890
|
+
const placement = nodes.get(id)?.placement;
|
|
891
|
+
if (placement !== void 0) {
|
|
892
|
+
return placement[TRANSLATION[2]] +
|
|
893
|
+
elevation * placement[LOCAL_Z_WORLD_Z];
|
|
685
894
|
}
|
|
686
895
|
id = parentOf.get(id);
|
|
687
896
|
}
|
|
688
|
-
return
|
|
897
|
+
return elevation;
|
|
689
898
|
}
|
|
690
899
|
/**
|
|
691
900
|
* The durable coordination frame, derived the way the preview channels
|
|
@@ -699,9 +908,9 @@ function storeyDatumZ_(localID, nodes, parentOf) {
|
|
|
699
908
|
* Best-effort by construction, and the failure has two shapes. Beyond
|
|
700
909
|
* LARGE_COORDINATE_BUDGET_M both sides snap to COORDINATION_SNAP_M and
|
|
701
910
|
* agree as long as the spatial root and the first geometry share a grid
|
|
702
|
-
* cell. Worse: the anchor comes from `
|
|
703
|
-
* IfcLocalPlacement
|
|
704
|
-
*
|
|
911
|
+
* cell. Worse: the anchor comes from `placementTransform_`, which reads
|
|
912
|
+
* IfcLocalPlacement chains and never looks at geometry — so a file that
|
|
913
|
+
* keeps identity placements and bakes absolute coordinates into the
|
|
705
914
|
* vertices (civil/GIS exports do this) has a root box near zero and gets
|
|
706
915
|
* no recentre at all, while the durable walk anchors tens of km out and
|
|
707
916
|
* does. That is a full site-offset, not a one-cell disagreement. Whenever
|