@sequent-org/ifc-viewer 1.2.4-ci.55.0 → 1.2.4-ci.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sequent-org/ifc-viewer",
3
3
  "private": false,
4
- "version": "1.2.4-ci.55.0",
4
+ "version": "1.2.4-ci.57.0",
5
5
  "type": "module",
6
6
  "description": "IFC 3D model viewer component for web applications - fully self-contained with local IFCLoader",
7
7
  "main": "src/index.js",
@@ -50,6 +50,21 @@ export class LabelPlacementController {
50
50
  this._ghostPos = { x: 0, y: 0 };
51
51
  this._raf = 0;
52
52
 
53
+ this._labelDrag = {
54
+ active: false,
55
+ moved: false,
56
+ pointerId: null,
57
+ id: null,
58
+ start: { x: 0, y: 0 },
59
+ last: { x: 0, y: 0 },
60
+ ghostPos: { x: 0, y: 0 },
61
+ clickMarker: null,
62
+ prevControlsEnabled: null,
63
+ threshold: 4,
64
+ };
65
+
66
+ this._labelDragDropSelector = deps?.labelDragDropSelector || null;
67
+
53
68
  this._controlsWasEnabled = null;
54
69
 
55
70
  this._containerOffset = { left: 0, top: 0 };
@@ -96,10 +111,23 @@ export class LabelPlacementController {
96
111
  try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture, { capture: true }); } catch (_) {
97
112
  try { dom?.removeEventListener("pointerdown", this._onPointerDownCapture); } catch (_) {}
98
113
  }
114
+ try { dom?.removeEventListener("pointerdown", this._onDbgDomPointerDown); } catch (_) {}
115
+ try { dom?.removeEventListener("pointermove", this._onDbgDomPointerMove); } catch (_) {}
116
+ try { dom?.removeEventListener("pointerup", this._onDbgDomPointerUp); } catch (_) {}
99
117
  try { window.removeEventListener("keydown", this._onKeyDown); } catch (_) {}
100
118
  try { window.removeEventListener("resize", this._onWindowResize); } catch (_) {}
101
119
  try { window.removeEventListener("scroll", this._onWindowScroll, true); } catch (_) {}
102
120
  try { window.removeEventListener("pointerdown", this._onWindowPointerDown, true); } catch (_) {}
121
+ try { document.removeEventListener("pointerdown", this._onDbgDocPointerDown); } catch (_) {}
122
+ try { document.removeEventListener("pointermove", this._onDbgDocPointerMove); } catch (_) {}
123
+ try { document.removeEventListener("pointerup", this._onDbgDocPointerUp); } catch (_) {}
124
+ try { document.removeEventListener("dragstart", this._onDbgDocDragStart); } catch (_) {}
125
+ try { document.removeEventListener("dragend", this._onDbgDocDragEnd); } catch (_) {}
126
+ try { document.removeEventListener("dragover", this._onDbgDocDragOver); } catch (_) {}
127
+ try { document.removeEventListener("drop", this._onDbgDocDrop); } catch (_) {}
128
+ try { document.removeEventListener("pointermove", this._onLabelDragPointerMove); } catch (_) {}
129
+ try { document.removeEventListener("pointerup", this._onLabelDragPointerUp); } catch (_) {}
130
+ try { document.removeEventListener("pointercancel", this._onLabelDragPointerCancel); } catch (_) {}
103
131
  try { this._ui?.btn?.removeEventListener("click", this._onBtnClick); } catch (_) {}
104
132
  try { this._ui?.menu?.removeEventListener("pointerdown", this._onMenuPointerDown); } catch (_) {}
105
133
  try { this._ui?.menu?.removeEventListener("click", this._onMenuClick); } catch (_) {}
@@ -116,6 +144,7 @@ export class LabelPlacementController {
116
144
  this._markers.length = 0;
117
145
 
118
146
  try { this._ui?.ghost?.remove?.(); } catch (_) {}
147
+ try { this._ui?.dragGhost?.remove?.(); } catch (_) {}
119
148
  try { this._ui?.btn?.remove?.(); } catch (_) {}
120
149
  try { this._ui?.menu?.remove?.(); } catch (_) {}
121
150
  try { this._ui?.canvasMenu?.remove?.(); } catch (_) {}
@@ -253,11 +282,16 @@ export class LabelPlacementController {
253
282
  planes?.[1]?.constant,
254
283
  planes?.[2]?.constant,
255
284
  ];
285
+ const clipPlanes = {
286
+ x: this.#getAxisPlaneState('x', planes?.[0]),
287
+ y: this.#getAxisPlaneState('y', planes?.[1]),
288
+ z: this.#getAxisPlaneState('z', planes?.[2]),
289
+ };
256
290
 
257
291
  return {
258
292
  camera: cam,
259
293
  modelTransform,
260
- clipping: { constants: clipConstants },
294
+ clipping: { constants: clipConstants, planes: clipPlanes },
261
295
  };
262
296
  }
