copper3d 3.0.0 → 3.0.1

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.
Files changed (45) hide show
  1. package/dist/Utils/segmentation/CommToolsData.d.ts +9 -10
  2. package/dist/Utils/segmentation/CommToolsData.js +4 -13
  3. package/dist/Utils/segmentation/CommToolsData.js.map +1 -1
  4. package/dist/Utils/segmentation/DragOperator.js +4 -4
  5. package/dist/Utils/segmentation/DragOperator.js.map +1 -1
  6. package/dist/Utils/segmentation/DrawToolCore.d.ts +5 -6
  7. package/dist/Utils/segmentation/DrawToolCore.js +57 -55
  8. package/dist/Utils/segmentation/DrawToolCore.js.map +1 -1
  9. package/dist/Utils/segmentation/NrrdTools.js +6 -2
  10. package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
  11. package/dist/Utils/segmentation/core/MaskVolume.d.ts +0 -33
  12. package/dist/Utils/segmentation/core/MaskVolume.js +0 -121
  13. package/dist/Utils/segmentation/core/MaskVolume.js.map +1 -1
  14. package/dist/Utils/segmentation/coreTools/coreType.d.ts +0 -3
  15. package/dist/Utils/segmentation/coreTools/coreType.js.map +1 -1
  16. package/dist/Utils/segmentation/coreTools/gui.d.ts +6 -3
  17. package/dist/Utils/segmentation/coreTools/gui.js.map +1 -1
  18. package/dist/Utils/segmentation/eventRouter/EventRouter.d.ts +1 -0
  19. package/dist/Utils/segmentation/eventRouter/EventRouter.js +9 -3
  20. package/dist/Utils/segmentation/eventRouter/EventRouter.js.map +1 -1
  21. package/dist/Utils/segmentation/tools/BaseTool.d.ts +3 -0
  22. package/dist/Utils/segmentation/tools/BaseTool.js.map +1 -1
  23. package/dist/Utils/segmentation/tools/ImageStoreHelper.d.ts +10 -13
  24. package/dist/Utils/segmentation/tools/ImageStoreHelper.js +13 -27
  25. package/dist/Utils/segmentation/tools/ImageStoreHelper.js.map +1 -1
  26. package/dist/Utils/segmentation/tools/SphereTool.d.ts +1 -3
  27. package/dist/Utils/segmentation/tools/SphereTool.js +1 -1
  28. package/dist/Utils/segmentation/tools/SphereTool.js.map +1 -1
  29. package/dist/Utils/segmentation/tools/ZoomTool.js +2 -1
  30. package/dist/Utils/segmentation/tools/ZoomTool.js.map +1 -1
  31. package/dist/bundle.esm.js +97 -228
  32. package/dist/bundle.umd.js +97 -228
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +1 -1
  35. package/dist/types/Utils/segmentation/CommToolsData.d.ts +9 -10
  36. package/dist/types/Utils/segmentation/DrawToolCore.d.ts +5 -6
  37. package/dist/types/Utils/segmentation/core/MaskVolume.d.ts +0 -33
  38. package/dist/types/Utils/segmentation/coreTools/coreType.d.ts +0 -3
  39. package/dist/types/Utils/segmentation/coreTools/gui.d.ts +6 -3
  40. package/dist/types/Utils/segmentation/eventRouter/EventRouter.d.ts +1 -0
  41. package/dist/types/Utils/segmentation/tools/BaseTool.d.ts +3 -0
  42. package/dist/types/Utils/segmentation/tools/ImageStoreHelper.d.ts +10 -13
  43. package/dist/types/Utils/segmentation/tools/SphereTool.d.ts +1 -3
  44. package/dist/types/index.d.ts +1 -1
  45. package/package.json +1 -1
@@ -71138,127 +71138,6 @@ class MaskVolume {
71138
71138
  }
71139
71139
  }
71140
71140
  }
