copper3d 3.6.6 → 3.6.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.
@@ -61165,6 +61165,15 @@ class MeshGraph {
61165
61165
  const nm = new Matrix3().getNormalMatrix(mesh.matrixWorld);
61166
61166
  return n.applyMatrix3(nm).normalize();
61167
61167
  }
61168
+ /** Vertex local normal at index i (reads from the welded graphGeo normal attribute). */
61169
+ vertexNormalLocal(i) {
61170
+ return new Vector3(this.normals ? this.normals[i * 3] : 0, this.normals ? this.normals[i * 3 + 1] : 0, this.normals ? this.normals[i * 3 + 2] : 1);
61171
+ }
61172
+ /** Normal at the vertex nearest to localPoint (for recovering normals from imported point coordinates). */
61173
+ nearestNormalLocal(p) {
61174
+ const i = this.nearestVertex(p);
61175
+ return this.vertexNormalLocal(i);
61176
+ }
61168
61177
  /** Vertex local coordinates + local normal (the graph geometry is already local space). */
61169
61178
  vertexLocal(i) {
61170
61179
  return {
@@ -61390,6 +61399,13 @@ class AnnotationStore {
61390
61399
  this.notify();
61391
61400
  }
61392
61401
  }
61402
+ setVisible(id, visible) {
61403
+ const a = this.get(id);
61404
+ if (a) {
61405
+ a.visible = visible;
61406
+ this.notify();
61407
+ }
61408
+ }
61393
61409
  toJSON(modelName, mesh, opts = {}) {
61394
61410
  var _a;
61395
61411
  const space = (_a = opts.space) !== null && _a !== void 0 ? _a : "local";
@@ -61414,6 +61430,7 @@ class AnnotationStore {
61414
61430
  label: a.label,
61415
61431
  color: a.color,
61416
61432
  closed: a.closed,
61433
+ visible: a.visible,
61417
61434
  points: a.vertices.map(toPt),
61418
61435
  })),
61419
61436
  };