263
297
 
@@ -332,17 +366,7 @@ export class LabelPlacementController {
332
366
  } catch (_) {}
333
367
 
334
368
  // 5) Клиппинг (как Home: сначала восстановили камеру+модель, затем planes)
335
- try {
336
- const constants = sceneState?.clipping?.constants || [];
337
- if (typeof viewer.setSection === "function") {
338
- ["x", "y", "z"].forEach((axis, i) => {
339
- const c = constants[i];
340
- const enabled = Number.isFinite(c);
341
- const dist = -Number(c);
342
- viewer.setSection(axis, enabled, enabled ? dist : 0);
343
- });
344
- }
345
- } catch (_) {}
369
+ this.#applyClippingFromState(sceneState);
346
370
 
347
371
  try { camera?.updateProjectionMatrix?.(); } catch (_) {}
348
372
  try { controls?.update?.(); } catch (_) {}
@@ -372,18 +396,65 @@ export class LabelPlacementController {
372
396
  const viewer = this.viewer;
373
397
  if (!viewer || !sceneState) return;
374
398
  try {
399
+ const byAxis = sceneState?.clipping?.planes || null;
400
+ if (byAxis && typeof viewer.setSection === "function") {
401
+ ["x", "y", "z"].forEach((axis) => {
402
+ const s = byAxis?.[axis] || null;
403
+ const enabled = !!s?.enabled;
404
+ const dist = Number(s?.distance);
405
+ viewer.setSection(axis, enabled, (enabled && Number.isFinite(dist)) ? dist : 0);
406
+ });
407
+ return;
408
+ }
375
409
  const constants = sceneState?.clipping?.constants || [];
376
410
  if (typeof viewer.setSection === "function") {
377
411
  ["x", "y", "z"].forEach((axis, i) => {
378
412
  const c = constants[i];
379
413
  const enabled = Number.isFinite(c);
380
- const dist = -Number(c);
381
- viewer.setSection(axis, enabled, enabled ? dist : 0);
414
+ const dist = this.#getAxisDistanceFromConstant(axis, c);
415
+ viewer.setSection(axis, enabled, (enabled && Number.isFinite(dist)) ? dist : 0);
382
416
  });
383
417
  }
384
418
  } catch (_) {}
385
419
  }
386
420
 
421
+ #getAxisPlaneState(axis, plane) {
422
+ if (!plane) return { enabled: false, distance: null };
423
+ const constant = plane.constant;
424
+ const enabled = Number.isFinite(constant);
425
+ if (!enabled) return { enabled: false, distance: null };
426
+ const distance = this.#getAxisDistanceFromPlane(axis, plane);
427
+ return { enabled: true, distance };
428
+ }
429
+
430
+ #getAxisDistanceFromPlane(axis, plane) {
431
+ if (!plane || !Number.isFinite(plane.constant)) return null;
432
+ const n = plane.normal || null;
433
+ const nx = Number(n?.x ?? 1);
434
+ const ny = Number(n?.y ?? 1);
435
+ const nz = Number(n?.z ?? 1);
436
+ const sign = (axis === 'x') ? (nx >= 0 ? 1 : -1)
437
+ : (axis === 'y') ? (ny >= 0 ? 1 : -1)
438
+ : (nz >= 0 ? 1 : -1);
439
+ return (sign > 0) ? -Number(plane.constant) : Number(plane.constant);
440
+ }
441
+
442
+ #getAxisDistanceFromConstant(axis, constant) {
443
+ if (!Number.isFinite(constant)) return null;
444
+ const viewer = this.viewer;
445
+ const planes = viewer?.clipping?.planes || [];
446
+ const idx = axis === 'x' ? 0 : axis === 'y' ? 1 : 2;
447
+ const plane = planes[idx] || null;
448
+ const n = plane?.normal || null;
449
+ const nx = Number(n?.x ?? 1);
450
+ const ny = Number(n?.y ?? 1);
451
+ const nz = Number(n?.z ?? 1);
452
+ const sign = (axis === 'x') ? (nx >= 0 ? 1 : -1)
453
+ : (axis === 'y') ? (ny >= 0 ? 1 : -1)
454
+ : (nz >= 0 ? 1 : -1);
455
+ return (sign > 0) ? -Number(constant) : Number(constant);
456
+ }
457
+
387
458
  #applyViewOffset(camera, viewOffset) {