71141
- /**
71142
- * Extract a 2D slice as raw RGBA ImageData (lossless round-trip).
71143
- *
71144
- * Requires the volume to have **≥ 4 channels** (ch0=R, ch1=G, ch2=B, ch3=A).
71145
- * Returns the exact pixel data that was stored via `setSliceFromImageData`,
71146
- * bypassing all render modes.
71147
- *
71148
- * @param sliceIndex Index along the specified axis.
71149
- * @param axis `'x'`, `'y'`, or `'z'` (default `'z'`).
71150
- * @returns ImageData with the stored RGBA values.
71151
- *
71152
- * @throws {RangeError} If sliceIndex is out of bounds.
71153
- * @throws {Error} If the volume has fewer than 4 channels.
71154
- */
71155
- getSliceRawImageData(sliceIndex, axis = 'z') {
71156
- if (this.numChannels < 4) {
71157
- throw new Error(`getSliceRawImageData requires ≥ 4 channels, volume has ${this.numChannels}`);
71158
- }
71159
- this.validateSliceIndex(sliceIndex, axis);
71160
- const { width, height } = this.dims;
71161
- const ch = this.numChannels;
71162
- const [sliceWidth, sliceHeight] = this.getSliceDimensions(axis);
71163
- const imageData = new ImageData(sliceWidth, sliceHeight);
71164
- const pixels = imageData.data;
71165
- const volData = this.data;
71166
- if (axis === 'z') {
71167
- // Z-axis (axial): slice data is contiguous in memory — direct bulk copy
71168
- const offset = sliceIndex * this.bytesPerSlice;
71169
- pixels.set(volData.subarray(offset, offset + this.bytesPerSlice));
71170
- }
71171
- else if (axis === 'y') {
71172
- // Y-axis (coronal): each row (fixed z, fixed y, varying x) is contiguous
71173
- // mapping: (i → x, sliceIndex → y, j → z)
71174
- const rowBytes = width * ch;
71175
- let px = 0;
71176
- for (let j = 0; j < sliceHeight; j++) {
71177
- const rowStart = j * height * width * ch + sliceIndex * width * ch;
71178
- pixels.set(volData.subarray(rowStart, rowStart + rowBytes), px);
71179
- px += rowBytes;
71180
- }
71181
- }
71182
- else {
71183
- // X-axis (sagittal): pixels not contiguous, but avoid function call overhead
71184
- // After dimension fix: sliceWidth = depth (Z), sliceHeight = height (Y)
71185
- // j → y (rows), i → z (columns)
71186
- let px = 0;
71187
- for (let j = 0; j < sliceHeight; j++) {
71188
- const yOffset = j * width * ch;
71189
- for (let i = 0; i < sliceWidth; i++) {
71190
- const baseIdx = i * height * width * ch + yOffset + sliceIndex * ch;
71191
- pixels[px] = volData[baseIdx];
71192
- pixels[px + 1] = volData[baseIdx + 1];
71193
- pixels[px + 2] = volData[baseIdx + 2];
71194
- pixels[px + 3] = volData[baseIdx + 3];
71195
- px += 4;
71196
- }
71197
- }
71198
- }
71199
- return imageData;
71200
- }
71201
- /**
71202
- * Write slice data into an existing ImageData buffer (zero-allocation).
71203
- *
71204
- * Same semantics as {@link getSliceRawImageData} but avoids creating a new
71205
- * ImageData object on every call. The caller is responsible for providing
71206
- * a buffer whose dimensions match the expected slice size.
71207
- *
71208
- * All pixels are fully overwritten — no clearing is needed beforehand.
71209
- *
71210
- * @param sliceIndex Index along the specified axis.
71211
- * @param axis `'x'`, `'y'`, or `'z'` (default `'z'`).
71212
- * @param target Pre-allocated ImageData to write into.
71213
- *
71214
- * @throws {Error} If the volume has fewer than 4 channels.
71215
- * @throws {RangeError} If sliceIndex is out of bounds.
71216
- * @throws {Error} If target dimensions don't match the slice.
71217
- */
71218
- getSliceRawImageDataInto(sliceIndex, axis = 'z', target) {
71219
- if (this.numChannels < 4) {
71220
- throw new Error(`getSliceRawImageDataInto requires ≥ 4 channels, volume has ${this.numChannels}`);
71221
- }
71222
- this.validateSliceIndex(sliceIndex, axis);
71223
- const [sliceWidth, sliceHeight] = this.getSliceDimensions(axis);
71224
- if (target.width !== sliceWidth || target.height !== sliceHeight) {
71225
- throw new Error(`Buffer size mismatch: expected ${sliceWidth}×${sliceHeight}, ` +
71226
- `got ${target.width}×${target.height}`);
71227
- }
71228
- const { width, height } = this.dims;
71229
- const ch = this.numChannels;
71230
- const pixels = target.data;
71231
- const volData = this.data;
71232
- if (axis === 'z') {
71233
- const offset = sliceIndex * this.bytesPerSlice;
71234
- pixels.set(volData.subarray(offset, offset + this.bytesPerSlice));
71235
- }
71236
- else if (axis === 'y') {
71237
- const rowBytes = width * ch;
71238
- let px = 0;
71239
- for (let j = 0; j < sliceHeight; j++) {
71240
- const rowStart = j * height * width * ch + sliceIndex * width * ch;
71241
- pixels.set(volData.subarray(rowStart, rowStart + rowBytes), px);
71242
- px += rowBytes;
71243
- }
71244
- }
71245
- else {
71246
- // X-axis (sagittal): sliceWidth = depth (Z), sliceHeight = height (Y)
71247
- // j → y (rows), i → z (columns)
71248
- let px = 0;
71249
- for (let j = 0; j < sliceHeight; j++) {
71250
- const yOffset = j * width * ch;
71251
- for (let i = 0; i < sliceWidth; i++) {
71252
- const baseIdx = i * height * width * ch + yOffset + sliceIndex * ch;
71253
- pixels[px] = volData[baseIdx];
71254
- pixels[px + 1] = volData[baseIdx + 1];
71255
- pixels[px + 2] = volData[baseIdx + 2];
71256
- pixels[px + 3] = volData[baseIdx + 3];
71257
- px += 4;
71258
- }
71259
- }
71260
- }
71261
- }
71262
71141
  // ── Label-based storage (1-channel volumes) ────────────────────────
