copper3d 3.6.5 → 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.
@@ -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
@@ -83019,12 +83162,20 @@ void main() {
83019
83162
  return this.drawCore.aiAssistTool.getScratchSegments();
83020
83163
  }
83021
83164
  /**
83022
- * Merge the AI scratch into a target layer as a single undoable group (sandbox
83023
- * merge — best-practice: non-destructive + one Ctrl+Z). The scratch's channel
83024
- * labels are PRESERVED (each AI channel maps onto the same channel of the
83025
- * target layer). Scans all z-slices, so voxels painted from any view are caught.
83165
+ * Merge the AI scratch into a target layer with an explicit per-segmentation
83166
+ * mapping, as a single undoable group (sandbox merge — non-destructive + one
83167
+ * Ctrl+Z). `mapping` is `{ AI scratch label -> target channel(1-8) }`; any label
83168
+ * absent from the mapping (or mapped to 0) is DISCARDED (not merged). Several AI
83169
+ * labels MAY map to the SAME channel — after merge the channel is the identity
83170
+ * (CLAUDE.md decision M2), so the merged voxels take that channel's fixed colour.
83171
+ *
83172
+ * UNION semantics (M4): an AI voxel only fills a target voxel that is currently
83173
+ * EMPTY (0) — existing manual annotation is NEVER erased. Where two AI labels map
83174
+ * to different channels and overlap the same voxel, the first one written wins
83175
+ * (iteration order), an accepted edge case for a single-value-per-voxel mask.
83176
+ * Scans all z-slices, so voxels painted from any view are caught.
83026
83177
  */