388
459
  if (!camera || !viewOffset) return;
389
460
  try {
@@ -543,6 +614,20 @@ export class LabelPlacementController {
543
614
  ghost.appendChild(dot);
544
615
  ghost.appendChild(num);
545
616
 
617
+ const dragGhost = document.createElement("div");
618
+ dragGhost.className = "ifc-label-ghost ifc-label-ghost--drag";
619
+ dragGhost.setAttribute("aria-hidden", "true");
620
+ dragGhost.style.display = "none";
621
+ dragGhost.style.left = "0px";
622
+ dragGhost.style.top = "0px";
623
+
624
+ const dragDot = document.createElement("div");
625
+ dragDot.className = "ifc-label-dot";
626
+ const dragNum = document.createElement("div");
627
+ dragNum.className = "ifc-label-num";
628
+ dragGhost.appendChild(dragDot);
629
+ dragGhost.appendChild(dragNum);
630
+
546
631
  const menu = document.createElement("div");
547
632
  menu.className = "ifc-label-menu";
548
633
  menu.style.display = "none";
@@ -583,13 +668,14 @@ export class LabelPlacementController {
583
668
 
584
669
  canvasMenu.appendChild(menuAdd);
585
670
 
586
- return { btn, ghost, dot, num, menu, canvasMenu };
671
+ return { btn, ghost, dot, num, dragGhost, dragNum, menu, canvasMenu };
587
672
  }
588
673
 
589
674
  #attachUi() {
590
675
  // Важно: container должен быть position:relative (в index.html уже так).
591
676
  this.container.appendChild(this._ui.btn);
592
677
  this.container.appendChild(this._ui.ghost);
678
+ this.container.appendChild(this._ui.dragGhost);
593
679
  this.container.appendChild(this._ui.menu);
594
680
  this.container.appendChild(this._ui.canvasMenu);
595
681
  }
@@ -705,9 +791,64 @@ export class LabelPlacementController {
705
791
  // scroll слушаем в capture, чтобы ловить скролл вложенных контейнеров
706
792
  window.addEventListener("scroll", this._onWindowScroll, true);
707
793
 
794
+ const logDnDEvent = (scope, e) => {
795
+ const target = e?.target || null;
796
+ const closestMarker = (target && typeof target.closest === "function")
797
+ ? target.closest(".ifc-label-marker")
798
+ : null;
799
+ if (!closestMarker) return;
800
+ const payload = {
801
+ type: e?.type,
802
+ target,
803
+ closestMarker,
804
+ button: e?.button,
805
+ buttons: e?.buttons,
806
+ clientX: e?.clientX,
807
+ clientY: e?.clientY,
808
+ defaultPrevented: !!e?.defaultPrevented,
809
+ };
810
+ try {
811
+ this.logger?.log?.("[LabelDnD]", scope, payload);
812
+ } catch (_) {
813
+ try { console.log("[LabelDnD]", scope, payload); } catch (_) {}
814
+ }
815
+ };
816
+
817
+ this._onDbgDocPointerDown = (e) => logDnDEvent("document", e);
818
+ this._onDbgDocPointerMove = (e) => logDnDEvent("document", e);
819
+ this._onDbgDocPointerUp = (e) => logDnDEvent("document", e);
820
+ this._onDbgDocDragStart = (e) => logDnDEvent("document", e);
821
+ this._onDbgDocDragEnd = (e) => logDnDEvent("document", e);
822
+ this._onDbgDocDragOver = (e) => logDnDEvent("document", e);
823
+ this._onDbgDocDrop = (e) => logDnDEvent("document", e);
824
+
825
+ document.addEventListener("pointerdown", this._onDbgDocPointerDown);
826
+ document.addEventListener("pointermove", this._onDbgDocPointerMove);
827
+ document.addEventListener("pointerup", this._onDbgDocPointerUp);
828
+ document.addEventListener("dragstart", this._onDbgDocDragStart);
829
+ document.addEventListener("dragend", this._onDbgDocDragEnd);
830
+ document.addEventListener("dragover", this._onDbgDocDragOver);
831
+ document.addEventListener("drop", this._onDbgDocDrop);
832
+
833
+ this._onLabelDragPointerMove = (e) => this.#updateLabelDrag(e);
834
+ this._onLabelDragPointerUp = (e) => this.#finishLabelDrag(e, "pointerup");
835
+ this._onLabelDragPointerCancel = (e) => this.#finishLabelDrag(e, "pointercancel");
836
+
837
+ document.addEventListener("pointermove", this._onLabelDragPointerMove, { passive: true });
838
+ document.addEventListener("pointerup", this._onLabelDragPointerUp, { passive: true });
839
+ document.addEventListener("pointercancel", this._onLabelDragPointerCancel, { passive: true });
840
+
708
841
  const dom = this.viewer?.renderer?.domElement;
709
842
  if (!dom) return;
710
843
 
844
+ this._onDbgDomPointerDown = (e) => logDnDEvent("ifc3dHost", e);
845
+ this._onDbgDomPointerMove = (e) => logDnDEvent("ifc3dHost", e);
846
+ this._onDbgDomPointerUp = (e) => logDnDEvent("ifc3dHost", e);
847
+
848
+ dom.addEventListener("pointerdown", this._onDbgDomPointerDown, { passive: true });
849
+ dom.addEventListener("pointermove", this._onDbgDomPointerMove, { passive: true });
850
+ dom.addEventListener("pointerup", this._onDbgDomPointerUp, { passive: true });
851
+
711
852
  this._onPointerMove = (e) => {
712
853
  if (!this._placing) return;
713
854
  this.#updateGhostFromClient(e.clientX, e.clientY);
@@ -804,6 +945,117 @@ export class LabelPlacementController {
804
945
  this.#updateGhostFromClient(this._lastPointer.x, this._lastPointer.y);
805
946
  }
806
947
 
948
+ #setDragGhostVisible(visible) {
949
+ if (!this._ui?.dragGhost) return;
950
+ this._ui.dragGhost.style.display = visible ? "block" : "none";
951
+ }
952
+
953
+ #applyDragGhostTransform() {
954
+ const g = this._ui?.dragGhost;
955
+ if (!g) return;
956
+ g.style.transform = `translate3d(${this._labelDrag.ghostPos.x}px, ${this._labelDrag.ghostPos.y}px, 0) translate(-50%, -50%)`;
957
+ }
958
+
959
+ #updateDragGhostFromClient(clientX, clientY) {
960
+ this._labelDrag.last = { x: clientX, y: clientY };
961
+ if (!this._containerOffsetValid) this.#refreshContainerOffset();
962
+ const x = (clientX - this._containerOffset.left);
963
+ const y = (clientY - this._containerOffset.top);
964
+ this._labelDrag.ghostPos.x = x;
965
+ this._labelDrag.ghostPos.y = y;
966
+ this.#applyDragGhostTransform();
967
+ }
968
+
969
+ #beginLabelDrag(marker, e) {
970
+ if (!marker || !e || e.button !== 0) return;
971
+ this._labelDrag.active = true;
972
+ this._labelDrag.moved = false;
973
+ this._labelDrag.pointerId = e.pointerId ?? null;
974
+ this._labelDrag.id = marker.id;
975
+ this._labelDrag.start = { x: e.clientX, y: e.clientY };
976
+ this._labelDrag.last = { x: e.clientX, y: e.clientY };
977
+ this._labelDrag.clickMarker = marker;
978
+
979
+ try {
980
+ this._labelDrag.prevControlsEnabled = this.viewer?.controls?.enabled ?? null;
981
+ if (this.viewer?.controls) this.viewer.controls.enabled = false;
982
+ } catch (_) {}
983
+
984
+ if (this._ui?.dragNum) this._ui.dragNum.textContent = String(marker.id);
985
+ this.#setDragGhostVisible(false);
986
+
987
+ try { marker.el?.setPointerCapture?.(e.pointerId); } catch (_) {}
988
+
989
+ this.#dispatchLabelEvent("ifcviewer:label-drag-start", {
990
+ id: marker.id,
991
+ clientX: e.clientX,
992
+ clientY: e.clientY,
993
+ }, null);
994
+ }
995
+
996
+ #updateLabelDrag(e) {
997
+ if (!this._labelDrag.active || !e) return;
998
+ if (this._labelDrag.pointerId != null && e.pointerId != null && e.pointerId !== this._labelDrag.pointerId) return;
999
+
1000
+ const dx = (e.clientX - this._labelDrag.start.x);
1001
+ const dy = (e.clientY - this._labelDrag.start.y);
1002
+ const dist = Math.hypot(dx, dy);
1003
+ if (!this._labelDrag.moved && dist >= this._labelDrag.threshold) {
1004
+ this._labelDrag.moved = true;
1005
+ this.#setDragGhostVisible(true);
1006
+ }
1007
+
1008
+ if (this._labelDrag.moved) {
1009
+ this.#updateDragGhostFromClient(e.clientX, e.clientY);
1010
+ }
1011
+ }
1012
+
1013
+ #resolveDropTarget(clientX, clientY) {
1014
+ const el = document.elementFromPoint?.(clientX, clientY) || null;
1015
+ if (!el) return { element: null, card: null };
1016
+ if (!this._labelDragDropSelector) return { element: el, card: null };
1017
+ const card = el.closest?.(this._labelDragDropSelector) || null;
1018
+ return { element: el, card };
1019
+ }
1020
+
1021
+ #finishLabelDrag(e, reason = "pointerup") {
1022
+ if (!this._labelDrag.active) return;
1023
+ const marker = this._labelDrag.clickMarker;
1024
+ const moved = !!this._labelDrag.moved;
1025
+ const clientX = e?.clientX ?? this._labelDrag.last.x;
1026
+ const clientY = e?.clientY ?? this._labelDrag.last.y;
1027
+
1028
+ this._labelDrag.active = false;
1029
+ this._labelDrag.moved = false;
1030
+ this._labelDrag.pointerId = null;
1031
+ this._labelDrag.id = null;
1032
+ this._labelDrag.clickMarker = null;
1033
+
1034
+ this.#setDragGhostVisible(false);
1035
+
1036
+ try {
1037
+ if (this.viewer?.controls && this._labelDrag.prevControlsEnabled != null) {
1038
+ this.viewer.controls.enabled = !!this._labelDrag.prevControlsEnabled;
1039
+ }
1040
+ } catch (_) {}
1041
+ this._labelDrag.prevControlsEnabled = null;
1042
+
1043
+ if (!moved) {
1044
+ if (marker) this.#handleMarkerClick(marker);
1045
+ return;
1046
+ }
1047
+
1048
+ const drop = this.#resolveDropTarget(clientX, clientY);
1049
+ this.#dispatchLabelEvent("ifcviewer:label-drop", {
1050
+ id: marker?.id ?? this._labelDrag.id,
1051
+ clientX,
1052
+ clientY,
1053
+ dropTarget: drop.card || null,
1054
+ elementFromPoint: drop.element || null,
1055
+ reason,
1056
+ }, null);
1057
+ }
1058
+
807
1059
  #pickModelPoint(clientX, clientY) {