71263
71142
  /**
71264
71143
  /**
@@ -72781,12 +72660,12 @@ class DragOperator {
72781
72660
  this.eventRouter.subscribeModeChange((prevMode, newMode) => {
72782
72661
  const prev = prevMode;
72783
72662
  const next = newMode;
72784
- // When entering draw or contrast mode, remove drag mode
72785
- if (next === 'draw' || next === 'contrast') {
72663
+ // When entering draw, contrast, or crosshair mode, remove drag mode
72664
+ if (next === 'draw' || next === 'contrast' || next === 'crosshair') {
72786
72665
  this.removeDragMode();
72787
72666
  }
72788
- // When leaving draw or contrast mode (returning to idle), restore drag mode
72789
- if ((prev === 'draw' || prev === 'contrast') && next === 'idle') {
72667
+ // When leaving draw, contrast, or crosshair mode (returning to idle), restore drag mode
72668
+ if ((prev === 'draw' || prev === 'contrast' || prev === 'crosshair') && next === 'idle') {
72790
72669
  if (!this.gui_states.sphere) {
72791
72670
  this.configDragMode();
72792
72671
  }
@@ -72889,7 +72768,6 @@ class CommToolsData {
72889
72768
  ratios: { x: 1, y: 1, z: 1 },
72890
72769
  contrastNum: 0,
72891
72770
  showContrast: false,
72892
- enableCursorChoose: false,
72893
72771
  isCursorSelect: false,
72894
72772
  cursorPageX: 0,
72895
72773
  cursorPageY: 0,
@@ -73029,8 +72907,6 @@ class CommToolsData {
73029
72907
  skipSlicesDic: {},
73030
72908
  currentShowingSlice: undefined,
73031
72909
  mainPreSlices: undefined,
73032
- Is_Shift_Pressed: false,
73033
- Is_Ctrl_Pressed: false,
73034
72910
  Is_Draw: false,
73035
72911
  axis: "z",
73036
72912
  maskData: {
@@ -73180,12 +73056,6 @@ class CommToolsData {
73180
73056
  clearStoreImages() {
73181
73057
  throw new Error("Child class must implement abstract clearStoreImages, currently you can find it in NrrdTools.");
73182
73058
  }
73183
- /**
73184
- * Rewrite this {createEmptyPaintImage} function under NrrdTools
73185
- */
73186
- createEmptyPaintImage(dimensions, paintImages) {
73187
- throw new Error("Child class must implement abstract clearStoreImages, currently you can find it in NrrdTools.");
73188
- }
73189
73059
  /**
73190
73060
  * Rewrite this {resizePaintArea} function under NrrdTools
73191
73061
  */
@@ -73247,13 +73117,13 @@ class CommToolsData {
73247
73117
  throw new Error("Child class must implement abstract redrawDisplayCanvas, currently you can find it in NrrdTools.");
73248
73118
  }
73249
73119
  /**
73250
- * Get a painted mask image (IPaintImage) based on current axis and input slice index.
73120
+ * Get a painted mask image based on current axis and input slice index.
73251
73121
  *
73252
- * Phase 3: Reads directly from MaskVolume (no caching needed — reads are fast).
73122
+ * Phase 3: Reads directly from MaskVolume.
73253
73123
  *
73254
73124
  * @param axis "x" | "y" | "z"
73255
73125
  * @param sliceIndex number
73256
- * @returns IPaintImage with the mask for the given slice, or undefined if not found
73126
+ * @returns Object with index and image, or undefined
73257
73127
  */
