@thatopen/components 3.4.6 → 3.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -197,6 +197,15 @@ class BaseRenderer extends BaseWorldItem {
197
197
  * The list of [clipping planes](https://threejs.org/docs/#api/en/renderers/WebGLRenderer.clippingPlanes) used by this instance of the renderer.
198
198
  */
199
199
  __publicField(this, "clippingPlanes", []);
200
+ /**
201
+ * The default `isLocal` value used by {@link setPlane} when the caller
202
+ * doesn't pass one. When `true`, planes added without an explicit
203
+ * `isLocal` stay out of the global `three.clippingPlanes` list and are
204
+ * clipped per-material instead. The {@link Clipper} sets this to mirror
205
+ * its own `localClippingPlanes` flag, so consumers like `Views` that add
206
+ * their own planes automatically inherit the active clipping mode.
207
+ */
208
+ __publicField(this, "localClippingPlanes", false);
200
209
  }
201
210
  /**
202
211
  * Updates the clipping planes and triggers the `onClippingPlanesUpdated` event.
@@ -222,7 +231,7 @@ class BaseRenderer extends BaseWorldItem {
222
231
  * The `three.clippingPlanes` property is then updated to reflect the current state of the `clippingPlanes` array,
223
232
  * excluding any planes marked as local.
224
233
  */
225
- setPlane(active, plane, isLocal) {
234
+ setPlane(active, plane, isLocal = this.localClippingPlanes) {
226
235
  plane.isLocal = isLocal;
227
236
  const index = this.clippingPlanes.indexOf(plane);
228
237
  if (active && index === -1) {
@@ -233,6 +242,7 @@ class BaseRenderer extends BaseWorldItem {
233
242
  this.three.clippingPlanes = this.clippingPlanes.filter(
234
243
  (plane2) => !plane2.isLocal
235
244
  );
245
+ this.updateClippingPlanes();
236
246
  }
237
247
  }
238
248
  const _Disposer = class _Disposer extends Component {
@@ -9487,9 +9497,11 @@ class VisibilityCuller {
9487
9497
  const tilesX = Math.ceil(pixelWidth / maxTextureSize);
9488
9498
  const tilesY = Math.ceil(pixelHeight / maxTextureSize);
9489
9499
  target.setSize(Math.ceil(pixelWidth / tilesX), Math.ceil(pixelHeight / tilesY));
9500
+ const yMargin = Math.max(size.y, 1) * 0.01;
9490
9501
  camera.rotation.x = -Math.PI / 2;
9491
- camera.far = box.max.y - box.min.y + camera.near;
9492
- camera.position.y = box.max.y + camera.near;
9502
+ camera.near = 0;
9503
+ camera.far = size.y + 2 * yMargin;
9504
+ camera.position.y = box.max.y + yMargin;
9493
9505
  const idScene = new THREE.Scene();
9494
9506
  const originalMaterials = /* @__PURE__ */ new Map();
9495
9507
  const originalParents = /* @__PURE__ */ new Map();
@@ -9523,7 +9535,7 @@ class VisibilityCuller {
9523
9535
  const tileMaxZ = tileMinZ + stepZ;
9524
9536
  const tileCenterX = (tileMinX + tileMaxX) / 2;
9525
9537
  const tileCenterZ = (tileMinZ + tileMaxZ) / 2;
9526
- camera.position.set(tileCenterX, box.max.y, tileCenterZ);
9538
+ camera.position.set(tileCenterX, box.max.y + yMargin, tileCenterZ);
9527
9539
  const halfWidth = stepX / 2;
9528
9540
  const halfHeight = stepZ / 2;
9529
9541
  camera.left = -halfWidth;
@@ -9572,6 +9584,11 @@ class IDMaterial extends THREE.ShaderMaterial {
9572
9584
  super({
9573
9585
  glslVersion: THREE.GLSL3,
9574
9586
  blending: THREE.NoBlending,
9587
+ // A flat item (a slab) seen straight down shows only one face; if its
9588
+ // winding puts the back face toward the camera it wouldn't rasterize and
9589
+ // the item would be culled. The pass only cares about coverage, so render
9590
+ // both sides.
9591
+ side: THREE.DoubleSide,
9575
9592
  uniforms: {
9576
9593
  objectId: { value: new THREE.Vector4() }
9577
9594
  },
@@ -12562,6 +12579,19 @@ const _FastModelPicker = class _FastModelPicker {
12562
12579
  * restored after.
12563
12580
  */
12564
12581
  __publicField(this, "_hiddenLods", []);
12582
+ /**
12583
+ * The meshes each model owns, i.e. excluding those belonging to a
12584
+ * model nested inside it. A delta model's object is parented under
12585
+ * its parent model's object (see the fragments EditHelper), while
12586
+ * both are listed as separate models, so subtree membership alone
12587
+ * does not determine ownership.
12588
+ *
12589
+ * Populated in {@link applyIdMaterial}, consumed by
12590
+ * {@link renderPickPass} to isolate one model per render without
12591
+ * relying on root visibility. A nested model can't be isolated by
12592
+ * its root, since hiding the parent's root culls the child too.
12593
+ */
12594
+ __publicField(this, "_ownMeshes", /* @__PURE__ */ new Map());
12565
12595
  if (!world.renderer) {
12566
12596
  throw new Error("A renderer is needed for the FastModelPicker to work!");
12567
12597
  }
@@ -12802,14 +12832,14 @@ const _FastModelPicker = class _FastModelPicker {
12802
12832
  applyIdMaterial() {
12803
12833
  const fragments = this.components.get(FragmentsManager);
12804
12834
  const byteToModel = /* @__PURE__ */ new Map();
12835
+ const modelRoots = this.getModelRoots();
12836
+ this._ownMeshes.clear();
12805
12837
  let nextByte = 1;
12806
12838
  for (const [modelId, model] of fragments.list) {
12807
12839
  if (nextByte > _FastModelPicker.MAX_MODELS)
12808
12840
  break;
12809
- const byte = nextByte;
12810
- nextByte += 1;
12811
- let any = false;
12812
- model.object.traverse((child) => {
12841
+ const owned = [];
12842
+ this.traverseOwn(model.object, modelRoots, (child) => {
12813
12843
  if (!(child instanceof THREE__namespace.Mesh))
12814
12844
  return;
12815
12845
  const geom = child.geometry;
@@ -12826,13 +12856,44 @@ const _FastModelPicker = class _FastModelPicker {
12826
12856
  const original = child.material;
12827
12857
  const len = Array.isArray(original) ? original.length : 1;
12828
12858
  child.material = new Array(len).fill(this._idMaterial);
12829
- any = true;
12859
+ owned.push(child);
12830
12860
  });
12831
- if (any)
12832
- byteToModel.set(byte, modelId);
12861
+ if (!owned.length)
12862
+ continue;
12863
+ this._ownMeshes.set(modelId, owned);
12864
+ byteToModel.set(nextByte, modelId);
12865
+ nextByte += 1;
12833
12866
  }
12834
12867
  return byteToModel;
12835
12868
  }
12869
+ /**
12870
+ * The object roots of every loaded model.
12871
+ */
12872
+ getModelRoots() {
12873
+ const fragments = this.components.get(FragmentsManager);
12874
+ const roots = /* @__PURE__ */ new Set();
12875
+ for (const [, model] of fragments.list)
12876
+ roots.add(model.object);
12877
+ return roots;
12878
+ }
12879
+ /**
12880
+ * Like `Object3D.traverse`, but prunes the subtree of any nested model
12881
+ * root, so each mesh is visited exactly once, by the model that owns
12882
+ * it. A delta model's object hangs under its parent model's object
12883
+ * while being a model in its own right; a plain `traverse` from the
12884
+ * parent would walk into it.
12885
+ */
12886
+ traverseOwn(root, modelRoots, callback) {
12887
+ const stack = [root];
12888
+ while (stack.length) {
12889
+ const node = stack.pop();
12890
+ if (node !== root && modelRoots.has(node))
12891
+ continue;
12892
+ callback(node);
12893
+ for (const child of node.children)
12894
+ stack.push(child);
12895
+ }
12896
+ }
12836
12897
  /**
12837
12898
  * Renders the world scene to the id target one model at a time,
12838
12899
  * flipping the `modelByte` uniform between renders. The depth buffer
@@ -12923,21 +12984,47 @@ const _FastModelPicker = class _FastModelPicker {
12923
12984
  nonBimVisibility.set(child, child.visible);
12924
12985
  child.visible = false;
12925
12986
  });
12926
- const bimVisibilityBefore = /* @__PURE__ */ new Map();
12987
+ const wasOnScreen = /* @__PURE__ */ new Map();
12988
+ for (const [modelId, obj] of objectsByModel) {
12989
+ let visible = true;
12990
+ let node = obj;
12991
+ while (node) {
12992
+ if (!node.visible) {
12993
+ visible = false;
12994
+ break;
12995
+ }
12996
+ node = node.parent;
12997
+ }
12998
+ wasOnScreen.set(modelId, visible);
12999
+ }
13000
+ const rootVisibilityBefore = /* @__PURE__ */ new Map();
12927
13001
  for (const obj of objectsByModel.values()) {
12928
- bimVisibilityBefore.set(obj, obj.visible);
12929
- obj.visible = false;
13002
+ rootVisibilityBefore.set(obj, obj.visible);
13003
+ obj.visible = true;
13004
+ }
13005
+ const meshVisibilityBefore = /* @__PURE__ */ new Map();
13006
+ for (const meshes of this._ownMeshes.values()) {
13007
+ for (const mesh of meshes) {
13008
+ meshVisibilityBefore.set(mesh, mesh.visible);
13009
+ mesh.visible = false;
13010
+ }
12930
13011
  }
12931
13012
  for (const [byte, modelId] of byteToModel) {
12932
- const obj = objectsByModel.get(modelId);
12933
- if (!obj)
13013
+ if (!wasOnScreen.get(modelId))
12934
13014
  continue;
12935
- obj.visible = true;
13015
+ const meshes = this._ownMeshes.get(modelId);
13016
+ if (!meshes)
13017
+ continue;
13018
+ for (const mesh of meshes)
13019
+ mesh.visible = meshVisibilityBefore.get(mesh);
12936
13020
  material.uniforms.modelByte.value = byte;
12937
13021
  renderer.render(scene, camera);
12938
- obj.visible = false;
13022
+ for (const mesh of meshes)
13023
+ mesh.visible = false;
12939
13024
  }
12940
- for (const [obj, was] of bimVisibilityBefore)
13025
+ for (const [mesh, was] of meshVisibilityBefore)
13026
+ mesh.visible = was;
13027
+ for (const [obj, was] of rootVisibilityBefore)
12941
13028
  obj.visible = was;
12942
13029
  for (const [obj, was] of nonBimVisibility)
12943
13030
  obj.visible = was;
@@ -12978,8 +13065,9 @@ const _FastModelPicker = class _FastModelPicker {
12978
13065
  const prevClearAlpha = renderer.getClearAlpha();
12979
13066
  const matSwap = /* @__PURE__ */ new Map();
12980
13067
  const hiddenLines = [];
13068
+ const modelRoots = this.getModelRoots();
12981
13069
  for (const [, model] of fragments.list) {
12982
- model.object.traverse((child) => {
13070
+ this.traverseOwn(model.object, modelRoots, (child) => {
12983
13071
  if (!(child instanceof THREE__namespace.Mesh))
12984
13072
  return;
12985
13073
  const geom = child.geometry;
@@ -13049,6 +13137,7 @@ const _FastModelPicker = class _FastModelPicker {
13049
13137
  mesh.material = mat;
13050
13138
  }
13051
13139
  this._originalMaterials.clear();
13140
+ this._ownMeshes.clear();
13052
13141
  for (const obj of this._hiddenLods)
13053
13142
  obj.visible = true;
13054
13143
  this._hiddenLods.length = 0;
@@ -13425,12 +13514,32 @@ class SnapResolver {
13425
13514
  */
13426
13515
  __publicField(this, "maxCacheSize", 1e3);
13427
13516
  __publicField(this, "components");
13517
+ __publicField(this, "_precision", "half");
13428
13518
  // Map insertion order is recency order (LRU). On every read we
13429
13519
  // delete + re-set the entry to bump it to most-recent; on every
13430
13520
  // write we drop oldest entries until size <= maxCacheSize.
13431
13521
  __publicField(this, "cache", /* @__PURE__ */ new Map());
13432
13522
  this.components = components;
13433
13523
  }
13524
+ /**
13525
+ * Numeric precision for cached face/edge/vertex buffers.
13526
+ *
13527
+ * `"half"` uses Float32Array for lower memory footprint.
13528
+ * `"full"` uses Float64Array for higher numeric precision.
13529
+ * Use `"full"` for workflows that require higher geometric fidelity.
13530
+ *
13531
+ * Changing this value clears the snap cache so subsequent requests
13532
+ * are rebuilt with the new precision.
13533
+ */
13534
+ get precision() {
13535
+ return this._precision;
13536
+ }
13537
+ set precision(value) {
13538
+ if (value === this._precision)
13539
+ return;
13540
+ this._precision = value;
13541
+ this.clear();
13542
+ }
13434
13543
  /** {@link Disposable.dispose} */
13435
13544
  dispose() {
13436
13545
  this.cache.clear();
@@ -13570,8 +13679,8 @@ class SnapResolver {
13570
13679
  }
13571
13680
  if (polygons.length === 0)
13572
13681
  return null;
13573
- const edges = deriveUniqueEdges(polygons);
13574
- const vertices = deriveUniqueVertices(polygons);
13682
+ const edges = deriveUniqueEdges(polygons, this._precision);
13683
+ const vertices = deriveUniqueVertices(polygons, this._precision);
13575
13684
  return { faces: polygons, edges, vertices };
13576
13685
  }
13577
13686
  collectPolygonsFromShell(shell, worldMatrix, out) {
@@ -13582,7 +13691,8 @@ class SnapResolver {
13582
13691
  const collect = (indices) => {
13583
13692
  if (indices.length < 3)
13584
13693
  return;
13585
- const flat = new Float32Array(indices.length * 3);
13694
+ const Ctor = this._precision === "full" ? Float64Array : Float32Array;
13695
+ const flat = new Ctor(indices.length * 3);
13586
13696
  for (let i = 0; i < indices.length; i++) {
13587
13697
  const p = points[indices[i]];
13588
13698
  tmp.set(p[0], p[1], p[2]).applyMatrix4(worldMatrix);
@@ -13691,19 +13801,33 @@ class SnapResolver {
13691
13801
  return null;
13692
13802
  let best = null;
13693
13803
  let bestAbs = Infinity;
13804
+ let bestContains = false;
13694
13805
  for (const f of faces) {
13695
- const signed = f.normal.x * point.x + f.normal.y * point.y + f.normal.z * point.z - f.d;
13806
+ const signed = this.signedDistance(f, point);
13696
13807
  const abs = Math.abs(signed);
13697
- if (abs <= this.maxDistance && abs < bestAbs) {
13808
+ if (abs > this.maxDistance)
13809
+ continue;
13810
+ const contains = this.containsPoint(f, this.projectOntoPlane(point, f, signed));
13811
+ if (bestContains && !contains)
13812
+ continue;
13813
+ if (contains && !bestContains) {
13814
+ bestContains = true;
13815
+ bestAbs = abs;
13816
+ best = f;
13817
+ continue;
13818
+ }
13819
+ if (abs < bestAbs) {
13698
13820
  bestAbs = abs;
13699
13821
  best = f;
13700
13822
  }
13701
13823
  }
13702
13824
  if (!best)
13703
13825
  return null;
13704
- const projected = point.clone().sub(best.normal.clone().multiplyScalar(
13705
- best.normal.x * point.x + best.normal.y * point.y + best.normal.z * point.z - best.d
13706
- ));
13826
+ const projected = this.projectOntoPlane(
13827
+ point,
13828
+ best,
13829
+ this.signedDistance(best, point)
13830
+ );
13707
13831
  return {
13708
13832
  result: {
13709
13833
  point: projected,
@@ -13714,6 +13838,50 @@ class SnapResolver {
13714
13838
  }
13715
13839
  };
13716
13840
  }
13841
+ signedDistance(face, point) {
13842
+ return face.normal.x * point.x + face.normal.y * point.y + face.normal.z * point.z - face.d;
13843
+ }
13844
+ projectOntoPlane(point, face, signed) {
13845
+ return point.clone().sub(face.normal.clone().multiplyScalar(signed));
13846
+ }
13847
+ /**
13848
+ * Even-odd point-in-polygon test, run on the polygon projected onto its
13849
+ * dominant plane. `point` is assumed to already lie on the face's plane.
13850
+ */
13851
+ containsPoint(face, point) {
13852
+ const { points, normal } = face;
13853
+ const count = points.length / 3;
13854
+ if (count < 3)
13855
+ return false;
13856
+ const ax = Math.abs(normal.x);
13857
+ const ay = Math.abs(normal.y);
13858
+ const az = Math.abs(normal.z);
13859
+ let a = 0;
13860
+ let b = 1;
13861
+ if (ax >= ay && ax >= az) {
13862
+ a = 1;
13863
+ b = 2;
13864
+ } else if (ay >= az) {
13865
+ a = 0;
13866
+ b = 2;
13867
+ }
13868
+ const px = point.getComponent(a);
13869
+ const py = point.getComponent(b);
13870
+ let inside = false;
13871
+ for (let i = 0, j = count - 1; i < count; j = i, i += 1) {
13872
+ const xi = points[i * 3 + a];
13873
+ const yi = points[i * 3 + b];
13874
+ const xj = points[j * 3 + a];
13875
+ const yj = points[j * 3 + b];
13876
+ const straddles = yi > py !== yj > py;
13877
+ if (!straddles)
13878
+ continue;
13879
+ const x = (xj - xi) * (py - yi) / (yj - yi) + xi;
13880
+ if (px < x)
13881
+ inside = !inside;
13882
+ }
13883
+ return inside;
13884
+ }
13717
13885
  }
13718
13886
  const _tmpMatrix = new THREE__namespace.Matrix4();
13719
13887
  const _tmpX = new THREE__namespace.Vector3();
@@ -13781,7 +13949,7 @@ function fanTriangulation(vertexCount) {
13781
13949
  }
13782
13950
  return out;
13783
13951
  }
13784
- function deriveUniqueEdges(faces) {
13952
+ function deriveUniqueEdges(faces, precision) {
13785
13953
  const seen = /* @__PURE__ */ new Set();
13786
13954
  const collected = [];
13787
13955
  for (const f of faces) {
@@ -13798,9 +13966,10 @@ function deriveUniqueEdges(faces) {
13798
13966
  collected.push(ax, ay, az, bx, by, bz);
13799
13967
  }
13800
13968
  }
13801
- return new Float32Array(collected);
13969
+ const Ctor = precision === "full" ? Float64Array : Float32Array;
13970
+ return new Ctor(collected);
13802
13971
  }
13803
- function deriveUniqueVertices(faces) {
13972
+ function deriveUniqueVertices(faces, precision) {
13804
13973
  const seen = /* @__PURE__ */ new Set();
13805
13974
  const collected = [];
13806
13975
  for (const f of faces) {
@@ -13815,7 +13984,8 @@ function deriveUniqueVertices(faces) {
13815
13984
  collected.push(x, y, z);
13816
13985
  }
13817
13986
  }
13818
- return new Float32Array(collected);
13987
+ const Ctor = precision === "full" ? Float64Array : Float32Array;
13988
+ return new Ctor(collected);
13819
13989
  }
13820
13990
  function quantKey(x, y, z) {
13821
13991
  return `${Math.round(x * VERTEX_QUANT)},${Math.round(y * VERTEX_QUANT)},${Math.round(z * VERTEX_QUANT)}`;
@@ -14111,6 +14281,9 @@ class SimpleWorld extends Base {
14111
14281
  */
14112
14282
  __publicField(this, "enabled", true);
14113
14283
  __publicField(this, "_dynamicAnchor", false);
14284
+ // Bumped on every anchor press so a slow geometry pick that resolves after a
14285
+ // newer press is ignored (see onPointerDown).
14286
+ __publicField(this, "_anchorRequestId", 0);
14114
14287
  /**
14115
14288
  * A unique identifier for the world. Is not meant to be changed at any moment.
14116
14289
  */
@@ -14122,21 +14295,36 @@ class SimpleWorld extends Base {
14122
14295
  __publicField(this, "_scene");
14123
14296
  __publicField(this, "_camera");
14124
14297
  __publicField(this, "_renderer", null);
14125
- __publicField(this, "onPointerDown", async (event) => {
14298
+ __publicField(this, "onPointerDown", (event) => {
14126
14299
  if (!this.camera.hasCameraControls()) {
14127
14300
  throw new Error(
14128
14301
  "World: can't set dynamic anchor if the camera doesn't have controls."
14129
14302
  );
14130
14303
  }
14304
+ if (event.button !== 0)
14305
+ return;
14306
+ const position = this.getPointerPosition(event);
14307
+ if (!position)
14308
+ return;
14131
14309
  const caster = this.components.get(Raycasters).get(this);
14132
- const result = await caster.castRay();
14133
- if (result && result.point && event.button === 0) {
14310
+ const planePoint = this.getPlaneAnchor(caster, position);
14311
+ if (planePoint) {
14134
14312
  this.camera.controls.setOrbitPoint(
14135
- result.point.x,
14136
- result.point.y,
14137
- result.point.z
14313
+ planePoint.x,
14314
+ planePoint.y,
14315
+ planePoint.z
14138
14316
  );
14139
14317
  }
14318
+ const requestId = ++this._anchorRequestId;
14319
+ caster.castRay({ position }).then((result) => {
14320
+ const isStale = requestId !== this._anchorRequestId;
14321
+ if (isStale || this.isDisposing || !this._dynamicAnchor)
14322
+ return;
14323
+ if (!(result == null ? void 0 : result.point) || !this.camera.hasCameraControls())
14324
+ return;
14325
+ const { x, y, z } = result.point;
14326
+ this.camera.controls.setOrbitPoint(x, y, z);
14327
+ });
14140
14328
  });
14141
14329
  __publicField(this, "_defaultCamera");
14142
14330
  }
@@ -14155,10 +14343,46 @@ class SimpleWorld extends Base {
14155
14343
  } else {
14156
14344
  container.removeEventListener("pointerdown", this.onPointerDown);
14157
14345
  }
14346
+ this._dynamicAnchor = value;
14158
14347
  }
14159
14348
  get dynamicAnchor() {
14160
14349
  return this._dynamicAnchor;
14161
14350
  }
14351
+ /**
14352
+ * The pointer position in normalized device coordinates, derived from the
14353
+ * event rather than the raycaster's mouse: that one only tracks pointermove
14354
+ * and touchstart, so on a bare pointerdown it would still report the center of
14355
+ * the viewport.
14356
+ */
14357
+ getPointerPosition(event) {
14358
+ var _a;
14359
+ const dom = (_a = this.renderer) == null ? void 0 : _a.three.domElement;
14360
+ if (!dom)
14361
+ return null;
14362
+ const bounds = dom.getBoundingClientRect();
14363
+ return new THREE__namespace.Vector2(
14364
+ (event.clientX - bounds.left) / (bounds.right - bounds.left) * 2 - 1,
14365
+ -((event.clientY - bounds.top) / (bounds.bottom - bounds.top)) * 2 + 1
14366
+ );
14367
+ }
14368
+ /**
14369
+ * The point where the pointer ray crosses the plane that contains the current
14370
+ * orbit target and faces the camera. Used as the anchor when the pointer
14371
+ * doesn't land on any geometry.
14372
+ */
14373
+ getPlaneAnchor(caster, position) {
14374
+ if (!this.camera.hasCameraControls())
14375
+ return null;
14376
+ const camera = this.camera.three;
14377
+ caster.three.setFromCamera(position, camera);
14378
+ const target = this.camera.controls.getTarget(new THREE__namespace.Vector3());
14379
+ const normal = camera.getWorldDirection(new THREE__namespace.Vector3());
14380
+ const plane = new THREE__namespace.Plane().setFromNormalAndCoplanarPoint(
14381
+ normal,
14382
+ target
14383
+ );
14384
+ return caster.three.ray.intersectPlane(plane, new THREE__namespace.Vector3());
14385
+ }
14162
14386
  get defaultCamera() {
14163
14387
  if (!this._defaultCamera) {
14164
14388
  throw new Error("World: there is no default camera defined.");
@@ -14261,8 +14485,16 @@ class SimpleWorld extends Base {
14261
14485
  }
14262
14486
  /** {@link Disposable.dispose} */
14263
14487
  dispose(disposeResources = true) {
14488
+ var _a;
14489
+ if (this.isDisposing)
14490
+ return;
14264
14491
  this.enabled = false;
14265
14492
  this.isDisposing = true;
14493
+ const container = (_a = this.renderer) == null ? void 0 : _a.three.domElement.parentElement;
14494
+ if (container) {
14495
+ container.removeEventListener("pointerdown", this.onPointerDown);
14496
+ }
14497
+ this._dynamicAnchor = false;
14266
14498
  this.scene.onWorldChanged.trigger({ world: this, action: "removed" });
14267
14499
  this.camera.onWorldChanged.trigger({ world: this, action: "removed" });
14268
14500
  if (this.renderer) {
@@ -15509,16 +15741,23 @@ const _Components = class _Components {
15509
15741
  dispose() {
15510
15742
  this.enabled = false;
15511
15743
  let fragments;
15744
+ let worlds;
15512
15745
  for (const [_id, component] of this.list) {
15513
15746
  component.enabled = false;
15514
15747
  if (_id === FragmentsManager.uuid) {
15515
15748
  fragments = component;
15516
15749
  continue;
15517
15750
  }
15751
+ if (_id === Worlds.uuid) {
15752
+ worlds = component;
15753
+ continue;
15754
+ }
15518
15755
  if (component.isDisposeable()) {
15519
15756
  component.dispose();
15520
15757
  }
15521
15758
  }
15759
+ if (worlds == null ? void 0 : worlds.isDisposeable())
15760
+ worlds.dispose();
15522
15761
  fragments == null ? void 0 : fragments.dispose();
15523
15762
  this._timer.dispose();
15524
15763
  this.onDisposed.trigger();
@@ -15995,7 +16234,7 @@ Dual licenced under the MIT license or GPLv3. See https://raw.github.com/Stuk/js
15995
16234
  JSZip uses the library pako released under the MIT license :
15996
16235
  https://github.com/nodeca/pako/blob/main/LICENSE
15997
16236
  */
15998
- (function(module2, exports2) {
16237
+ (function(module2, exports$1) {
15999
16238
  !function(e) {
16000
16239
  module2.exports = e();
16001
16240
  }(function() {
@@ -19053,33 +19292,31 @@ const _Topic = class _Topic {
19053
19292
  serialize() {
19054
19293
  var _a, _b;
19055
19294
  const version = this._managerVersion;
19295
+ const isV3 = version === "3";
19056
19296
  const topic = {
19057
19297
  $Guid: this.guid,
19058
19298
  $TopicType: this.type,
19059
19299
  $TopicStatus: this.status,
19060
19300
  $ServerAssignedId: this.serverAssignedId,
19061
19301
  Title: this.title,
19062
- CreationAuthor: this.creationAuthor,
19063
- CreationDate: this.creationDate.toISOString(),
19064
19302
  Priority: this.priority,
19065
19303
  Index: version === "2.1" ? this.index : void 0,
19304
+ Labels: isV3 ? { Label: this.createLabelTags() } : this.createLabelTags(),
19305
+ CreationDate: this.creationDate.toISOString(),
19306
+ CreationAuthor: this.creationAuthor,
19066
19307
  ModifiedDate: (_a = this.modifiedDate) == null ? void 0 : _a.toISOString(),
19067
19308
  ModifiedAuthor: this.modifiedAuthor,
19068
19309
  DueDate: (_b = this.dueDate) == null ? void 0 : _b.toISOString(),
19069
19310
  AssignedTo: this.assignedTo,
19070
- Description: this.description,
19071
19311
  Stage: this.stage,
19072
- DocumentReferences: version === "3" ? { DocumentReference: this.createDocumentReferencesTag(version) } : void 0,
19073
- RelatedTopics: version === "3" ? { RelatedTopic: this.createRelatedTopicTags() } : void 0,
19074
- RelatedTopic: version === "2.1" ? this.createRelatedTopicTags() : void 0,
19075
- Labels: version === "3" ? { Label: this.createLabelTags() } : void 0,
19076
- Viewpoints: version === "3" ? { ViewPoint: this.createViewpointTags() } : void 0,
19077
- Comments: version === "3" ? { Comment: this.createCommentTags() } : void 0
19312
+ Description: this.description,
19313
+ DocumentReferences: isV3 ? { DocumentReference: this.createDocumentReferencesTag(version) } : void 0,
19314
+ DocumentReference: isV3 ? void 0 : this.createDocumentReferencesTag(version),
19315
+ RelatedTopics: isV3 ? { RelatedTopic: this.createRelatedTopicTags() } : void 0,
19316
+ RelatedTopic: isV3 ? void 0 : this.createRelatedTopicTags(),
19317
+ Comments: isV3 ? { Comment: this.createCommentTags() } : void 0,
19318
+ Viewpoints: isV3 ? { ViewPoint: this.createViewpointTags() } : void 0
19078
19319
  };
19079
- if (version === "2.1") {
19080
- topic.Labels = this.createLabelTags();
19081
- topic.DocumentReference = this.createDocumentReferencesTag(version);
19082
- }
19083
19320
  const markup = {
19084
19321
  Markup: { Topic: topic }
19085
19322
  };
@@ -19506,10 +19743,13 @@ const _BCFTopics = class _BCFTopics extends Component {
19506
19743
  */
19507
19744
  async export(topics = this.list.values()) {
19508
19745
  const zip = new JSZip();
19746
+ const isV3 = this.config.version === "3";
19747
+ const versionId = isV3 ? "3.0" : this.config.version;
19748
+ const schemaRelease = isV3 ? "release_3_0" : "release_2_1";
19509
19749
  zip.file(
19510
19750
  "bcf.version",
19511
19751
  `<?xml version="1.0" encoding="UTF-8"?>
19512
- <Version VersionId="${this.config.version}" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/buildingSMART/BCF-XML/release_3_0/Schemas/version.xsd"
19752
+ <Version VersionId="${versionId}" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/buildingSMART/BCF-XML/${schemaRelease}/Schemas/version.xsd"
19513
19753
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
19514
19754
  </Version>`
19515
19755
  );
@@ -19544,7 +19784,7 @@ const _BCFTopics = class _BCFTopics extends Component {
19544
19784
  );
19545
19785
  }
19546
19786
  }
19547
- zip.file("bcf.extensions", this.serializeExtensions());
19787
+ zip.file("extensions.xml", this.serializeExtensions());
19548
19788
  const viewpoints = this.components.get(Viewpoints);
19549
19789
  for (const topic of topics) {
19550
19790
  const topicFolder = zip.folder(topic.guid);
@@ -19616,7 +19856,7 @@ ${users}
19616
19856
  Comment: CommentText,
19617
19857
  Viewpoint: Viewpoint2
19618
19858
  } = markupComment;
19619
- if (!(Guid && CommentDate && Author && Comment))
19859
+ if (!(Guid && CommentDate && Author && (Comment || Viewpoint2)))
19620
19860
  return null;
19621
19861
  const comment = new Comment(this.components, CommentText ?? "");
19622
19862
  comment.guid = Guid;
@@ -19713,14 +19953,16 @@ ${users}
19713
19953
  const versionFile = files.find((file) => file.name.endsWith(".version"));
19714
19954
  if (versionFile) {
19715
19955
  const versionXML = await versionFile.async("string");
19716
- const bcfVersion = _BCFTopics.xmlParser.parse(versionXML).Version.VersionId;
19717
- version = String(bcfVersion);
19956
+ const bcfVersion = String(
19957
+ _BCFTopics.xmlParser.parse(versionXML).Version.VersionId
19958
+ );
19959
+ version = bcfVersion === "3.0" ? "3" : bcfVersion;
19718
19960
  }
19719
19961
  if (!(version && (version === "2.1" || version === "3"))) {
19720
19962
  throw new Error(`BCFTopics: ${version} is not supported.`);
19721
19963
  }
19722
19964
  const extensionsFile = files.find(
19723
- (file) => file.name.endsWith(".extensions")
19965
+ (file) => file.name.endsWith("extensions.xml") || file.name.endsWith(".extensions")
19724
19966
  );
19725
19967
  if (updateExtensionsOnImport && extensionsFile) {
19726
19968
  const extensionsXML = await extensionsFile.async("string");
@@ -20263,7 +20505,7 @@ class SimplePlane {
20263
20505
  dispose() {
20264
20506
  var _a;
20265
20507
  this._enabled = false;
20266
- if ((_a = this.world.camera) == null ? void 0 : _a.controls) {
20508
+ if (!this.world.isDisposing && ((_a = this.world.camera) == null ? void 0 : _a.controls)) {
20267
20509
  this.world.camera.controls.removeEventListener(
20268
20510
  "update",
20269
20511
  this.updateScale
@@ -20504,8 +20746,13 @@ const _Clipper = class _Clipper extends Component {
20504
20746
  * Default `false` to preserve existing behavior. Set this once
20505
20747
  * before creating planes; toggling at runtime won't reclassify
20506
20748
  * planes already on the renderer.
20749
+ *
20750
+ * Setting this also mirrors the value onto every world's
20751
+ * `renderer.localClippingPlanes`, so planes added by other
20752
+ * components (e.g. the {@link Views} cut planes) default to the
20753
+ * same mode without each call site having to pass `isLocal`.
20507
20754
  */
20508
- __publicField(this, "localClippingPlanes", false);
20755
+ __publicField(this, "_localClippingPlanes", false);
20509
20756
  /**
20510
20757
  * The type of clipping plane to be created.
20511
20758
  * Default is {@link SimplePlane}.
@@ -20541,6 +20788,17 @@ const _Clipper = class _Clipper extends Component {
20541
20788
  this.components.add(_Clipper.uuid, this);
20542
20789
  this.setEvents();
20543
20790
  }
20791
+ get localClippingPlanes() {
20792
+ return this._localClippingPlanes;
20793
+ }
20794
+ set localClippingPlanes(value) {
20795
+ this._localClippingPlanes = value;
20796
+ const worlds = this.components.get(Worlds);
20797
+ for (const [, world] of worlds.list) {
20798
+ if (world.renderer)
20799
+ world.renderer.localClippingPlanes = value;
20800
+ }
20801
+ }
20544
20802
  /** {@link Component.enabled} */
20545
20803
  get enabled() {
20546
20804
  return this._enabled;
@@ -23468,19 +23726,24 @@ class IDSProperty extends IDSFacet {
23468
23726
  );
23469
23727
  }
23470
23728
  getTypePsets(item) {
23471
- if (!Array.isArray(item.IsTypedBy))
23472
- return [];
23473
- const [type] = item.IsTypedBy;
23474
- if (!(type && Array.isArray(type.HasPropertySets))) {
23475
- return [];
23729
+ const definitions = [
23730
+ ...Array.isArray(item.IsTypedBy) ? item.IsTypedBy : [],
23731
+ ...Array.isArray(item.IsDefinedBy) ? item.IsDefinedBy : []
23732
+ ];
23733
+ const psets = [];
23734
+ for (const definition of definitions) {
23735
+ if (Array.isArray(definition == null ? void 0 : definition.HasPropertySets)) {
23736
+ psets.push(...definition.HasPropertySets);
23737
+ }
23476
23738
  }
23477
- return type.HasPropertySets;
23739
+ return psets;
23478
23740
  }
23479
23741
  async getPsets(item) {
23480
23742
  const typeSets = this.getTypePsets(item);
23481
23743
  if (!Array.isArray(item.IsDefinedBy))
23482
23744
  return typeSets;
23483
23745
  const sets = [];
23746
+ const mergedTypeSets = /* @__PURE__ */ new Set();
23484
23747
  for (const definition of item.IsDefinedBy) {
23485
23748
  if (!("value" in definition.Name))
23486
23749
  continue;
@@ -23493,22 +23756,29 @@ class IDSProperty extends IDSFacet {
23493
23756
  return false;
23494
23757
  return set.Name.value === definitionName;
23495
23758
  });
23496
- if (typeSet && Array.isArray(typeSet.HasProperties) && Array.isArray(definition.HasProperties)) {
23497
- for (const prop of typeSet.HasProperties) {
23498
- if (!("value" in prop.Name))
23499
- continue;
23500
- const name = prop.Name.value;
23501
- const existingProp = definition.HasProperties.find((p) => {
23502
- if (!("value" in p.Name))
23503
- return false;
23504
- return p.Name.value === name;
23505
- });
23506
- if (!existingProp)
23507
- definition.HasProperties.push(prop);
23759
+ if (typeSet) {
23760
+ mergedTypeSets.add(typeSet);
23761
+ if (Array.isArray(typeSet.HasProperties) && Array.isArray(definition.HasProperties)) {
23762
+ for (const prop of typeSet.HasProperties) {
23763
+ if (!("value" in prop.Name))
23764
+ continue;
23765
+ const name = prop.Name.value;
23766
+ const existingProp = definition.HasProperties.find((p) => {
23767
+ if (!("value" in p.Name))
23768
+ return false;
23769
+ return p.Name.value === name;
23770
+ });
23771
+ if (!existingProp)
23772
+ definition.HasProperties.push(prop);
23773
+ }
23508
23774
  }
23509
23775
  }
23510
23776
  sets.push(definition);
23511
23777
  }
23778
+ for (const typeSet of typeSets) {
23779
+ if (!mergedTypeSets.has(typeSet))
23780
+ sets.push(typeSet);
23781
+ }
23512
23782
  return sets;
23513
23783
  }
23514
23784
  // IFCPROPERTYBOUNDEDVALUE are not supported yet
@@ -23530,7 +23800,7 @@ class IDSProperty extends IDSFacet {
23530
23800
  return false;
23531
23801
  }
23532
23802
  const facetValue = structuredClone(this.value);
23533
- if (valueAttr.type === "IFCLABEL" && facetValue.type === "simple") {
23803
+ if (typeof valueAttr.value === "string" && facetValue.type === "simple") {
23534
23804
  facetValue.parameter = String(facetValue.parameter);
23535
23805
  }
23536
23806
  const result = this.evalRequirement(
@@ -23586,12 +23856,38 @@ class IDSProperty extends IDSFacet {
23586
23856
  class IDSMaterial extends IDSFacet {
23587
23857
  constructor() {
23588
23858
  super(...arguments);
23859
+ // Every type IfcRelAssociatesMaterial.RelatingMaterial (IfcMaterialSelect)
23860
+ // can point at. Missing any of them silently drops every element that
23861
+ // associates its material through it: an element can reference a layer set
23862
+ // directly instead of through a layer set usage, and both spellings are
23863
+ // valid IFC.
23589
23864
  __publicField(this, "_ifcMaterialEntities", [
23590
23865
  /^IFCMATERIALLAYERSETUSAGE$/,
23866
+ /^IFCMATERIALLAYERSET$/,
23867
+ /^IFCMATERIALPROFILESETUSAGE$/,
23868
+ /^IFCMATERIALPROFILESET$/,
23591
23869
  /^IFCMATERIALCONSTITUENTSET$/,
23592
23870
  /^IFCMATERIAL$/,
23593
23871
  /^IFCMATERIALLIST$/
23594
23872
  ]);
23873
+ // Attributes that lead from a material set / usage down to its IfcMaterial.
23874
+ __publicField(this, "_materialParentAttributes", [
23875
+ "ForLayerSet",
23876
+ "MaterialLayers",
23877
+ "ForProfileSet",
23878
+ "MaterialProfiles",
23879
+ "Material",
23880
+ "MaterialConstituents",
23881
+ "Materials"
23882
+ ]);
23883
+ __publicField(this, "_materialRelations", {
23884
+ MaterialConstituents: { attributes: true, relations: true },
23885
+ ForLayerSet: { attributes: true, relations: true },
23886
+ MaterialLayers: { attributes: true, relations: true },
23887
+ ForProfileSet: { attributes: true, relations: true },
23888
+ MaterialProfiles: { attributes: true, relations: true },
23889
+ Materials: { attributes: true, relations: false }
23890
+ });
23595
23891
  __publicField(this, "facetType", "Material");
23596
23892
  __publicField(this, "value");
23597
23893
  __publicField(this, "uri");
@@ -23623,10 +23919,7 @@ class IDSMaterial extends IDSFacet {
23623
23919
  const data = await model.getItemsData(localIds, {
23624
23920
  relations: {
23625
23921
  AssociatedTo: { attributes: true, relations: false },
23626
- MaterialConstituents: { attributes: true, relations: true },
23627
- ForLayerSet: { attributes: true, relations: true },
23628
- MaterialLayers: { attributes: true, relations: true },
23629
- Materials: { attributes: true, relations: false }
23922
+ ...this._materialRelations
23630
23923
  }
23631
23924
  });
23632
23925
  for (const item of data) {
@@ -23655,10 +23948,7 @@ class IDSMaterial extends IDSFacet {
23655
23948
  relations: {
23656
23949
  AssociatedTo: { attributes: false, relations: false },
23657
23950
  HasAssociations: { attributes: true, relations: true },
23658
- MaterialConstituents: { attributes: true, relations: true },
23659
- ForLayerSet: { attributes: true, relations: true },
23660
- MaterialLayers: { attributes: true, relations: true },
23661
- Materials: { attributes: true, relations: false }
23951
+ ...this._materialRelations
23662
23952
  }
23663
23953
  });
23664
23954
  for (const item of data) {
@@ -23702,13 +23992,7 @@ class IDSMaterial extends IDSFacet {
23702
23992
  isValid = true;
23703
23993
  } else {
23704
23994
  for (const [name, attribute] of Object.entries(item)) {
23705
- if (![
23706
- "ForLayerSet",
23707
- "MaterialLayers",
23708
- "Material",
23709
- "MaterialConstituents",
23710
- "Materials"
23711
- ].includes(name))
23995
+ if (!this._materialParentAttributes.includes(name))
23712
23996
  continue;
23713
23997
  if (!Array.isArray(attribute))
23714
23998
  continue;
@@ -26715,7 +26999,7 @@ function buildPreviewPositions(kind, points, cursor, style) {
26715
26999
  for (let i = 0; i < points.length - 1; i++) {
26716
27000
  positions.push(
26717
27001
  ...buildDimensionPositions(
26718
- { uuid: "", pointA: points[i], pointB: points[i + 1], offset, style: "" },
27002
+ { pointA: points[i], pointB: points[i + 1], offset },
26719
27003
  style
26720
27004
  )
26721
27005
  );