808
1060
  const model = this.viewer?.activeModel;
809
1061
  const camera = this.viewer?.camera;
@@ -900,6 +1152,18 @@ export class LabelPlacementController {
900
1152
  return this._markers.find((m) => String(m?.id) === String(this._selectedId)) || null;
901
1153
  }
902
1154
 
1155
+ #handleMarkerClick(marker) {
1156
+ if (!marker) return;
1157
+ this.#closeContextMenu();
1158
+ this.#setSelectedMarker(marker);
1159
+ this.#dispatchLabelEvent("ifcviewer:label-click", {
1160
+ id: marker.id,
1161
+ sceneState: marker.sceneState || null,
1162
+ }, "ifcviewer:card-click");
1163
+ // "Долеталка" камеры: быстрый старт + мягкий конец
1164
+ this.#animateToSceneState(marker.sceneState, 550);
1165
+ }
1166
+
903
1167
  #buildActionPayload(marker) {
904
1168
  if (!marker) return null;
905
1169
  return {
@@ -1019,24 +1283,78 @@ export class LabelPlacementController {
1019
1283
  try { e.preventDefault(); } catch (_) {}
1020
1284
  try { e.stopPropagation(); } catch (_) {}
1021
1285
  try { e.stopImmediatePropagation?.(); } catch (_) {}
1286
+ try {
1287
+ this.logger?.log?.("[LabelDnD]", "marker", {
1288
+ type: e?.type,
1289
+ target: e?.target || null,
1290
+ closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
1291
+ button: e?.button,
1292
+ buttons: e?.buttons,
1293
+ clientX: e?.clientX,
1294
+ clientY: e?.clientY,
1295
+ defaultPrevented: !!e?.defaultPrevented,
1296
+ });
1297
+ } catch (_) {}
1022
1298
  // если были в режиме постановки — выходим
1023
1299
  try { this.cancelPlacement(); } catch (_) {}
1024
1300
 
1025
1301
  if (e.button === 0) {
1026
1302
  this.#closeContextMenu();
1027
1303
  this.#setSelectedMarker(marker);
1028
- this.#dispatchLabelEvent("ifcviewer:label-click", {
1029
- id: marker.id,
1030
- sceneState: marker.sceneState || null,
1031
- }, "ifcviewer:card-click");
1032
- // "Долеталка" камеры: быстрый старт + мягкий конец
1033
- this.#animateToSceneState(marker.sceneState, 550);
1304
+ this.#beginLabelDrag(marker, e);
1034
1305
  }