73258
73128
  filterDrawedImage(axis, sliceIndex) {
73259
73129
  try {
@@ -73310,7 +73180,7 @@ class CommToolsData {
73310
73180
  /**
73311
73181
  * Render a layer's slice into a reusable buffer and draw to the target canvas.
73312
73182
  *
73313
- * Uses MaskVolume.getSliceRawImageDataInto() for zero-allocation rendering.
73183
+ * Uses MaskVolume.renderLabelSliceInto() for zero-allocation rendering.
73314
73184
  * The caller should obtain the buffer via getOrCreateSliceBuffer() and reuse
73315
73185
  * it across multiple layer renders.
73316
73186
  *
@@ -73566,11 +73436,13 @@ class EventRouter {
73566
73436
  }
73567
73437
  /**
73568
73438
  * Toggle crosshair mode.
73439
+ * Blocked when draw or contrast mode is active (mutual exclusion).
73569
73440
  */
73570
73441
  toggleCrosshair() {
73571
73442
  if (!DRAWING_TOOLS.has(this.guiTool))
73572
73443
  return;
73573
- if (this.state.shiftHeld || this.state.ctrlHeld)
73444
+ // Block crosshair activation during draw or contrast
73445
+ if (this.state.shiftHeld || this.mode === 'draw' || this.mode === 'contrast')
73574
73446
  return;
73575
73447
  this.state.crosshairEnabled = !this.state.crosshairEnabled;
73576
73448
  this.setMode(this.state.crosshairEnabled ? 'crosshair' : 'idle');
@@ -73689,12 +73561,16 @@ class EventRouter {
73689
73561
  // Update state based on modifier keys
73690
73562
  if (ev.key === this.keyboardSettings.draw) {
73691
73563
  this.state.shiftHeld = true;
73692
- if (DRAWING_TOOLS.has(this.guiTool) && !this.state.ctrlHeld) {
73564
+ // Block draw mode when crosshair or contrast is active (mutual exclusion)
73565
+ if (DRAWING_TOOLS.has(this.guiTool) && !this.state.ctrlHeld && !this.state.crosshairEnabled) {
73693
73566
  this.setMode('draw');
73694
73567
  }
73695
73568
  }
73696
73569
  if (this.contrastEnabled && this.keyboardSettings.contrast.includes(ev.key)) {
73697
- this.state.ctrlHeld = true;
73570
+ // Block contrast state when crosshair or draw is active (mutual exclusion)
73571
+ if (!this.state.crosshairEnabled && this.mode !== 'draw') {
73572
+ this.state.ctrlHeld = true;
73573
+ }
73698
73574
  }
73699
73575
  // Route to external handler
73700
73576
  if (this.keydownHandler) {
@@ -73854,7 +73730,7 @@ class SphereTool extends BaseTool {
73854
73730
  this.callbacks.setEmptyCanvasSize(axis);
73855
73731
  this.callbacks.drawImageOnEmptyImage(this.ctx.protectedData.canvases.drawingSphereCanvas);
73856
73732
  const imageData = this.ctx.protectedData.ctxes.emptyCtx.getImageData(0, 0, this.ctx.protectedData.canvases.emptyCanvas.width, this.ctx.protectedData.canvases.emptyCanvas.height);
73857
- this.callbacks.storeImageToAxis(index, { x: [], y: [], z: [] }, imageData, axis);
73733
+ this.callbacks.storeImageToAxis(index, imageData, axis);
73858
73734
  }
73859
73735
  // ===== Multi-View Sphere =====
73860
73736
  drawSphereOnEachViews(decay, axis) {
@@ -74251,7 +74127,8 @@ class ZoomTool extends BaseTool {
74251
74127
  configMouseZoomWheel() {
74252
74128
  let moveDistance = 1;
74253
74129
  return (e) => {
74254
- if (this.ctx.protectedData.Is_Shift_Pressed) {
74130
+ var _a;
74131
+ if ((_a = this.ctx.eventRouter) === null || _a === void 0 ? void 0 : _a.isShiftHeld()) {
74255
74132
  return;
74256
74133
  }
74257
74134
  e.preventDefault();
@@ -74388,8 +74265,7 @@ class EraserTool extends BaseTool {
74388
74265
  * Extracted from DrawToolCore.ts:
74389
74266
  * - storeAllImages / storeImageToAxis / storeImageToLayer / storeEachLayerImage
74390
74267
  *
74391
- * Phase 2 Day 7: Updated to write/read MaskVolume alongside legacy IPaintImages.
74392
- * Volume is the primary storage; IPaintImages kept for backward compatibility.
74268
+ * Phase 3: MaskVolume is the sole storage backend. All IPaintImages params removed.
74393
74269
  */
74394
74270
  class ImageStoreHelper extends BaseTool {
74395
74271
  constructor(ctx, callbacks) {
@@ -74406,12 +74282,12 @@ class ImageStoreHelper extends BaseTool {
74406
74282
  */
74407
74283
  getVolumeForLayer(layer) {
74408
74284
  const { volumes } = this.ctx.protectedData.maskData;
74409
- switch (layer) {
74410
- case "layer1": return volumes.layer1;
74411
- case "layer2": return volumes.layer2;
74412
- case "layer3": return volumes.layer3;
74413
- default: return volumes.layer1;
74414
- }
74285
+ const vol = volumes[layer];
74286
+ if (vol)
74287
+ return vol;
74288
+ const firstLayerId = this.ctx.nrrd_states.layers[0];
74289
+ console.warn(`ImageStoreHelper: unknown layer "${layer}", falling back to "${firstLayerId}"`);
74290
+ return volumes[firstLayerId];
74415
74291
  }
74416
74292
  /**
74417
74293
  * Get MaskVolume for the currently active layer.
@@ -74430,19 +74306,17 @@ class ImageStoreHelper extends BaseTool {
74430
74306
  }
74431
74307
  // ===== Store Image To Axis =====
74432
74308
  /**
74433
- * Phase 3: Simplified to be a no-op.
74434
- * MaskVolume storage happens in storeAllImages via setSliceFromImageData.
74435
- * This method kept for backward compatibility with existing call sites.
74309
+ * Phase 3: No-op MaskVolume storage happens in storeAllImages.
74436
74310
  */
74437
- storeImageToAxis(_index, _paintedImages, _imageData, _axis) {
74311
+ storeImageToAxis(_index, _imageData, _axis) {
74438
74312
  // No-op: MaskVolume is the primary storage, updated in storeAllImages
74439
74313
  }
74440
74314
  /**
74441
74315
  * Retrieve the drawn image for a given axis and slice.
74442
74316
  *
74443
- * Phase 3: Reads exclusively from MaskVolume (no legacy fallback).
74317
+ * Phase 3: Reads exclusively from MaskVolume.
74444
74318
  */
74445
- filterDrawedImage(axis, sliceIndex, _paintedImages) {
74319
+ filterDrawedImage(axis, sliceIndex) {
74446
74320
  try {
74447
74321
  const volume = this.getCurrentVolume();
74448
74322
  if (volume) {
@@ -74531,10 +74405,9 @@ class ImageStoreHelper extends BaseTool {
74531
74405
  }
74532
74406
  }
74533
74407
  /**
74534
- * Phase 3: Simplified - extracts ImageData from canvas but no longer stores to paintImages.
74535
- * Kept for backward compatibility with existing call sites.
74408
+ * Extract ImageData from canvas (MaskVolume storage is handled in storeAllImages).
74536
74409
  */
74537
- storeImageToLayer(_index, canvas, _paintedImages) {
74410
+ storeImageToLayer(_index, canvas) {
74538
74411
  if (!this.ctx.nrrd_states.loadMaskJson) {
74539
74412
  this.callbacks.setEmptyCanvasSize();
74540
74413
  this.callbacks.drawImageOnEmptyImage(canvas);
@@ -74543,16 +74416,6 @@ class ImageStoreHelper extends BaseTool {
74543
74416
  // No longer stores to paintedImages - MaskVolume is primary storage
74544
74417
  return imageData;
74545
74418
  }
74546
- // ===== Helper Methods =====
74547
- hasNonZeroPixels(imageData) {
74548
- const data = imageData.data;
74549
- for (let i = 0; i < data.length; i += 4) {
74550
- if (data[i] !== 0 || data[i + 1] !== 0 || data[i + 2] !== 0 || data[i + 3] !== 0) {
74551
- return true;
74552
- }
74553
- }
74554
- return false;
74555
- }
74556
74419
  }
74557
74420
 
74558
74421
  class DrawToolCore extends CommToolsData {
@@ -74631,8 +74494,7 @@ class DrawToolCore extends CommToolsData {
74631
74494
  this.sphereTool = new SphereTool(toolCtx, {
74632
74495
  setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
74633
74496
  drawImageOnEmptyImage: (canvas) => this.drawImageOnEmptyImage(canvas),
74634
- storeImageToAxis: (index, paintedImages, imageData, axis) => this.imageStoreHelper.storeImageToAxis(index, paintedImages, imageData, axis),
74635
- createEmptyPaintImage: (dimensions, paintImages) => this.createEmptyPaintImage(dimensions, paintImages),
74497
+ storeImageToAxis: (index, imageData, axis) => this.imageStoreHelper.storeImageToAxis(index, imageData, axis),
74636
74498
  });
74637
74499
  this.crosshairTool = new CrosshairTool(toolCtx);
74638
74500
  this.contrastTool = new ContrastTool(toolCtx, this.container, this.contrastEventPrameters, {
@@ -74655,40 +74517,28 @@ class DrawToolCore extends CommToolsData {
74655
74517
  // Use string comparison to avoid TypeScript narrowing issues
74656
74518
  const prev = prevMode;
74657
74519
  const next = newMode;
74658
- // Sync EventRouter mode changes with existing state flags
74659
- if (next === 'draw') {
74660
- this.protectedData.Is_Shift_Pressed = true;
74661
- this.nrrd_states.enableCursorChoose = false;
74662
- }
74663
- else if (prev === 'draw') {
74664
- this.protectedData.Is_Shift_Pressed = false;
74665
- }
74666
74520
  if (next === 'contrast') {
74667
- this.protectedData.Is_Ctrl_Pressed = true;
74668
- this.protectedData.Is_Shift_Pressed = false;
74669
74521
  this.configContrastDragMode();
74670
74522
  }
74671
74523
  else if (prev === 'contrast') {
74672
- this.protectedData.Is_Ctrl_Pressed = false;
74673
74524
  this.removeContrastDragMode();
74674
74525
  this.gui_states.readyToUpdate = true;
74675
74526
  }
74676
74527
  if (next === 'crosshair') {
74677
- this.nrrd_states.enableCursorChoose = true;
74678
74528
  this.protectedData.Is_Draw = false;
74679
74529
  }
74680
- else if (prev === 'crosshair') {
74681
- this.nrrd_states.enableCursorChoose = false;
74682
- }
74683
74530
  }
74684
74531
  });
74532
+ // Inject eventRouter into ToolContext so tools can query mode/state
74533
+ const toolCtx = this.sphereTool['ctx'];
74534
+ toolCtx.eventRouter = this.eventRouter;
74685
74535
  // Configure keyboard settings from class field
74686
74536
  this.eventRouter.setKeyboardSettings(this._keyboardSettings);
74687
74537
  // Track undo flag for Ctrl+Z handling
74688
74538
  let undoFlag = false;
74689
74539
  // Register keyboard handlers with EventRouter
74690
74540
  this.eventRouter.setKeydownHandler((ev) => {
74691
- var _a;
74541
+ var _a, _b;
74692
74542
  if (this._configKeyBoard)
74693
74543
  return;
74694
74544
  // Handle undo (Ctrl+Z)
@@ -74709,15 +74559,16 @@ class DrawToolCore extends CommToolsData {
74709
74559
  }
74710
74560
  }
74711
74561
  // Handle draw mode (Shift key) - EventRouter already tracks this
74562
+ // EventRouter's handleKeyDown will enforce mutual exclusion
74712
74563
  if (ev.key === this._keyboardSettings.draw && !this.gui_states.sphere && !this.gui_states.calculator) {
74713
- if (this.protectedData.Is_Ctrl_Pressed) {
74564
+ if ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCtrlHeld()) {
74714
74565
  return; // Ctrl takes priority
74715
74566
  }
74716
74567
  // EventRouter will set mode to 'draw' via internal handler
74717
74568
  }
74718
74569
  });
74719
74570
  this.eventRouter.setKeyupHandler((ev) => {
74720
- var _a, _b, _c, _d;
74571
+ var _a, _b, _c, _d, _e, _f;
74721
74572
  if (this._configKeyBoard)
74722
74573
  return;
74723
74574
  // Handle Ctrl key release (contrast mode toggle)
@@ -74730,12 +74581,15 @@ class DrawToolCore extends CommToolsData {
74730
74581
  // Skip mode toggle when contrast shortcut is disabled
74731
74582
  if (!((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isContrastEnabled()))
74732
74583
  return;
74584
+ // Block contrast toggle during crosshair or draw (mutual exclusion)
74585
+ if (((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled()) || ((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.getMode()) === 'draw')
74586
+ return;
74733
74587
  // Toggle contrast mode manually since it's on keyup
74734
- if (((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.getMode()) !== 'contrast') {
74735
- (_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.setMode('contrast');
74588
+ if (((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.getMode()) !== 'contrast') {
74589
+ (_e = this.eventRouter) === null || _e === void 0 ? void 0 : _e.setMode('contrast');
74736
74590
  }
74737
74591
  else {
74738
- (_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.setMode('idle');
74592
+ (_f = this.eventRouter) === null || _f === void 0 ? void 0 : _f.setMode('idle');
74739
74593
  }
74740
74594
  }
74741
74595
  });
@@ -74911,6 +74765,7 @@ class DrawToolCore extends CommToolsData {
74911
74765
  }
74912
74766
  };
74913
74767
  this.drawingPrameters.handleOnDrawingMouseDown = (e) => {
74768
+ var _a, _b, _c, _d;
74914
74769
  if (leftclicked || rightclicked) {
74915
74770
  this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
74916
74771
  this.protectedData.ctxes.drawingLayerMasterCtx.closePath();
@@ -74926,7 +74781,7 @@ class DrawToolCore extends CommToolsData {
74926
74781
  // remove it when mouse click down
74927
74782
  this.protectedData.canvases.drawingCanvas.removeEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
74928
74783
  if (e.button === 0) {
74929
- if (this.protectedData.Is_Shift_Pressed) {
74784
+ if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode()) === 'draw') {
74930
74785
  leftclicked = true;
74931
74786
  lines = [];
74932
74787
  Is_Painting = true;
@@ -74953,13 +74808,13 @@ class DrawToolCore extends CommToolsData {
74953
74808
  const vol = this.getVolumeForLayer(this.gui_states.layer);
74954
74809
  this.preDrawSlice = vol.getSliceUint8(this.preDrawSliceIndex, this.preDrawAxis).data.slice();
74955
74810
  }
74956
- catch (_a) {
74811
+ catch (_e) {
74957
74812
  this.preDrawSlice = null;
74958
74813
  }
74959
74814
  this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
74960
74815
  this.protectedData.canvases.drawingCanvas.addEventListener("pointermove", this.drawingPrameters.handleOnDrawingMouseMove);
74961
74816
  }
74962
- else if (this.nrrd_states.enableCursorChoose) {
74817
+ else if ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled()) {
74963
74818
  this.nrrd_states.cursorPageX =
74964
74819
  e.offsetX / this.nrrd_states.sizeFoctor;
74965
74820
  this.nrrd_states.cursorPageY =
@@ -74967,10 +74822,10 @@ class DrawToolCore extends CommToolsData {
74967
74822
  this.enableCrosshair();
74968
74823
  this.protectedData.canvases.drawingCanvas.addEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
74969
74824
  }
74970
- else if (this.gui_states.sphere && !this.nrrd_states.enableCursorChoose) {
74825
+ else if (this.gui_states.sphere && !((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isCrosshairEnabled())) {
74971
74826
  sphere(e);
74972
74827
  }
74973
- else if (this.gui_states.calculator && !this.nrrd_states.enableCursorChoose) {
74828
+ else if (this.gui_states.calculator && !((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.isCrosshairEnabled())) {
74974
74829
  this.drawCalSphereDown(e.offsetX, e.offsetY, this.nrrd_states.currentIndex, this.gui_states.cal_distance);
74975
74830
  }
74976
74831
  }
@@ -75035,8 +74890,9 @@ class DrawToolCore extends CommToolsData {
75035
74890
  ctx.drawImage(this.protectedData.canvases.emptyCanvas, 0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
75036
74891
  };
75037
74892
  this.drawingPrameters.handleOnDrawingMouseUp = (e) => {
74893
+ var _a, _b, _c, _d;
75038
74894
  if (e.button === 0) {
75039
- if (this.protectedData.Is_Shift_Pressed || Is_Painting) {
74895
+ if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode()) === 'draw' || Is_Painting) {
75040
74896
  leftclicked = false;
75041
74897
  let { ctx, canvas } = this.setCurrentLayer();
75042
74898
  ctx.closePath();
@@ -75084,7 +74940,7 @@ class DrawToolCore extends CommToolsData {
75084
74940
  };
75085
74941
  this.undoManager.push(delta);
75086
74942
  }
75087
- catch (_a) {
74943
+ catch (_e) {
75088
74944
  // Volume not ready — skip
75089
74945
  }
75090
74946
  this.preDrawSlice = null;
@@ -75095,7 +74951,7 @@ class DrawToolCore extends CommToolsData {
75095
74951
  });
75096
74952
  }
75097
74953
  else if (this.gui_states.sphere &&
75098
- !this.nrrd_states.enableCursorChoose) {
74954
+ !((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled())) {
75099
74955
  // plan B
75100
74956
  // findout all index in the sphere radius range in Axial view
75101
74957
  if (this.nrrd_states.spherePlanB) {
@@ -75115,12 +74971,12 @@ class DrawToolCore extends CommToolsData {
75115
74971
  this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
75116
74972
  }
75117
74973
  else if ((this.gui_states.sphere || this.gui_states.calculator) &&
75118
- this.nrrd_states.enableCursorChoose) {
74974
+ ((_c = this.eventRouter) === null || _c === void 0 ? void 0 : _c.isCrosshairEnabled())) {
75119
74975
  this.protectedData.canvases.drawingCanvas.addEventListener("wheel", this.drawingPrameters.handleMouseZoomSliceWheel);
75120
74976
  this.protectedData.canvases.drawingCanvas.removeEventListener("pointerup", this.drawingPrameters.handleOnDrawingMouseUp);
75121
74977
  }
75122
74978
  else if (this.gui_states.calculator &&
75123
- !this.nrrd_states.enableCursorChoose) {
74979
+ !((_d = this.eventRouter) === null || _d === void 0 ? void 0 : _d.isCrosshairEnabled())) {
75124
74980
  // When mouse up
75125
74981
  this.drawCalSphereUp();
75126
74982
  }
@@ -75162,6 +75018,7 @@ class DrawToolCore extends CommToolsData {
75162
75018
  }
75163
75019
  });
75164
75020
  this.start = () => {
75021
+ var _a, _b;
75165
75022
  if (this.gui_states.readyToUpdate) {
75166
75023
  this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
75167
75024
  this.protectedData.ctxes.drawingCtx.globalAlpha =
@@ -75175,7 +75032,10 @@ class DrawToolCore extends CommToolsData {
75175
75032
  }
75176
75033
  }
75177
75034
  else {
75178
- if (this.protectedData.Is_Shift_Pressed) {
75035
+ // Use EventRouter mode for mutually exclusive crosshair vs draw rendering
75036
+ const currentMode = (_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.getMode();
75037
+ if (currentMode === 'draw') {
75038
+ // Draw mode: show brush circle preview
75179
75039
  if (!this.gui_states.pencil &&
75180
75040
  !this.gui_states.Eraser &&
75181
75041
  this.nrrd_states.Mouse_Over) {
@@ -75189,7 +75049,8 @@ class DrawToolCore extends CommToolsData {
75189
75049
  this.protectedData.ctxes.drawingCtx.stroke();
75190
75050
  }
75191
75051
  }
75192
- if (this.nrrd_states.enableCursorChoose) {
75052
+ else if (currentMode === 'crosshair' || ((_b = this.eventRouter) === null || _b === void 0 ? void 0 : _b.isCrosshairEnabled())) {
75053
+ // Crosshair mode: show red cross lines (mutually exclusive with draw)
75193
75054
  this.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.nrrd_states.changedWidth, this.nrrd_states.changedHeight);
75194
75055
  const ex = this.nrrd_states.cursorPageX * this.nrrd_states.sizeFoctor;
75195
75056
  const ey = this.nrrd_states.cursorPageY * this.nrrd_states.sizeFoctor;
@@ -75345,19 +75206,18 @@ class DrawToolCore extends CommToolsData {
75345
75206
  /**
75346
75207
  * Clear mask on current slice canvas.
75347
75208
  *
75348
- * Phase 2: Clears the MaskVolume slice for all three layers,
75349
- * re-stores, and notifies external via getMask callback with clearFlag=true.
75350
- * Phase 6: Also records a MaskDelta for undo support.
75209
+ * Only clears the active layer's MaskVolume slice data.
75210
+ * Other layers are left untouched. After clearing, all layer canvases
75211
+ * are re-rendered from MaskVolume to keep visuals in sync.
75351
75212
  */
75352
75213
  clearPaint() {
75353
75214
  this.protectedData.Is_Draw = true;
75354
- this.resetLayerCanvas();
75355
75215
  this.protectedData.canvases.originCanvas.width =
75356
75216
  this.protectedData.canvases.originCanvas.width;
75357
75217
  this.protectedData.mainPreSlices.repaint.call(this.protectedData.mainPreSlices);
75358
75218
  this.protectedData.previousDrawingImage =
75359
75219
  this.protectedData.ctxes.emptyCtx.createImageData(1, 1);
75360
- // Phase 2 + 6: Clear volume slices and record undo delta
75220
+ // Clear only the active layer's MaskVolume slice and record undo delta
75361
75221
  try {
75362
75222
  const axis = this.protectedData.axis;
75363
75223
  const idx = this.nrrd_states.currentIndex;
@@ -75365,11 +75225,8 @@ class DrawToolCore extends CommToolsData {
75365
75225
  const vol = this.getVolumeForLayer(activeLayer);
75366
75226
  // Capture old slice for undo before clearing
75367
75227
  const oldSlice = vol.getSliceUint8(idx, axis).data.slice();
75368
- // Clear only the active layer (clear also clears all for canvas consistency)
75369
- const { layer1, layer2, layer3 } = this.protectedData.maskData.volumes;
75370
- layer1.clearSlice(idx, axis);
75371
- layer2.clearSlice(idx, axis);
75372
- layer3.clearSlice(idx, axis);
75228
+ // Clear only the active layer's MaskVolume slice
75229
+ vol.clearSlice(idx, axis);
75373
75230
  // New (all-zero) slice for undo newSlice
75374
75231
  const { data: newSlice, width, height } = vol.getSliceUint8(idx, axis);
75375
75232
  // Push clearPaint delta to UndoManager (supports undo)
@@ -75388,12 +75245,23 @@ class DrawToolCore extends CommToolsData {
75388
75245
  }
75389
75246
  }
75390
75247
  catch (_a) {
75391
- // Volume not ready (1×1×1 placeholder) — continue with legacy path
75248
+ // Volume not ready (1×1×1 placeholder)
75249
+ }
75250
+ // Re-render ALL layers from MaskVolume to canvas (rebuilds visuals from source of truth)
75251
+ this.resetLayerCanvas();
75252
+ const buffer = this.getOrCreateSliceBuffer(this.protectedData.axis);
75253
+ if (buffer) {
75254
+ const w = this.nrrd_states.changedWidth;
75255
+ const h = this.nrrd_states.changedHeight;
75256
+ for (const layerId of this.nrrd_states.layers) {
75257
+ const target = this.protectedData.layerTargets.get(layerId);
75258
+ if (!target)
75259
+ continue;
75260
+ target.ctx.clearRect(0, 0, w, h);
75261
+ this.renderSliceToCanvas(layerId, this.protectedData.axis, this.nrrd_states.currentIndex, buffer, target.ctx, w, h);
75262
+ }
75392
75263
  }
75393
- this.storeAllImages(this.nrrd_states.currentIndex, this.gui_states.layer);
75394
- const restLayers = this.getRestLayer();
75395
- this.storeEachLayerImage(this.nrrd_states.currentIndex, restLayers[0]);
75396
- this.storeEachLayerImage(this.nrrd_states.currentIndex, restLayers[1]);
75264
+ this.compositeAllLayers();
75397
75265
  this.setIsDrawFalse(1000);
75398
75266
  }
75399
75267
  /**
@@ -75474,14 +75342,11 @@ class DrawToolCore extends CommToolsData {
75474
75342
  this.protectedData.ctxes.drawingLayerMasterCtx.getImageData(0, 0, this.protectedData.canvases.drawingCanvasLayerMaster.width, this.protectedData.canvases.drawingCanvasLayerMaster.height);
75475
75343
  }
75476
75344
  /****************************Store images (delegated to ImageStoreHelper)****************************************************/
75477
- storeImageToAxis(index, paintedImages, imageData, axis) {
75478
- this.imageStoreHelper.storeImageToAxis(index, paintedImages, imageData, axis);
75479
- }
75480
75345
  storeAllImages(index, layer) {
75481
75346
  this.imageStoreHelper.storeAllImages(index, layer);
75482
75347
  }
75483
- storeImageToLayer(index, canvas, paintedImages) {
75484
- return this.imageStoreHelper.storeImageToLayer(index, canvas, paintedImages);
75348
+ storeImageToLayer(index, canvas) {
75349
+ return this.imageStoreHelper.storeImageToLayer(index, canvas);
75485
75350
  }
75486
75351
  storeEachLayerImage(index, layer) {
75487
75352
  this.imageStoreHelper.storeEachLayerImage(index, layer);
@@ -76177,8 +76042,9 @@ class NrrdTools extends DrawToolCore {
76177
76042
  * @param {string} aixs:"x" | "y" | "z"
76178
76043
  * */
76179
76044
  setSliceOrientation(axisTo) {
76045
+ var _a;
76180
76046
  let convetObj;
76181
- if (this.nrrd_states.enableCursorChoose || this.gui_states.sphere) {
76047
+ if (((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isCrosshairEnabled()) || this.gui_states.sphere) {
76182
76048
  if (this.protectedData.axis === "z") {
76183
76049
  this.cursorPage.z.index = this.nrrd_states.currentIndex;
76184
76050
  this.cursorPage.z.cursorPageX = this.nrrd_states.cursorPageX;
@@ -76563,6 +76429,8 @@ class NrrdTools extends DrawToolCore {
76563
76429
  }
76564
76430
  // Invalidate reusable slice buffer
76565
76431
  this.invalidateSliceBuffer();
76432
+ // Reload all layers to canvas (restores other layers' visuals)
76433
+ this.reloadMasksFromVolume();
76566
76434
  }
76567
76435
  /**
76568
76436
  * Reset the draw and display canvases layout after mouse pan.
@@ -76773,7 +76641,8 @@ class NrrdTools extends DrawToolCore {
76773
76641
  */
76774
76642
  configMouseSliceWheel() {
76775
76643
  const handleMouseZoomSliceWheelMove = (e) => {
76776
- if (this.protectedData.Is_Shift_Pressed) {
76644
+ var _a;
76645
+ if ((_a = this.eventRouter) === null || _a === void 0 ? void 0 : _a.isShiftHeld()) {
76777
76646
  return;
76778
76647
  }
76779
76648
  e.preventDefault();
@@ -77013,7 +76882,7 @@ function evaluateElement(element, weights) {
77013
76882
  }
77014
76883
 
77015
76884
  // import * as kiwrious from "copper3d_plugin_heart_k";
77016
- const REVISION = "v3.0.0-beta";
76885
+ const REVISION = "v3.0.1-beta";
77017
76886
  console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
77018
76887
 
77019
76888
  export { CHANNEL_COLORS, CHANNEL_HEX_COLORS, CameraViewPoint, Copper3dTrackballControls, MeshNodeTool, NrrdTools, REVISION, addBoxHelper, addLabelToScene, configKiwriousHeart, convert3DPostoScreenPos, convertScreenPosto3DPos, copperMScene, copperMSceneRenderer, copperRenderer, copperRendererOnDemond, copperScene, copperSceneOnDemond, createTexture2D_NRRD, fullScreenListenner, kiwrious, loading, removeGuiFolderChilden, rgbaToCss, rgbaToHex, setHDRFilePath, throttle };