@@ -61433,6 +61450,10 @@ class SurfaceAnnotator {
61433
61450
  var _a, _b, _c;
61434
61451
  this.mode = "navigate";
61435
61452
  this.spaceHeld = false;
61453
+ this.drawLock = false;
61454
+ this.armed = "freehand";
61455
+ this.spaceDownAt = 0;
61456
+ this.spaceDragged = false;
61436
61457
  this.pointerDown = false;
61437
61458
  this.store = new AnnotationStore();
61438
61459
  this.managed = new Set();
@@ -61459,14 +61480,18 @@ class SurfaceAnnotator {
61459
61480
  (this.activeGeoLine.material.resolution.set(w, h));
61460
61481
  };
61461
61482
  this.onPointerDown = (e) => {
61462
- if (this.spaceHeld)
61463
- return;
61464
61483
  if (e.button !== 0)
61465
61484
  return; // left button only
61466
61485
  if (!this.insideContainer(e))
61467
61486
  return;
61468
61487
  this.pointerDown = true;
61469
- if (this.mode === "point") {
61488
+ // Only act with the armed tool when in drawing mode (drawLock or spaceHeld).
61489
+ // When not drawing, navigation is the default — pointer events go to camera controls.
61490
+ if (!this.drawing)
61491
+ return;
61492
+ // Use armed tool (not this.mode which may be "navigate")
61493
+ const activeTool = this.armed;
61494
+ if (activeTool === "point") {
61470
61495
  const h = this.hit(e);
61471
61496
  if (!h)
61472
61497
  return;
@@ -61480,13 +61505,14 @@ class SurfaceAnnotator {
61480
61505
  label: `Point ${this.seq}`,
61481
61506
  color: this.pointColorVal,
61482
61507
  closed: false,
61508
+ visible: true,
61483
61509
  vertices: [v],
61484
61510
  object3D: marker,
61485
61511
  };
61486
61512
  this.store.add(ann);
61487
61513
  return;
61488
61514
  }
61489
- if (this.mode === "freehand") {
61515
+ if (activeTool === "freehand") {
61490
61516
  this.activeStroke = new StrokeContour(this.minGap, this.maxJump, this.o.mesh);
61491
61517
  this.activeStroke.begin();
61492
61518
  const h = this.hit(e);
@@ -61496,7 +61522,7 @@ class SurfaceAnnotator {
61496
61522
  this.o.scene.add(this.activeLine);
61497
61523
  return;
61498
61524
  }
61499
- if (this.mode === "geodesic") {
61525
+ if (activeTool === "geodesic") {
61500
61526
  // First check whether an existing anchor was clicked → cancel that point (any point can be canceled).
61501
61527
  const pick = this.pickGeoMarker(e);
61502
61528
  if (pick >= 0 && this.activeGeo) {
@@ -61523,15 +61549,26 @@ class SurfaceAnnotator {
61523
61549
  }
61524
61550
  };
61525
61551
  this.onPointerMove = (e) => {
61526
- if (this.spaceHeld) {
61527
- this.setGeoHover(-1);
61552
+ // Track if user dragged while space was held (so a hold-drag is not treated as a tap).
61553
+ if (this.spaceHeld && this.pointerDown) {
61554
+ this.spaceDragged = true;
61555
+ }
61556
+ // When not drawing, navigation is default — don't interfere with camera controls.
61557
+ if (!this.drawing) {
61558
+ // In geodesic mode we can still show the hover hint when not drawing.
61559
+ if (this.armed === "geodesic" && !this.pointerDown) {
61560
+ this.setGeoHover(this.insideContainer(e) ? this.pickGeoMarker(e) : -1);
61561
+ }
61562
+ else {
61563
+ this.setGeoHover(-1);
61564
+ }
61528
61565
  return;
61529
61566
  }
61530
- // In geodesic mode, show the "✕" hint when hovering an anchor sphere (that point can be clicked to cancel).
61531
- if (this.mode === "geodesic" && !this.pointerDown) {
61567
+ // Drawing mode: geodesic hover hint
61568
+ if (this.armed === "geodesic" && !this.pointerDown) {
61532
61569
  this.setGeoHover(this.insideContainer(e) ? this.pickGeoMarker(e) : -1);
61533
61570
  }
61534
- if (this.mode === "freehand" &&
61571
+ if (this.armed === "freehand" &&
61535
61572
  this.pointerDown &&
61536
61573
  this.activeStroke &&
61537
61574
  this.activeLine) {
@@ -61556,6 +61593,7 @@ class SurfaceAnnotator {
61556
61593
  label: `Contour ${this.seq}`,
61557
61594
  color: this.freehandColor,
61558
61595
  closed: false,
61596
+ visible: true,
61559
61597
  vertices: verts,
61560
61598
  object3D: this.activeLine,
61561
61599
  };
@@ -61573,11 +61611,17 @@ class SurfaceAnnotator {
61573
61611
  if (this.isTypingTarget(e))
61574
61612
  return;
61575
61613
  if (e.code === "Space") {
61614
+ if (!this.spaceHeld) {
61615
+ this.spaceDownAt = performance.now();
61616
+ this.spaceDragged = false;
61617
+ }
61576
61618
  this.spaceHeld = true;
61577
61619
  this.applyCameraGating();
61620
+ e.preventDefault();
61578
61621
  return;
61579
61622
  }
61580
61623
  if (e.key === "Escape") {
61624
+ this.drawLock = false;
61581
61625
  this.setMode("navigate");
61582
61626
  return;
61583
61627
  }
@@ -61610,7 +61654,11 @@ class SurfaceAnnotator {
61610
61654
  };
61611
61655
  this.onKeyUp = (e) => {
61612
61656
  if (e.code === "Space") {
61657
+ const held = performance.now() - this.spaceDownAt;
61613
61658
  this.spaceHeld = false;
61659
+ if (held <= SurfaceAnnotator.TAP_MS && !this.spaceDragged) {
61660
+ this.drawLock = !this.drawLock; // tap toggles lock
61661
+ }
61614
61662
  this.applyCameraGating();
61615
61663
  }
61616
61664
  };
@@ -61666,6 +61714,10 @@ class SurfaceAnnotator {
61666
61714
  if (m !== "geodesic" && this.activeGeo)
61667
61715
  this.clearActiveGeo();
61668
61716
  this.mode = m;
61717
+ // Record armed tool when choosing a drawing mode (not navigate).
61718
+ // Camera gating is NOT changed here — the user must use Space to enter drawing.
61719
+ if (m !== "navigate")
61720
+ this.armed = m;
61669
61721
  this.applyCameraGating();
61670
61722
  (_b = (_a = this.o).onModeChange) === null || _b === void 0 ? void 0 : _b.call(_a, m);
61671
61723
  }
@@ -61728,10 +61780,92 @@ class SurfaceAnnotator {
61728
61780
  exportJSON(modelName, opts) {
61729
61781
  return this.store.toJSON(modelName, this.o.mesh, opts);
61730
61782
  }
61783
+ setVisible(id, visible) {
61784
+ this.store.setVisible(id, visible);
61785
+ }
61786
+ /**
61787
+ * Rebuild annotations from an exported payload (local-space points). Normals are taken from the
61788
+ * point when present ([x,y,z,nx,ny,nz]); otherwise recovered from the welded graph's nearest
61789
+ * vertex. Each imported item becomes first-class (select/recolor/hide/delete/export).
61790
+ */
61791
+ importAnnotations(payload) {
61792
+ var _a, _b, _c, _d;
61793
+ let count = 0;
61794
+ let maxImported = this.seq;
61795
+ for (const a of (_a = payload.annotations) !== null && _a !== void 0 ? _a : []) {
61796
+ const verts = a.points.map((p) => {
61797
+ const [x, y, z] = p;
61798
+ let nx = p[3], ny = p[4], nz = p[5];
61799
+ if (nx === undefined || ny === undefined || nz === undefined) {
61800
+ const nrm = this.graph.nearestNormalLocal(new Vector3(x, y, z));
61801
+ nx = nrm.x;
61802
+ ny = nrm.y;
61803
+ nz = nrm.z;
61804
+ }
61805
+ return { x, y, z, nx, ny, nz, faceIndex: 0 };
61806
+ });
61807
+ if (a.type === "points") {
61808
+ for (const v of verts) {
61809
+ // Only reuse the provided id for a single-point entry (the round-trip case);
61810
+ // a multi-point points entry must get a fresh id per marker to avoid duplicate keys.
61811
+ const id = a.id && verts.length === 1 ? a.id : this.nextId();
61812
+ const marker = makePointMarker(v, this.o.mesh, a.color, this.markerRadius);
61813
+ this.o.scene.add(marker);
61814
+ this.store.add({
61815
+ id,
61816
+ type: "points",
61817
+ mode: null,
61818
+ label: a.label,
61819
+ color: a.color,
61820
+ closed: false,
61821
+ visible: (_b = a.visible) !== null && _b !== void 0 ? _b : true,
61822
+ vertices: [v],
61823
+ object3D: marker,
61824
+ });
61825
+ // track max numeric id
61826
+ const m = id.match(/^a(\d+)$/);
61827
+ if (m)
61828
+ maxImported = Math.max(maxImported, parseInt(m[1], 10));
61829
+ count++;
61830
+ }
61831
+ }
61832
+ else {
61833
+ if (verts.length < 2)
61834
+ continue;
61835
+ const id = (_c = a.id) !== null && _c !== void 0 ? _c : this.nextId();
61836
+ const line = makeContourLine(verts, a.color, a.closed, this.o.container, this.epsilon, this.o.mesh);
61837
+ this.o.scene.add(line);
61838
+ this.store.add({
61839
+ id,
61840
+ type: "contour",
61841
+ mode: a.mode,
61842
+ label: a.label,
61843
+ color: a.color,
61844
+ closed: a.closed,
61845
+ visible: (_d = a.visible) !== null && _d !== void 0 ? _d : true,
61846
+ vertices: verts,
61847
+ object3D: line,
61848
+ });
61849
+ const m = id.match(/^a(\d+)$/);
61850
+ if (m)
61851
+ maxImported = Math.max(maxImported, parseInt(m[1], 10));
61852
+ count++;
61853
+ }
61854
+ }
61855
+ // keep seq ahead of any imported numeric ids to avoid collisions
61856
+ if (maxImported > this.seq)
61857
+ this.seq = maxImported;
61858
+ return count;
61859
+ }
61731
61860
  // ---- Internal ----
61861
+ get drawing() {
61862
+ return this.drawLock || this.spaceHeld;
61863
+ }
61732
61864
  applyCameraGating() {
61733
- // Holding Space for temporary rotation takes priority; otherwise enable the camera only in navigate mode.
61734
- this.o.controls.enabled = this.spaceHeld || this.mode === "navigate";
61865
+ var _a, _b;
61866
+ // Default is navigate (camera enabled). Drawing only when drawLock or spaceHeld.
61867
+ this.o.controls.enabled = !this.drawing;
61868
+ (_b = (_a = this.o).onInteractionChange) === null || _b === void 0 ? void 0 : _b.call(_a, { drawing: this.drawing, armed: this.armed, locked: this.drawLock });
61735
61869
  }
61736
61870
  /** Reconcile the scene against store.list(): add missing objects, remove deleted ones (no dispose, kept for undo restore). */
61737
61871
  reconcile() {
@@ -61749,6 +61883,11 @@ class SurfaceAnnotator {
61749
61883
  this.o.scene.add(o);
61750
61884
  }
61751
61885
  this.managed = present;
61886
+ // Apply per-annotation visibility
61887
+ for (const a of this.store.list()) {
61888
+ if (a.object3D)
61889
+ a.object3D.visible = a.visible;
61890
+ }
61752
61891
  this.applySelection();
61753
61892
  (_b = (_a = this.o).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, this.store.list());
61754
61893
  }
@@ -61756,6 +61895,8 @@ class SurfaceAnnotator {
61756
61895
  for (const a of this.store.list()) {
61757
61896
  if (!a.object3D)
61758
61897
  continue;
61898
+ if (!a.visible)
61899
+ continue;
61759
61900
  const sel = a.id === this.selectedId;
61760
61901
  if (a.type === "contour") {
61761
61902
  const mat = a.object3D.material;
@@ -61988,6 +62129,7 @@ class SurfaceAnnotator {
61988
62129
  label: `Contour ${this.seq}`,
61989
62130
  color: this.geodesicColor,
61990
62131
  closed,
62132
+ visible: true,
61991
62133
  vertices: verts,
61992
62134
  object3D: this.activeGeoLine,
61993
62135
  };
@@ -62021,7 +62163,8 @@ class SurfaceAnnotator {
62021
62163
  window.removeEventListener("keyup", this.onKeyUp);
62022
62164
  window.removeEventListener("resize", this.onResize);
62023
62165
  }
62024
- }
62166
+ }
62167
+ SurfaceAnnotator.TAP_MS = 250;
62025
62168
 
62026
62169
  class copperScene extends baseScene {
62027
62170
  // rayster pick
@@ -83444,15 +83587,15 @@ function evaluateElement(element, weights) {
83444
83587
  }
83445
83588
 
83446
83589
  // import * as kiwrious from "copper3d_plugin_heart_k";
83447
- // "v3.6.6" is injected at build time by rollup @rollup/plugin-replace, sourced from package.json version.
83590
+ // "v3.6.7" is injected at build time by rollup @rollup/plugin-replace, sourced from package.json version.
83448
83591
  // When copper3d is consumed from local source (no rollup replace step), the identifier is undefined and
83449
83592
  // referencing it throws a ReferenceError — guard it so local loading still works.
83450
83593
  let _revision = "unknown";
83451
83594
  try {
83452
- _revision = "v3.6.6";
83595
+ _revision = "v3.6.7";
83453
83596
  }
83454
83597
  catch (_a) {
83455
- /* "v3.6.6" not injected (local source build) */
83598
+ /* "v3.6.7" not injected (local source build) */
83456
83599
  }
83457
83600
  const REVISION = _revision;
83458
83601
  // Expose on global so the version can be read in a production browser console via window.__COPPER3D_VERSION__
@@ -61173,6 +61173,15 @@ void main() {
61173
61173
  const nm = new Matrix3().getNormalMatrix(mesh.matrixWorld);
61174
61174
  return n.applyMatrix3(nm).normalize();
61175
61175
  }
61176
+ /** Vertex local normal at index i (reads from the welded graphGeo normal attribute). */
61177
+ vertexNormalLocal(i) {
61178
+ return new Vector3(this.normals ? this.normals[i * 3] : 0, this.normals ? this.normals[i * 3 + 1] : 0, this.normals ? this.normals[i * 3 + 2] : 1);
61179
+ }
61180
+ /** Normal at the vertex nearest to localPoint (for recovering normals from imported point coordinates). */
61181
+ nearestNormalLocal(p) {
61182
+ const i = this.nearestVertex(p);
61183
+ return this.vertexNormalLocal(i);
61184
+ }
61176
61185
  /** Vertex local coordinates + local normal (the graph geometry is already local space). */
61177
61186
  vertexLocal(i) {
61178
61187
  return {
@@ -61398,6 +61407,13 @@ void main() {
61398
61407
  this.notify();
61399
61408
  }
61400
61409
  }
61410
+ setVisible(id, visible) {
61411
+ const a = this.get(id);
61412
+ if (a) {
61413
+ a.visible = visible;
61414
+ this.notify();
61415
+ }
61416
+ }
61401
61417
  toJSON(modelName, mesh, opts = {}) {
61402
61418
  var _a;
61403
61419
  const space = (_a = opts.space) !== null && _a !== void 0 ? _a : "local";
@@ -61422,6 +61438,7 @@ void main() {
61422
61438
  label: a.label,
61423
61439
  color: a.color,
61424
61440
  closed: a.closed,
61441
+ visible: a.visible,
61425
61442
  points: a.vertices.map(toPt),
61426
61443
  })),
61427
61444
  };
@@ -61441,6 +61458,10 @@ void main() {
61441
61458
  var _a, _b, _c;
61442
61459
  this.mode = "navigate";
61443
61460
  this.spaceHeld = false;
61461
+ this.drawLock = false;
61462
+ this.armed = "freehand";
61463
+ this.spaceDownAt = 0;
61464
+ this.spaceDragged = false;
61444
61465
  this.pointerDown = false;
61445
61466
  this.store = new AnnotationStore();
61446
61467
  this.managed = new Set();
@@ -61467,14 +61488,18 @@ void main() {
61467
61488
  (this.activeGeoLine.material.resolution.set(w, h));
61468
61489
  };
61469
61490
  this.onPointerDown = (e) => {
61470
- if (this.spaceHeld)
61471
- return;
61472
61491
  if (e.button !== 0)
61473
61492
  return; // left button only
61474
61493
  if (!this.insideContainer(e))
61475
61494
  return;
61476
61495
  this.pointerDown = true;
61477
- if (this.mode === "point") {
61496
+ // Only act with the armed tool when in drawing mode (drawLock or spaceHeld).
61497
+ // When not drawing, navigation is the default — pointer events go to camera controls.
61498
+ if (!this.drawing)
61499
+ return;
61500
+ // Use armed tool (not this.mode which may be "navigate")
61501
+ const activeTool = this.armed;
61502
+ if (activeTool === "point") {
61478
61503
  const h = this.hit(e);
61479
61504
  if (!h)
61480
61505
  return;
@@ -61488,13 +61513,14 @@ void main() {
61488
61513
  label: `Point ${this.seq}`,
61489
61514
  color: this.pointColorVal,
61490
61515
  closed: false,
61516
+ visible: true,
61491
61517
  vertices: [v],
61492
61518
  object3D: marker,
61493
61519
  };
61494
61520
  this.store.add(ann);
61495
61521
  return;
61496
61522
  }
61497
- if (this.mode === "freehand") {
61523
+ if (activeTool === "freehand") {
61498
61524
  this.activeStroke = new StrokeContour(this.minGap, this.maxJump, this.o.mesh);
61499
61525
  this.activeStroke.begin();
61500
61526
  const h = this.hit(e);
@@ -61504,7 +61530,7 @@ void main() {
61504
61530
  this.o.scene.add(this.activeLine);
61505
61531
  return;
61506
61532
  }
61507
- if (this.mode === "geodesic") {
61533
+ if (activeTool === "geodesic") {
61508
61534
  // First check whether an existing anchor was clicked → cancel that point (any point can be canceled).
61509
61535
  const pick = this.pickGeoMarker(e);
61510
61536
  if (pick >= 0 && this.activeGeo) {
@@ -61531,15 +61557,26 @@ void main() {
61531
61557
  }
61532
61558
  };
61533
61559
  this.onPointerMove = (e) => {
61534
- if (this.spaceHeld) {
61535
- this.setGeoHover(-1);
61560
+ // Track if user dragged while space was held (so a hold-drag is not treated as a tap).
61561
+ if (this.spaceHeld && this.pointerDown) {
61562
+ this.spaceDragged = true;
61563
+ }
61564
+ // When not drawing, navigation is default — don't interfere with camera controls.
61565
+ if (!this.drawing) {
61566
+ // In geodesic mode we can still show the hover hint when not drawing.
61567
+ if (this.armed === "geodesic" && !this.pointerDown) {
61568
+ this.setGeoHover(this.insideContainer(e) ? this.pickGeoMarker(e) : -1);
61569
+ }
61570
+ else {
61571
+ this.setGeoHover(-1);
61572
+ }
61536
61573
  return;
61537
61574
  }
61538
- // In geodesic mode, show the "✕" hint when hovering an anchor sphere (that point can be clicked to cancel).
61539
- if (this.mode === "geodesic" && !this.pointerDown) {
61575
+ // Drawing mode: geodesic hover hint
61576
+ if (this.armed === "geodesic" && !this.pointerDown) {
61540
61577
  this.setGeoHover(this.insideContainer(e) ? this.pickGeoMarker(e) : -1);
61541
61578
  }
61542
- if (this.mode === "freehand" &&
61579
+ if (this.armed === "freehand" &&
61543
61580
  this.pointerDown &&
61544
61581
  this.activeStroke &&
61545
61582
  this.activeLine) {
@@ -61564,6 +61601,7 @@ void main() {
61564
61601
  label: `Contour ${this.seq}`,
61565
61602
  color: this.freehandColor,
61566
61603
  closed: false,
61604
+ visible: true,
61567
61605
  vertices: verts,
61568
61606
  object3D: this.activeLine,
61569
61607
  };
@@ -61581,11 +61619,17 @@ void main() {
61581
61619
  if (this.isTypingTarget(e))
61582
61620
  return;
61583
61621
  if (e.code === "Space") {
61622
+ if (!this.spaceHeld) {
61623
+ this.spaceDownAt = performance.now();
61624
+ this.spaceDragged = false;
61625
+ }
61584
61626
  this.spaceHeld = true;
61585
61627
  this.applyCameraGating();
61628
+ e.preventDefault();
61586
61629
  return;
61587
61630
  }
61588
61631
  if (e.key === "Escape") {
61632
+ this.drawLock = false;
61589
61633
  this.setMode("navigate");
61590
61634
  return;
61591
61635
  }
@@ -61618,7 +61662,11 @@ void main() {
61618
61662
  };
61619
61663
  this.onKeyUp = (e) => {
61620
61664
  if (e.code === "Space") {
61665
+ const held = performance.now() - this.spaceDownAt;
61621
61666
  this.spaceHeld = false;
61667
+ if (held <= SurfaceAnnotator.TAP_MS && !this.spaceDragged) {
61668
+ this.drawLock = !this.drawLock; // tap toggles lock
61669
+ }
61622
61670
  this.applyCameraGating();
61623
61671
  }
61624
61672
  };
@@ -61674,6 +61722,10 @@ void main() {
61674
61722
  if (m !== "geodesic" && this.activeGeo)
61675
61723
  this.clearActiveGeo();
61676
61724
  this.mode = m;
61725
+ // Record armed tool when choosing a drawing mode (not navigate).
61726
+ // Camera gating is NOT changed here — the user must use Space to enter drawing.
61727
+ if (m !== "navigate")
61728
+ this.armed = m;
61677
61729
  this.applyCameraGating();
61678
61730
  (_b = (_a = this.o).onModeChange) === null || _b === void 0 ? void 0 : _b.call(_a, m);
61679
61731
  }
@@ -61736,10 +61788,92 @@ void main() {
61736
61788
  exportJSON(modelName, opts) {
61737
61789
  return this.store.toJSON(modelName, this.o.mesh, opts);
61738
61790
  }
61791
+ setVisible(id, visible) {
61792
+ this.store.setVisible(id, visible);
61793
+ }
61794
+ /**
61795
+ * Rebuild annotations from an exported payload (local-space points). Normals are taken from the
61796
+ * point when present ([x,y,z,nx,ny,nz]); otherwise recovered from the welded graph's nearest
61797
+ * vertex. Each imported item becomes first-class (select/recolor/hide/delete/export).
61798
+ */
61799
+ importAnnotations(payload) {
61800
+ var _a, _b, _c, _d;
61801
+ let count = 0;
61802
+ let maxImported = this.seq;
61803
+ for (const a of (_a = payload.annotations) !== null && _a !== void 0 ? _a : []) {
61804
+ const verts = a.points.map((p) => {
61805
+ const [x, y, z] = p;
61806
+ let nx = p[3], ny = p[4], nz = p[5];
61807
+ if (nx === undefined || ny === undefined || nz === undefined) {
61808
+ const nrm = this.graph.nearestNormalLocal(new Vector3(x, y, z));
61809
+ nx = nrm.x;
61810
+ ny = nrm.y;
61811
+ nz = nrm.z;
61812
+ }
61813
+ return { x, y, z, nx, ny, nz, faceIndex: 0 };
61814
+ });
61815
+ if (a.type === "points") {
61816
+ for (const v of verts) {
61817
+ // Only reuse the provided id for a single-point entry (the round-trip case);
61818
+ // a multi-point points entry must get a fresh id per marker to avoid duplicate keys.
61819
+ const id = a.id && verts.length === 1 ? a.id : this.nextId();
61820
+ const marker = makePointMarker(v, this.o.mesh, a.color, this.markerRadius);
61821
+ this.o.scene.add(marker);
61822
+ this.store.add({
61823
+ id,
61824
+ type: "points",
61825
+ mode: null,
61826
+ label: a.label,
61827
+ color: a.color,
61828
+ closed: false,
61829
+ visible: (_b = a.visible) !== null && _b !== void 0 ? _b : true,
61830
+ vertices: [v],
61831
+ object3D: marker,
61832
+ });
61833
+ // track max numeric id
61834
+ const m = id.match(/^a(\d+)$/);
61835
+ if (m)
61836
+ maxImported = Math.max(maxImported, parseInt(m[1], 10));
61837
+ count++;
61838
+ }
61839
+ }
61840
+ else {
61841
+ if (verts.length < 2)
61842
+ continue;
61843
+ const id = (_c = a.id) !== null && _c !== void 0 ? _c : this.nextId();
61844
+ const line = makeContourLine(verts, a.color, a.closed, this.o.container, this.epsilon, this.o.mesh);
61845
+ this.o.scene.add(line);
61846
+ this.store.add({
61847
+ id,
61848
+ type: "contour",
61849
+ mode: a.mode,
61850
+ label: a.label,
61851
+ color: a.color,
61852
+ closed: a.closed,
61853
+ visible: (_d = a.visible) !== null && _d !== void 0 ? _d : true,
61854
+ vertices: verts,
61855
+ object3D: line,
61856
+ });
61857
+ const m = id.match(/^a(\d+)$/);
61858
+ if (m)
61859
+ maxImported = Math.max(maxImported, parseInt(m[1], 10));
61860
+ count++;
61861
+ }
61862
+ }
61863
+ // keep seq ahead of any imported numeric ids to avoid collisions
61864
+ if (maxImported > this.seq)
61865
+ this.seq = maxImported;
61866
+ return count;
61867
+ }
61739
61868
  // ---- Internal ----
61869
+ get drawing() {
61870
+ return this.drawLock || this.spaceHeld;
61871
+ }
61740
61872
  applyCameraGating() {
61741
- // Holding Space for temporary rotation takes priority; otherwise enable the camera only in navigate mode.
61742
- this.o.controls.enabled = this.spaceHeld || this.mode === "navigate";
61873
+ var _a, _b;
61874
+ // Default is navigate (camera enabled). Drawing only when drawLock or spaceHeld.
61875
+ this.o.controls.enabled = !this.drawing;
61876
+ (_b = (_a = this.o).onInteractionChange) === null || _b === void 0 ? void 0 : _b.call(_a, { drawing: this.drawing, armed: this.armed, locked: this.drawLock });
61743
61877
  }
61744
61878
  /** Reconcile the scene against store.list(): add missing objects, remove deleted ones (no dispose, kept for undo restore). */
61745
61879
  reconcile() {
@@ -61757,6 +61891,11 @@ void main() {
61757
61891
  this.o.scene.add(o);
61758
61892
  }
61759
61893
  this.managed = present;
61894
+ // Apply per-annotation visibility
61895
+ for (const a of this.store.list()) {
61896
+ if (a.object3D)
61897
+ a.object3D.visible = a.visible;
61898
+ }
61760
61899
  this.applySelection();
61761
61900
  (_b = (_a = this.o).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, this.store.list());
61762
61901
  }
@@ -61764,6 +61903,8 @@ void main() {
61764
61903
  for (const a of this.store.list()) {
61765
61904
  if (!a.object3D)
61766
61905
  continue;
61906
+ if (!a.visible)
61907
+ continue;
61767
61908
  const sel = a.id === this.selectedId;
61768
61909
  if (a.type === "contour") {
61769
61910
  const mat = a.object3D.material;
@@ -61996,6 +62137,7 @@ void main() {
61996
62137
  label: `Contour ${this.seq}`,
61997
62138
  color: this.geodesicColor,
61998
62139
  closed,
62140
+ visible: true,
61999
62141
  vertices: verts,
62000
62142
  object3D: this.activeGeoLine,
62001
62143
  };
@@ -62029,7 +62171,8 @@ void main() {
62029
62171
  window.removeEventListener("keyup", this.onKeyUp);
62030
62172
  window.removeEventListener("resize", this.onResize);
62031
62173
  }
62032
- }
62174
+ }
62175
+ SurfaceAnnotator.TAP_MS = 250;
62033
62176
 
62034
62177
  class copperScene extends baseScene {
62035
62178
  // rayster pick
@@ -83452,15 +83595,15 @@ void main() {
83452
83595
  }
83453
83596
 
83454
83597
  // import * as kiwrious from "copper3d_plugin_heart_k";
83455
- // "v3.6.6" is injected at build time by rollup @rollup/plugin-replace, sourced from package.json version.
83598
+ // "v3.6.7" is injected at build time by rollup @rollup/plugin-replace, sourced from package.json version.
83456
83599
  // When copper3d is consumed from local source (no rollup replace step), the identifier is undefined and
83457
83600
  // referencing it throws a ReferenceError — guard it so local loading still works.
83458
83601
  let _revision = "unknown";
83459
83602
  try {
83460
- _revision = "v3.6.6";
83603
+ _revision = "v3.6.7";
83461
83604
  }
83462
83605
  catch (_a) {
83463
- /* "v3.6.6" not injected (local source build) */
83606
+ /* "v3.6.7" not injected (local source build) */
83464
83607
  }
83465
83608
  const REVISION = _revision;
83466
83609
  // Expose on global so the version can be read in a production browser console via window.__COPPER3D_VERSION__
@@ -29,6 +29,10 @@ export declare class MeshGraph {
29
29
  shortestPath(startV: number, endV: number): number[];
30
30
  vertexWorld(i: number, matrixWorld: THREE.Matrix4): THREE.Vector3;
31
31
  vertexNormalWorld(i: number, mesh: THREE.Mesh): THREE.Vector3;
32
+ /** Vertex local normal at index i (reads from the welded graphGeo normal attribute). */
33
+ vertexNormalLocal(i: number): THREE.Vector3;
34
+ /** Normal at the vertex nearest to localPoint (for recovering normals from imported point coordinates). */
35
+ nearestNormalLocal(p: THREE.Vector3): THREE.Vector3;
32
36
  /** Vertex local coordinates + local normal (the graph geometry is already local space). */
33
37
  vertexLocal(i: number): {
34
38
  x: number;