1035
1306
  };
1036
1307
  // capture-phase, чтобы обогнать любые handlers на canvas
1037
1308
  try { marker.el.addEventListener("pointerdown", onMarkerPointerDown, { capture: true, passive: false }); } catch (_) {
1038
1309
  try { marker.el.addEventListener("pointerdown", onMarkerPointerDown); } catch (_) {}
1039
1310
  }
1311
+ const onMarkerPointerUp = (e) => {
1312
+ try {
1313
+ this.logger?.log?.("[LabelDnD]", "marker", {
1314
+ type: e?.type,
1315
+ target: e?.target || null,
1316
+ closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
1317
+ button: e?.button,
1318
+ buttons: e?.buttons,
1319
+ clientX: e?.clientX,
1320
+ clientY: e?.clientY,
1321
+ defaultPrevented: !!e?.defaultPrevented,
1322
+ });
1323
+ } catch (_) {}
1324
+ };
1325
+ try { marker.el.addEventListener("pointerup", onMarkerPointerUp, { capture: true, passive: true }); } catch (_) {
1326
+ try { marker.el.addEventListener("pointerup", onMarkerPointerUp); } catch (_) {}
1327
+ }
1328
+ const onMarkerDragStart = (e) => {
1329
+ try {
1330
+ this.logger?.log?.("[LabelDnD]", "marker", {
1331
+ type: e?.type,
1332
+ target: e?.target || null,
1333
+ closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
1334
+ button: e?.button,
1335
+ buttons: e?.buttons,
1336
+ clientX: e?.clientX,
1337
+ clientY: e?.clientY,
1338
+ defaultPrevented: !!e?.defaultPrevented,
1339
+ });
1340
+ } catch (_) {}
1341
+ };
1342
+ const onMarkerDragEnd = (e) => {
1343
+ try {
1344
+ this.logger?.log?.("[LabelDnD]", "marker", {
1345
+ type: e?.type,
1346
+ target: e?.target || null,
1347
+ closestMarker: e?.target?.closest?.(".ifc-label-marker") || null,
1348
+ button: e?.button,
1349
+ buttons: e?.buttons,
1350
+ clientX: e?.clientX,
1351
+ clientY: e?.clientY,
1352
+ defaultPrevented: !!e?.defaultPrevented,
1353
+ });
1354
+ } catch (_) {}
1355
+ };
1356
+ try { marker.el.addEventListener("dragstart", onMarkerDragStart); } catch (_) {}
1357
+ try { marker.el.addEventListener("dragend", onMarkerDragEnd); } catch (_) {}
1040
1358
 
1041
1359
  const onMarkerContextMenu = (e) => {
1042
1360
  try { e.preventDefault(); } catch (_) {}
@@ -3464,7 +3464,7 @@ export class Viewer {
3464
3464
  const s = plane.normal.dot(c) + plane.constant;
3465
3465
  if (s < minSigned) minSigned = s;
3466
3466
  }
3467
- if (minSigned < 0) {
3467
+ if (minSigned < 0 && minSigned >= -1e-3) {
3468
3468
  plane.constant -= (minSigned - (-1e-4)); // сдвинем чуть так, чтобы все вершины имели s >= -1e-4
3469
3469
  }
3470
3470
  }