83027
- aiCommitToLayer(targetLayer = "layer1") {
83178
+ aiCommitToLayerMapped(targetLayer, mapping) {
83028
83179
  const scratch = this.drawCore.aiAssistTool.getScratchVolume();
83029
83180
  if (!scratch)
83030
83181
  return;
@@ -83046,10 +83197,21 @@ void main() {
83046
83197
  continue;
83047
83198
  const oldSlice = target.getSliceUint8(z, "z").data; // copy
83048
83199
  const newSlice = oldSlice.slice();
83200
+ let changed = false;
83049
83201
  for (let i = 0; i < newSlice.length; i++) {
83050
- if (sc[i] !== 0)
83051
- newSlice[i] = sc[i]; // preserve the AI channel label
83202
+ const label = sc[i];
83203
+ if (label === 0)
83204
+ continue;
83205
+ const ch = mapping[label];
83206
+ if (!ch)
83207
+ continue; // unmapped / discarded segmentation
83208
+ if (newSlice[i] === 0) { // union: fill only EMPTY voxels (M4)
83209
+ newSlice[i] = ch;
83210
+ changed = true;
83211
+ }
83052
83212
  }
83213
+ if (!changed)
83214
+ continue; // nothing landed on this slice → no delta
83053
83215
  target.setSliceUint8(z, newSlice, "z");
83054
83216
  deltas.push({ layerId: targetLayer, axis: "z", sliceIndex: z, oldSlice, newSlice: newSlice.slice() });
83055
83217
  }
@@ -83057,6 +83219,19 @@ void main() {
83057
83219
  this.drawCore.undoManager.pushGroup(deltas);
83058
83220
  this.reloadMasksFromVolume();
83059
83221
  }
83222
+ /**
83223
+ * Back-compat shim: merge with an IDENTITY mapping (each AI label → the same
83224
+ * channel number) via the mapped path above. Newer callers should use
83225
+ * `aiCommitToLayerMapped` with an explicit per-segmentation mapping.
83226
+ */
83227
+ aiCommitToLayer(targetLayer = "layer1") {
83228
+ var _a;
83229
+ const segs = this.drawCore.aiAssistTool.getScratchSegments();
83230
+ const mapping = {};
83231
+ for (const s of (_a = segs === null || segs === void 0 ? void 0 : segs.segments) !== null && _a !== void 0 ? _a : [])
83232
+ mapping[s.label] = s.label;
83233
+ this.aiCommitToLayerMapped(targetLayer, mapping);
83234
+ }
83060
83235
  // ═══════════════════════════════════════════════════════════════════════════
83061
83236
  // 11. Clear / Reset
83062
83237
  // ═══════════════════════════════════════════════════════════════════════════
@@ -83420,8 +83595,17 @@ void main() {
83420
83595
  }
83421
83596
 
83422
83597
  // import * as kiwrious from "copper3d_plugin_heart_k";
83423
- // "v3.6.5" is injected at build time by rollup @rollup/plugin-replace, sourced from package.json version
83424
- const REVISION = "v3.6.5";
83598
+ // "v3.6.7" is injected at build time by rollup @rollup/plugin-replace, sourced from package.json version.
83599
+ // When copper3d is consumed from local source (no rollup replace step), the identifier is undefined and
83600
+ // referencing it throws a ReferenceError — guard it so local loading still works.
83601
+ let _revision = "unknown";
83602
+ try {
83603
+ _revision = "v3.6.7";
83604
+ }
83605
+ catch (_a) {
83606
+ /* "v3.6.7" not injected (local source build) */
83607
+ }
83608
+ const REVISION = _revision;
83425
83609
  // Expose on global so the version can be read in a production browser console via window.__COPPER3D_VERSION__
83426
83610
  if (typeof window !== "undefined") {
83427
83611
  window.__COPPER3D_VERSION__ = REVISION;
package/dist/index.d.ts CHANGED
@@ -28,6 +28,6 @@ import { CHANNEL_COLORS, CHANNEL_HEX_COLORS, AI_MASK_CHANNEL_COLORS, AI_CHANNEL_
28
28
  import type { LayerId, ChannelValue } from "./Utils/segmentation/core/index";
29
29
  import type { AiPromptTool, AiPromptPoint, AiPromptPayload, AiMaskResult } from "./Utils/segmentation/tools/AiAssistTool";
30
30
  import "./css/style.css";
31
- export declare const REVISION = "v3.6.5";
31
+ export declare const REVISION: string;
32
32
  export { copperRenderer, copperRendererOnDemond, copperMSceneRenderer, setHDRFilePath, addLabelToScene, convert3DPostoScreenPos, convertScreenPosto3DPos, addBoxHelper, fullScreenListenner, configKiwriousHeart, copperScene, copperSceneOnDemond, copperMScene, CameraViewPoint, kiwrious, NrrdTools, loading, Copper3dTrackballControls, createTexture2D_NRRD, MeshNodeTool, throttle, removeGuiFolderChilden, CHANNEL_COLORS, CHANNEL_HEX_COLORS, AI_MASK_CHANNEL_COLORS, AI_CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss, GaussianSmoother, SurfaceAnnotator, };
33
33
  export type { positionType, screenPosType, optsType, nrrdMeshesType, nrrdSliceType, SensorDecodedValue_kiwrious, SensorReadResult_kiwrious, HeartRateResult_kiwrious, loadingBarType, IPaintImage, exportPaintImageType, IOptVTKLoader, aligned4DSurfaceType, aligned4DOptsType, Aligned4DController, ICommXYZ, IGUIStates, IGuiParameterSettings, INrrdStates, NrrdState, GuiState, IGuiMeta, ToolMode, IAnnotationCallbacks, LayerId, ChannelValue, AiPromptTool, AiPromptPoint, AiPromptPayload, AiMaskResult, SurfaceAnnotatorOptions, Annotation, AnnotationMode, ExportOptions, };
package/dist/index.js CHANGED
@@ -22,8 +22,17 @@ import { removeGuiFolderChilden } from "./Utils/segmentation/coreTools/gui";
22
22
  import { SurfaceAnnotator } from "./Utils/surfaceAnnotation";
23
23
  import { CHANNEL_COLORS, CHANNEL_HEX_COLORS, AI_MASK_CHANNEL_COLORS, AI_CHANNEL_HEX_COLORS, rgbaToHex, rgbaToCss } from "./Utils/segmentation/core/index";
24
24
  import "./css/style.css";
25
- // __REVISION__ is injected at build time by rollup @rollup/plugin-replace, sourced from package.json version
26
- export const REVISION = __REVISION__;
25
+ // __REVISION__ is injected at build time by rollup @rollup/plugin-replace, sourced from package.json version.
26
+ // When copper3d is consumed from local source (no rollup replace step), the identifier is undefined and
27
+ // referencing it throws a ReferenceError — guard it so local loading still works.
28
+ let _revision = "unknown";
29
+ try {
30
+ _revision = __REVISION__;
31
+ }
32
+ catch (_a) {
33
+ /* __REVISION__ not injected (local source build) */
34
+ }
35
+ export const REVISION = _revision;
27
36
  // Expose on global so the version can be read in a production browser console via window.__COPPER3D_VERSION__
28
37
  if (typeof window !== "undefined") {
29
38
  window.__COPPER3D_VERSION__ = REVISION;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EACL,eAAe,EAGf,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAY,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,QAAQ,MAAM,iCAAiC,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAC9E,iDAAiD;AAEjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAE5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AA0B7D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAS1J,OAAO,iBAAiB,CAAC;AAEzB,6GAA6G;AAC7G,MAAM,CAAC,MAAM,QAAQ,GAAG,YAAY,CAAC;AAErC,8GAA8G;AAC9G,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;IAChC,MAAc,CAAC,oBAAoB,GAAG,QAAQ,CAAC;CACjD;AAED,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE;IAClC,OAAO,CAAC,GAAG,CACT,8BAA8B,QAAQ,EAAE,EACxC,8CAA8C,EAC9C,8CAA8C,CAC/C,CAAC;CACH;AAED,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,SAAS,EACT,OAAO,EACP,yBAAyB,EACzB,oBAAoB,EACpB,YAAY,EACZ,QAAQ,EACR,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,gBAAgB,GACjB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EACL,eAAe,EAGf,uBAAuB,EACvB,uBAAuB,GACxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAY,MAAM,2BAA2B,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iCAAiC,CAAC;AACvE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,QAAQ,MAAM,iCAAiC,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gCAAgC,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,4CAA4C,CAAC;AAC9E,iDAAiD;AAEjD,OAAO,EAAE,yBAAyB,EAAE,MAAM,sCAAsC,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAE5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AA0B7D,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAS1J,OAAO,iBAAiB,CAAC;AAEzB,8GAA8G;AAC9G,wGAAwG;AACxG,kFAAkF;AAClF,IAAI,SAAS,GAAG,SAAS,CAAC;AAC1B,IAAI;IACF,SAAS,GAAG,YAAY,CAAC;CAC1B;AAAC,WAAM;IACN,oDAAoD;CACrD;AACD,MAAM,CAAC,MAAM,QAAQ,GAAG,SAAS,CAAC;AAElC,8GAA8G;AAC9G,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE;IAChC,MAAc,CAAC,oBAAoB,GAAG,QAAQ,CAAC;CACjD;AAED,IAAI,OAAO,OAAO,KAAK,WAAW,EAAE;IAClC,OAAO,CAAC,GAAG,CACT,8BAA8B,QAAQ,EAAE,EACxC,8CAA8C,EAC9C,8CAA8C,CAC/C,CAAC;CACH;AAED,OAAO,EACL,cAAc,EACd,sBAAsB,EACtB,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,uBAAuB,EACvB,uBAAuB,EACvB,YAAY,EACZ,mBAAmB,EACnB,mBAAmB,EACnB,WAAW,EACX,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,SAAS,EACT,OAAO,EACP,yBAAyB,EACzB,oBAAoB,EACpB,YAAY,EACZ,QAAQ,EACR,sBAAsB,EACtB,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACtB,qBAAqB,EACrB,SAAS,EACT,SAAS,EACT,gBAAgB,EAChB,gBAAgB,GACjB,CAAC"}
@@ -276,10 +276,24 @@ export declare class NrrdTools {
276
276
  }[];
277
277
  } | null;
278
278
  /**
279
- * Merge the AI scratch into a target layer as a single undoable group (sandbox
280
- * merge — best-practice: non-destructive + one Ctrl+Z). The scratch's channel
281
- * labels are PRESERVED (each AI channel maps onto the same channel of the
282
- * target layer). Scans all z-slices, so voxels painted from any view are caught.
279
+ * Merge the AI scratch into a target layer with an explicit per-segmentation
280
+ * mapping, as a single undoable group (sandbox merge — non-destructive + one
281
+ * Ctrl+Z). `mapping` is `{ AI scratch label -> target channel(1-8) }`; any label
282
+ * absent from the mapping (or mapped to 0) is DISCARDED (not merged). Several AI
283
+ * labels MAY map to the SAME channel — after merge the channel is the identity
284
+ * (CLAUDE.md decision M2), so the merged voxels take that channel's fixed colour.
285
+ *
286
+ * UNION semantics (M4): an AI voxel only fills a target voxel that is currently
287
+ * EMPTY (0) — existing manual annotation is NEVER erased. Where two AI labels map
288
+ * to different channels and overlap the same voxel, the first one written wins
289
+ * (iteration order), an accepted edge case for a single-value-per-voxel mask.
290
+ * Scans all z-slices, so voxels painted from any view are caught.
291
+ */
292
+ aiCommitToLayerMapped(targetLayer: string, mapping: Record<number, number>): void;
293
+ /**
294
+ * Back-compat shim: merge with an IDENTITY mapping (each AI label → the same
295
+ * channel number) via the mapped path above. Newer callers should use
296
+ * `aiCommitToLayerMapped` with an explicit per-segmentation mapping.
283
297
  */
284
298
  aiCommitToLayer(targetLayer?: string): void;
285
299
  clearActiveLayer(): void;
@@ -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.5",
4
+ "version": "3.6.7",
5
5
  "main": "dist/bundle.umd.js",
6
6
  "moudle": "dist/bundle.esm.js",
7
7
  "types": "dist/types/index.d.ts",