copper3d 3.6.6 → 3.6.8

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.
@@ -49760,8 +49760,11 @@ void main() {
49760
49760
  this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
49761
49761
  };
49762
49762
  this.renderer = renderer;
49763
- this.ambientLight = new AmbientLight(0x606060, 0.8);
49764
- this.directionalLight = new DirectionalLight(0xffffff, 1.0);
49763
+ // three r165+ removed `useLegacyLights`; lighting is always physically
49764
+ // correct now, which makes the same intensity ~1/PI as dim as the old
49765
+ // (three <=0.150) legacy default. Scale by PI to keep the prior brightness.
49766
+ this.ambientLight = new AmbientLight(0x606060, 0.8 * Math.PI);
49767
+ this.directionalLight = new DirectionalLight(0xffffff, 1.0 * Math.PI);
49765
49768
  if (!(opt === null || opt === void 0 ? void 0 : opt.alpha)) {
49766
49769
  this.vignette = createBackground({
49767
49770
  aspect: this.container.clientWidth / this.container.clientHeight,
@@ -49846,7 +49849,7 @@ void main() {
49846
49849
  }
49847
49850
  addLights() {
49848
49851
  // Hemisphere light: sky/ground for broad ambient fill
49849
- const hemiLight = new HemisphereLight(0xddeeff, 0x0f0e0d, 0.6);
49852
+ const hemiLight = new HemisphereLight(0xddeeff, 0x0f0e0d, 0.6 * Math.PI);
49850
49853
  hemiLight.name = "hemi_light";
49851
49854
  this.scene.add(hemiLight);
49852
49855
  // Main key light — front-right, slightly above
@@ -49856,12 +49859,12 @@ void main() {
49856
49859
  this.camera.add(this.ambientLight);
49857
49860
  this.camera.add(this.directionalLight);
49858
49861
  // Fill light — opposite side to soften shadows
49859
- const fillLight = new DirectionalLight(0xffffff, 0.4);
49862
+ const fillLight = new DirectionalLight(0xffffff, 0.4 * Math.PI);
49860
49863
  fillLight.name = "fill_light";
49861
49864
  fillLight.position.set(-0.5, -0.2, -0.866);
49862
49865
  this.camera.add(fillLight);
49863
49866
  // Top light — illuminates the top surface that key/fill lights miss
49864
- const topLight = new DirectionalLight(0xffffff, 0.5);
49867
+ const topLight = new DirectionalLight(0xffffff, 0.5 * Math.PI);
49865
49868
  topLight.name = "top_light";
49866
49869
  topLight.position.set(0, 1, 0.2);
49867
49870
  this.camera.add(topLight);
@@ -61173,6 +61176,15 @@ void main() {
61173
61176
  const nm = new Matrix3().getNormalMatrix(mesh.matrixWorld);
61174
61177
  return n.applyMatrix3(nm).normalize();
61175
61178
  }
61179
+ /** Vertex local normal at index i (reads from the welded graphGeo normal attribute). */
61180
+ vertexNormalLocal(i) {
61181
+ 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);
61182
+ }
61183
+ /** Normal at the vertex nearest to localPoint (for recovering normals from imported point coordinates). */
61184
+ nearestNormalLocal(p) {
61185
+ const i = this.nearestVertex(p);
61186
+ return this.vertexNormalLocal(i);
61187
+ }
61176
61188
  /** Vertex local coordinates + local normal (the graph geometry is already local space). */
61177
61189
  vertexLocal(i) {
61178
61190
  return {
@@ -61398,6 +61410,13 @@ void main() {
61398
61410
  this.notify();
61399
61411
  }
61400
61412
  }
61413
+ setVisible(id, visible) {
61414
+ const a = this.get(id);
61415
+ if (a) {
61416
+ a.visible = visible;
61417
+ this.notify();
61418
+ }
61419
+ }
61401
61420
  toJSON(modelName, mesh, opts = {}) {
61402
61421
  var _a;
61403
61422
  const space = (_a = opts.space) !== null && _a !== void 0 ? _a : "local";
@@ -61422,6 +61441,7 @@ void main() {
61422
61441
  label: a.label,
61423
61442
  color: a.color,
61424
61443
  closed: a.closed,
61444
+ visible: a.visible,
61425
61445
  points: a.vertices.map(toPt),
61426
61446
  })),
61427
61447
  };
@@ -61441,6 +61461,10 @@ void main() {
61441
61461
  var _a, _b, _c;
61442
61462
  this.mode = "navigate";
61443
61463
  this.spaceHeld = false;
61464
+ this.drawLock = false;
61465
+ this.armed = "freehand";
61466
+ this.spaceDownAt = 0;
61467
+ this.spaceDragged = false;
61444
61468
  this.pointerDown = false;
61445
61469
  this.store = new AnnotationStore();
61446
61470
  this.managed = new Set();
@@ -61467,14 +61491,18 @@ void main() {
61467
61491
  (this.activeGeoLine.material.resolution.set(w, h));
61468
61492
  };
61469
61493
  this.onPointerDown = (e) => {
61470
- if (this.spaceHeld)
61471
- return;
61472
61494
  if (e.button !== 0)
61473
61495
  return; // left button only
61474
61496
  if (!this.insideContainer(e))
61475
61497
  return;
61476
61498
  this.pointerDown = true;
61477
- if (this.mode === "point") {
61499
+ // Only act with the armed tool when in drawing mode (drawLock or spaceHeld).
61500
+ // When not drawing, navigation is the default — pointer events go to camera controls.
61501
+ if (!this.drawing)
61502
+ return;
61503
+ // Use armed tool (not this.mode which may be "navigate")
61504
+ const activeTool = this.armed;
61505
+ if (activeTool === "point") {
61478
61506
  const h = this.hit(e);
61479
61507
  if (!h)
61480
61508
  return;
@@ -61488,13 +61516,14 @@ void main() {
61488
61516
  label: `Point ${this.seq}`,
61489
61517
  color: this.pointColorVal,
61490
61518
  closed: false,
61519
+ visible: true,
61491
61520
  vertices: [v],
61492
61521
  object3D: marker,
61493
61522
  };
61494
61523
  this.store.add(ann);
61495
61524
  return;
61496
61525
  }
61497
- if (this.mode === "freehand") {
61526
+ if (activeTool === "freehand") {
61498
61527
  this.activeStroke = new StrokeContour(this.minGap, this.maxJump, this.o.mesh);
61499
61528
  this.activeStroke.begin();
61500
61529
  const h = this.hit(e);
@@ -61504,7 +61533,7 @@ void main() {
61504
61533
  this.o.scene.add(this.activeLine);
61505
61534
  return;
61506
61535
  }
61507
- if (this.mode === "geodesic") {
61536
+ if (activeTool === "geodesic") {
61508
61537
  // First check whether an existing anchor was clicked → cancel that point (any point can be canceled).
61509
61538
  const pick = this.pickGeoMarker(e);
61510
61539
  if (pick >= 0 && this.activeGeo) {
@@ -61531,15 +61560,26 @@ void main() {
61531
61560
  }
61532
61561
  };
61533
61562
  this.onPointerMove = (e) => {
61534
- if (this.spaceHeld) {
61535
- this.setGeoHover(-1);
61563
+ // Track if user dragged while space was held (so a hold-drag is not treated as a tap).
61564
+ if (this.spaceHeld && this.pointerDown) {
61565
+ this.spaceDragged = true;
61566
+ }
61567
+ // When not drawing, navigation is default — don't interfere with camera controls.
61568
+ if (!this.drawing) {
61569
+ // In geodesic mode we can still show the hover hint when not drawing.
61570
+ if (this.armed === "geodesic" && !this.pointerDown) {
61571
+ this.setGeoHover(this.insideContainer(e) ? this.pickGeoMarker(e) : -1);
61572
+ }
61573
+ else {
61574
+ this.setGeoHover(-1);
61575
+ }
61536
61576
  return;
61537
61577
  }
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) {
61578
+ // Drawing mode: geodesic hover hint
61579
+ if (this.armed === "geodesic" && !this.pointerDown) {
61540
61580
  this.setGeoHover(this.insideContainer(e) ? this.pickGeoMarker(e) : -1);
61541
61581
  }
61542
- if (this.mode === "freehand" &&
61582
+ if (this.armed === "freehand" &&
61543
61583
  this.pointerDown &&
61544
61584
  this.activeStroke &&
61545
61585
  this.activeLine) {
@@ -61564,6 +61604,7 @@ void main() {
61564
61604
  label: `Contour ${this.seq}`,
61565
61605
  color: this.freehandColor,
61566
61606
  closed: false,
61607
+ visible: true,
61567
61608
  vertices: verts,
61568
61609
  object3D: this.activeLine,
61569
61610
  };
@@ -61581,11 +61622,17 @@ void main() {
61581
61622
  if (this.isTypingTarget(e))
61582
61623
  return;
61583
61624
  if (e.code === "Space") {
61625
+ if (!this.spaceHeld) {
61626
+ this.spaceDownAt = performance.now();
61627
+ this.spaceDragged = false;
61628
+ }
61584
61629
  this.spaceHeld = true;
61585
61630
  this.applyCameraGating();
61631
+ e.preventDefault();
61586
61632
  return;
61587
61633
  }
61588
61634
  if (e.key === "Escape") {
61635
+ this.drawLock = false;
61589
61636
  this.setMode("navigate");
61590
61637
  return;
61591
61638
  }
@@ -61618,7 +61665,11 @@ void main() {
61618
61665
  };
61619
61666
  this.onKeyUp = (e) => {
61620
61667
  if (e.code === "Space") {
61668
+ const held = performance.now() - this.spaceDownAt;
61621
61669
  this.spaceHeld = false;
61670
+ if (held <= SurfaceAnnotator.TAP_MS && !this.spaceDragged) {
61671
+ this.drawLock = !this.drawLock; // tap toggles lock
61672
+ }
61622
61673
  this.applyCameraGating();
61623
61674
  }
61624
61675
  };
@@ -61674,6 +61725,10 @@ void main() {
61674
61725
  if (m !== "geodesic" && this.activeGeo)
61675
61726
  this.clearActiveGeo();
61676
61727
  this.mode = m;
61728
+ // Record armed tool when choosing a drawing mode (not navigate).
61729
+ // Camera gating is NOT changed here — the user must use Space to enter drawing.
61730
+ if (m !== "navigate")
61731
+ this.armed = m;
61677
61732
  this.applyCameraGating();
61678
61733
  (_b = (_a = this.o).onModeChange) === null || _b === void 0 ? void 0 : _b.call(_a, m);
61679
61734
  }
@@ -61736,10 +61791,92 @@ void main() {
61736
61791
  exportJSON(modelName, opts) {
61737
61792
  return this.store.toJSON(modelName, this.o.mesh, opts);
61738
61793
  }
61794
+ setVisible(id, visible) {
61795
+ this.store.setVisible(id, visible);
61796
+ }
61797
+ /**
61798
+ * Rebuild annotations from an exported payload (local-space points). Normals are taken from the
61799
+ * point when present ([x,y,z,nx,ny,nz]); otherwise recovered from the welded graph's nearest
61800
+ * vertex. Each imported item becomes first-class (select/recolor/hide/delete/export).
61801
+ */
61802
+ importAnnotations(payload) {
61803
+ var _a, _b, _c, _d;
61804
+ let count = 0;
61805
+ let maxImported = this.seq;
61806
+ for (const a of (_a = payload.annotations) !== null && _a !== void 0 ? _a : []) {
61807
+ const verts = a.points.map((p) => {
61808
+ const [x, y, z] = p;
61809
+ let nx = p[3], ny = p[4], nz = p[5];
61810
+ if (nx === undefined || ny === undefined || nz === undefined) {
61811
+ const nrm = this.graph.nearestNormalLocal(new Vector3(x, y, z));
61812
+ nx = nrm.x;
61813
+ ny = nrm.y;
61814
+ nz = nrm.z;
61815
+ }
61816
+ return { x, y, z, nx, ny, nz, faceIndex: 0 };
61817
+ });
61818
+ if (a.type === "points") {
61819
+ for (const v of verts) {
61820
+ // Only reuse the provided id for a single-point entry (the round-trip case);
61821
+ // a multi-point points entry must get a fresh id per marker to avoid duplicate keys.
61822
+ const id = a.id && verts.length === 1 ? a.id : this.nextId();
61823
+ const marker = makePointMarker(v, this.o.mesh, a.color, this.markerRadius);
61824
+ this.o.scene.add(marker);
61825
+ this.store.add({
61826
+ id,
61827
+ type: "points",
61828
+ mode: null,
61829
+ label: a.label,
61830
+ color: a.color,
61831
+ closed: false,
61832
+ visible: (_b = a.visible) !== null && _b !== void 0 ? _b : true,
61833
+ vertices: [v],
61834
+ object3D: marker,
61835
+ });
61836
+ // track max numeric id
61837
+ const m = id.match(/^a(\d+)$/);
61838
+ if (m)
61839
+ maxImported = Math.max(maxImported, parseInt(m[1], 10));
61840
+ count++;
61841
+ }
61842
+ }
61843
+ else {
61844
+ if (verts.length < 2)
61845
+ continue;
61846
+ const id = (_c = a.id) !== null && _c !== void 0 ? _c : this.nextId();
61847
+ const line = makeContourLine(verts, a.color, a.closed, this.o.container, this.epsilon, this.o.mesh);
61848
+ this.o.scene.add(line);
61849
+ this.store.add({
61850
+ id,
61851
+ type: "contour",
61852
+ mode: a.mode,
61853
+ label: a.label,
61854
+ color: a.color,
61855
+ closed: a.closed,
61856
+ visible: (_d = a.visible) !== null && _d !== void 0 ? _d : true,
61857
+ vertices: verts,
61858
+ object3D: line,
61859
+ });
61860
+ const m = id.match(/^a(\d+)$/);
61861
+ if (m)
61862
+ maxImported = Math.max(maxImported, parseInt(m[1], 10));
61863
+ count++;
61864
+ }
61865
+ }
61866
+ // keep seq ahead of any imported numeric ids to avoid collisions
61867
+ if (maxImported > this.seq)
61868
+ this.seq = maxImported;
61869
+ return count;
61870
+ }
61739
61871
  // ---- Internal ----
61872
+ get drawing() {
61873
+ return this.drawLock || this.spaceHeld;
61874
+ }
61740
61875
  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";
61876
+ var _a, _b;
61877
+ // Default is navigate (camera enabled). Drawing only when drawLock or spaceHeld.
61878
+ this.o.controls.enabled = !this.drawing;
61879
+ (_b = (_a = this.o).onInteractionChange) === null || _b === void 0 ? void 0 : _b.call(_a, { drawing: this.drawing, armed: this.armed, locked: this.drawLock });
61743
61880
  }
61744
61881
  /** Reconcile the scene against store.list(): add missing objects, remove deleted ones (no dispose, kept for undo restore). */
61745
61882
  reconcile() {
@@ -61757,6 +61894,11 @@ void main() {
61757
61894
  this.o.scene.add(o);
61758
61895
  }
61759
61896
  this.managed = present;
61897
+ // Apply per-annotation visibility
61898
+ for (const a of this.store.list()) {
61899
+ if (a.object3D)
61900
+ a.object3D.visible = a.visible;
61901
+ }
61760
61902
  this.applySelection();
61761
61903
  (_b = (_a = this.o).onChange) === null || _b === void 0 ? void 0 : _b.call(_a, this.store.list());
61762
61904
  }
@@ -61764,6 +61906,8 @@ void main() {
61764
61906
  for (const a of this.store.list()) {
61765
61907
  if (!a.object3D)
61766
61908
  continue;
61909
+ if (!a.visible)
61910
+ continue;
61767
61911
  const sel = a.id === this.selectedId;
61768
61912
  if (a.type === "contour") {
61769
61913
  const mat = a.object3D.material;
@@ -61996,6 +62140,7 @@ void main() {
61996
62140
  label: `Contour ${this.seq}`,
61997
62141
  color: this.geodesicColor,
61998
62142
  closed,
62143
+ visible: true,
61999
62144
  vertices: verts,
62000
62145
  object3D: this.activeGeoLine,
62001
62146
  };
@@ -62029,7 +62174,8 @@ void main() {
62029
62174
  window.removeEventListener("keyup", this.onKeyUp);
62030
62175
  window.removeEventListener("resize", this.onResize);
62031
62176
  }
62032
- }
62177
+ }
62178
+ SurfaceAnnotator.TAP_MS = 250;
62033
62179
 
62034
62180
  class copperScene extends baseScene {
62035
62181
  // rayster pick
@@ -83452,15 +83598,15 @@ void main() {
83452
83598
  }
83453
83599
 
83454
83600
  // 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.
83601
+ // "v3.6.8" is injected at build time by rollup @rollup/plugin-replace, sourced from package.json version.
83456
83602
  // When copper3d is consumed from local source (no rollup replace step), the identifier is undefined and
83457
83603
  // referencing it throws a ReferenceError — guard it so local loading still works.
83458
83604
  let _revision = "unknown";
83459
83605
  try {
83460
- _revision = "v3.6.6";
83606
+ _revision = "v3.6.8";
83461
83607
  }
83462
83608
  catch (_a) {
83463
- /* "v3.6.6" not injected (local source build) */
83609
+ /* "v3.6.8" not injected (local source build) */
83464
83610
  }
83465
83611
  const REVISION = _revision;
83466
83612
  // 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;
@@ -31,6 +31,12 @@ export interface SurfaceAnnotatorOptions {
31
31
  onModeChange?: (m: AnnotationMode) => void;
32
32
  /** Callback when the annotation list changes (add/remove/undo/clear). */
33
33
  onChange?: (annotations: Annotation[]) => void;
34
+ /** Callback when interaction state changes (drawing, armed tool, draw-lock status). */
35
+ onInteractionChange?: (s: {
36
+ drawing: boolean;
37
+ armed: AnnotationMode;
38
+ locked: boolean;
39
+ }) => void;
34
40
  }
35
41
  /**
36
42
  * Main surface-annotation controller (Phase 4): four modes navigate / freehand / geodesic / point,
@@ -42,6 +48,11 @@ export declare class SurfaceAnnotator {
42
48
  private o;
43
49
  private mode;
44
50
  private spaceHeld;
51
+ private drawLock;
52
+ private armed;
53
+ private spaceDownAt;
54
+ private spaceDragged;
55
+ private static readonly TAP_MS;
45
56
  private pointerDown;
46
57
  private readonly markerRadius;
47
58
  private readonly epsilon;
@@ -91,9 +102,29 @@ export declare class SurfaceAnnotator {
91
102
  label: string;
92
103
  color: string;
93
104
  closed: boolean;
105
+ visible: boolean;
94
106
  points: number[][];
95
107
  }[];
96
108
  };
109
+ setVisible(id: string, visible: boolean): void;
110
+ /**
111
+ * Rebuild annotations from an exported payload (local-space points). Normals are taken from the
112
+ * point when present ([x,y,z,nx,ny,nz]); otherwise recovered from the welded graph's nearest
113
+ * vertex. Each imported item becomes first-class (select/recolor/hide/delete/export).
114
+ */
115
+ importAnnotations(payload: {
116
+ annotations: Array<{
117
+ id?: string;
118
+ type: "contour" | "points";
119
+ mode: "freehand" | "geodesic" | null;
120
+ label: string;
121
+ color: string;
122
+ closed: boolean;
123
+ visible?: boolean;
124
+ points: number[][];
125
+ }>;
126
+ }): number;
127
+ private get drawing();
97
128
  private applyCameraGating;
98
129
  /** Reconcile the scene against store.list(): add missing objects, remove deleted ones (no dispose, kept for undo restore). */
99
130
  private reconcile;
@@ -20,6 +20,7 @@ export declare class AnnotationStore {
20
20
  get(id: string): Annotation | undefined;
21
21
  setLabel(id: string, label: string): void;
22
22
  setColor(id: string, color: string): void;
23
+ setVisible(id: string, visible: boolean): void;
23
24
  toJSON(modelName: string, mesh: THREE.Mesh, opts?: ExportOptions): {
24
25
  model: string;
25
26
  exportedAt: string;
@@ -31,6 +32,7 @@ export declare class AnnotationStore {
31
32
  label: string;
32
33
  color: string;
33
34
  closed: boolean;
35
+ visible: boolean;
34
36
  points: number[][];
35
37
  }[];
36
38
  };
@@ -22,6 +22,7 @@ export interface Annotation {
22
22
  label: string;
23
23
  color: string;
24
24
  closed: boolean;
25
+ visible: boolean;
25
26
  vertices: AnnotationVertex[];
26
27
  object3D: THREE.Object3D | null;
27
28
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "copper3d",
3
3
  "description": "A 3d visualisation package base on threejs provides multiple scenes and Nrrd image load funtion.",
4
- "version": "3.6.6",
4
+ "version": "3.6.8",
5
5
  "main": "dist/bundle.umd.js",
6
6
  "moudle": "dist/bundle.esm.js",
7
7
  "types": "dist/types/index.d.ts",