copper3d 3.4.5 → 3.4.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.
Files changed (31) hide show
  1. package/dist/Utils/segmentation/DrawToolCore.d.ts +19 -2
  2. package/dist/Utils/segmentation/DrawToolCore.js +58 -13
  3. package/dist/Utils/segmentation/DrawToolCore.js.map +1 -1
  4. package/dist/Utils/segmentation/NrrdTools.d.ts +0 -1
  5. package/dist/Utils/segmentation/NrrdTools.js +2 -7
  6. package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
  7. package/dist/Utils/segmentation/core/types.d.ts +0 -1
  8. package/dist/Utils/segmentation/core/types.js.map +1 -1
  9. package/dist/Utils/segmentation/coreTools/gui.d.ts +0 -1
  10. package/dist/Utils/segmentation/coreTools/gui.js +21 -29
  11. package/dist/Utils/segmentation/coreTools/gui.js.map +1 -1
  12. package/dist/Utils/segmentation/tools/DrawingTool.d.ts +18 -2
  13. package/dist/Utils/segmentation/tools/DrawingTool.js +60 -21
  14. package/dist/Utils/segmentation/tools/DrawingTool.js.map +1 -1
  15. package/dist/Utils/segmentation/tools/ToolHost.d.ts +2 -3
  16. package/dist/Utils/utils.d.ts +0 -1
  17. package/dist/Utils/utils.js +0 -80
  18. package/dist/Utils/utils.js.map +1 -1
  19. package/dist/bundle.esm.js +141 -149
  20. package/dist/bundle.umd.js +141 -149
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/types/Utils/segmentation/DrawToolCore.d.ts +19 -2
  24. package/dist/types/Utils/segmentation/NrrdTools.d.ts +0 -1
  25. package/dist/types/Utils/segmentation/core/types.d.ts +0 -1
  26. package/dist/types/Utils/segmentation/coreTools/gui.d.ts +0 -1
  27. package/dist/types/Utils/segmentation/tools/DrawingTool.d.ts +18 -2
  28. package/dist/types/Utils/segmentation/tools/ToolHost.d.ts +2 -3
  29. package/dist/types/Utils/utils.d.ts +0 -1
  30. package/dist/types/index.d.ts +1 -1
  31. package/package.json +1 -1
@@ -40606,86 +40606,6 @@ function switchPencilIcon(icon, urls) {
40606
40606
  }
40607
40607
  return url;
40608
40608
  }
40609
- function switchEraserSize(size, urls) {
40610
- let url = "";
40611
- if (!!urls && urls.length > 0) {
40612
- if (size <= 3) {
40613
- url = `url(${urls[0]}) 3 3, crosshair`;
40614
- }
40615
- else if (3 < size && size <= 8) {
40616
- url = `url(${urls[1]}) 8 8, crosshair`;
40617
- }
40618
- else if (8 < size && size <= 13) {
40619
- url = `url(${urls[2]}) 13 13, crosshair`;
40620
- }
40621
- else if (13 < size && size <= 18) {
40622
- url = `url(${urls[3]}) 18 18, crosshair`;
40623
- }
40624
- else if (18 < size && size <= 23) {
40625
- url = `url(${urls[4]}) 23 23, crosshair`;
40626
- }
40627
- else if (23 < size && size <= 28) {
40628
- url = `url(${urls[5]}) 28 28, crosshair`;
40629
- }
40630
- else if (28 < size && size <= 33) {
40631
- url = `url(${urls[6]}) 33 33, crosshair`;
40632
- }
40633
- else if (33 < size && size <= 38) {
40634
- url = `url(${urls[7]}) 38 38, crosshair`;
40635
- }
40636
- else if (38 < size && size <= 43) {
40637
- url = `url(${urls[8]}) 43 43, crosshair`;
40638
- }
40639
- else if (43 < size && size <= 48) {
40640
- url = `url(${urls[9]}) 48 48, crosshair`;
40641
- }
40642
- else if (48 < size && size <= 53) {
40643
- url = `url(${urls[10]}) 52 52, crosshair`;
40644
- }
40645
- else {
40646
- url = `url(${urls[11]}) 52 52, crosshair`;
40647
- }
40648
- }
40649
- else {
40650
- if (size <= 3) {
40651
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_3.png) 3 3, crosshair`;
40652
- }
40653
- else if (3 < size && size <= 8) {
40654
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_8.png) 8 8, crosshair`;
40655
- }
40656
- else if (8 < size && size <= 13) {
40657
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_13.png) 13 13, crosshair`;
40658
- }
40659
- else if (13 < size && size <= 18) {
40660
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_18.png) 18 18, crosshair`;
40661
- }
40662
- else if (18 < size && size <= 23) {
40663
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_23.png) 23 23, crosshair`;
40664
- }
40665
- else if (23 < size && size <= 28) {
40666
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_28.png) 28 28, crosshair`;
40667
- }
40668
- else if (28 < size && size <= 33) {
40669
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_33.png) 33 33, crosshair`;
40670
- }
40671
- else if (33 < size && size <= 38) {
40672
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_38.png) 38 38, crosshair`;
40673
- }
40674
- else if (38 < size && size <= 43) {
40675
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_43.png) 43 43, crosshair`;
40676
- }
40677
- else if (43 < size && size <= 48) {
40678
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_48.png) 48 48, crosshair`;
40679
- }
40680
- else if (48 < size && size <= 53) {
40681
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_52.png) 52 52, crosshair`;
40682
- }
40683
- else {
40684
- url = `url(https://raw.githubusercontent.com/LinkunGao/copper3d-datasets/main/icons/eraser/circular-cursor_52.png) 52 52, crosshair`;
40685
- }
40686
- }
40687
- return url;
40688
- }
40689
40609
  function throttle(callback, wait) {
40690
40610
  let start = 0;
40691
40611
  return function (event) {
@@ -73002,10 +72922,7 @@ function setupGui(configs) {
73002
72922
  .name("BrushAndEraserSize")
73003
72923
  .min(5)
73004
72924
  .max(50)
73005
- .step(1)
73006
- .onChange(() => {
73007
- updateGuiBrushAndEraserSize();
73008
- });
72925
+ .step(1);
73009
72926
  actionsFolder.add(configs.gui_states.mode, "eraser").onChange((value) => {
73010
72927
  updateGuiEraserState();
73011
72928
  });
@@ -73133,37 +73050,33 @@ function setupGui(configs) {
73133
73050
  configs.gui_states.viewConfig.readyToUpdate = true;
73134
73051
  });
73135
73052
  actionsFolder.open();
73136
- const updateGuiBrushAndEraserSize = () => {
73137
- if (configs.gui_states.mode.eraser) {
73138
- configs.eraserUrls.length > 0
73139
- ? (configs.drawingCanvas.style.cursor = switchEraserSize(configs.gui_states.drawing.brushAndEraserSize, configs.eraserUrls))
73140
- : (configs.drawingCanvas.style.cursor = switchEraserSize(configs.gui_states.drawing.brushAndEraserSize));
73141
- }
73142
- };
73143
- const updatePencilState = () => {
73144
- if (configs.gui_states.mode.pencil) {
73145
- // add canvas brush circle move event listeners
73146
- configs.drawingCanvas.removeEventListener("mouseover", configs.drawingPrameters.handleOnDrawingBrushCricleMove);
73147
- configs.drawingCanvas.removeEventListener("mouseout", configs.drawingPrameters.handleOnDrawingBrushCricleMove);
73053
+ // Add/remove the hover-tracking listeners that feed the brush/eraser preview
73054
+ // ring (mouseOver position). Present for brush & eraser, removed for pencil.
73055
+ const setBrushHoverTracking = (enabled) => {
73056
+ const handler = configs.drawingPrameters.handleOnDrawingBrushCricleMove;
73057
+ if (enabled) {
73058
+ configs.drawingCanvas.addEventListener("mouseover", handler);
73059
+ configs.drawingCanvas.addEventListener("mouseout", handler);
73148
73060
  }
73149
73061
  else {
73150
- // add canvas brush circle move event listeners
73151
- configs.drawingCanvas.addEventListener("mouseover", configs.drawingPrameters.handleOnDrawingBrushCricleMove);
73152
- configs.drawingCanvas.addEventListener("mouseout", configs.drawingPrameters.handleOnDrawingBrushCricleMove);
73062
+ configs.drawingCanvas.removeEventListener("mouseover", handler);
73063
+ configs.drawingCanvas.removeEventListener("mouseout", handler);
73153
73064
  }
73065
+ };
73066
+ const updatePencilState = () => {
73067
+ // Pencil has no preview ring; brush keeps the hover-tracking listeners.
73068
+ setBrushHoverTracking(!configs.gui_states.mode.pencil);
73154
73069
  configs.drawingCanvas.style.cursor = configs.gui_states.viewConfig.defaultPaintCursor;
73155
73070
  };
73156
73071
  const updateGuiEraserState = () => {
73157
- // configs.gui_states.mode.eraser = value;
73072
+ // The eraser shows a dashed preview ring (rendered in DrawToolCore.start),
73073
+ // so it needs the same hover-tracking as the brush. The canvas keeps the
73074
+ // default paint cursor; the ring conveys size (resize via Shift+wheel).
73158
73075
  if (configs.gui_states.mode.eraser) {
73159
- configs.eraserUrls.length > 0
73160
- ? (configs.drawingCanvas.style.cursor = switchEraserSize(configs.gui_states.drawing.brushAndEraserSize, configs.eraserUrls))
73161
- : (configs.drawingCanvas.style.cursor = switchEraserSize(configs.gui_states.drawing.brushAndEraserSize));
73162
- }
73163
- else {
73164
- configs.drawingCanvas.style.cursor =
73165
- configs.gui_states.viewConfig.defaultPaintCursor;
73076
+ setBrushHoverTracking(true);
73166
73077
  }
73078
+ configs.drawingCanvas.style.cursor =
73079
+ configs.gui_states.viewConfig.defaultPaintCursor;
73167
73080
  };
73168
73081
  const updateGuiSphereState = () => {
73169
73082
  var _a, _b, _c, _d;
@@ -73219,7 +73132,6 @@ function setupGui(configs) {
73219
73132
  min: 5,
73220
73133
  max: 50,
73221
73134
  step: 1,
73222
- onChange: updateGuiBrushAndEraserSize,
73223
73135
  },
73224
73136
  eraser: {
73225
73137
  name: "Eraser",
@@ -74948,18 +74860,11 @@ class DrawingTool extends BaseTool {
74948
74860
  this.drawingLines = [];
74949
74861
  this.isPainting = true;
74950
74862
  this.ctx.protectedData.isDrawing = true;
74951
- // Set cursor based on mode
74952
- if (this.ctx.gui_states.mode.eraser) {
74953
- const urls = this.callbacks.getEraserUrls();
74954
- this.ctx.protectedData.canvases.drawingCanvas.style.cursor =
74955
- urls.length > 0
74956
- ? switchEraserSize(this.ctx.gui_states.drawing.brushAndEraserSize, urls)
74957
- : switchEraserSize(this.ctx.gui_states.drawing.brushAndEraserSize);
74958
- }
74959
- else {
74960
- this.ctx.protectedData.canvases.drawingCanvas.style.cursor =
74961
- this.ctx.gui_states.viewConfig.defaultPaintCursor;
74962
- }
74863
+ // Brush and eraser both show their size via the dashed/solid preview ring
74864
+ // rendered in DrawToolCore.start(); the canvas keeps the default paint
74865
+ // cursor underneath. (The eraser previously swapped in a sized PNG cursor.)
74866
+ this.ctx.protectedData.canvases.drawingCanvas.style.cursor =
74867
+ this.ctx.gui_states.viewConfig.defaultPaintCursor;
74963
74868
  // Record draw start position
74964
74869
  this.ctx.nrrd_states.interaction.drawStartPos.x = e.offsetX;
74965
74870
  this.ctx.nrrd_states.interaction.drawStartPos.y = e.offsetY;
@@ -75035,9 +74940,15 @@ class DrawingTool extends BaseTool {
75035
74940
  this.refreshLayerFromVolume();
75036
74941
  }
75037
74942
  else {
75038
- // Brush mode: voxels already written during mousemove
75039
- // Just re-render final state and composite
74943
+ // Brush mode: voxels already written directly into the MaskVolume
74944
+ // during mousemove, so no canvas→voxel bake (syncLayerSliceData) is
74945
+ // needed. But we MUST still notify the backend — unlike pencil/eraser
74946
+ // (which go through syncLayerSliceData) the brush path never fires
74947
+ // onMaskChanged, so brush strokes were silently never persisted via
74948
+ // useReplaceMask. Re-render the final state, then fire onMaskChanged
74949
+ // for the current slice (same pattern as SphereBrushTool.refreshDisplay).
75040
74950
  this.refreshLayerFromVolume();
74951
+ this.notifyMaskChanged();
75041
74952
  }
75042
74953
  }
75043
74954
  else {
@@ -75091,20 +75002,37 @@ class DrawingTool extends BaseTool {
75091
75002
  return handler;
75092
75003
  }
75093
75004
  /**
75094
- * Render brush circle preview on the drawing context.
75005
+ * Render the size-preview ring for the brush / eraser on the drawing context.
75006
+ *
75007
+ * - Brush: solid outline in the current brush colour.
75008
+ * - Eraser: red dashed outline (matches the sphereEraser preview), so the
75009
+ * eraser no longer needs a sized PNG cursor — the ring tracks the live
75010
+ * `brushAndEraserSize` and is what the user resizes with Shift+wheel.
75011
+ *
75012
+ * Only the pencil has no preview ring (its stroke width is fixed).
75095
75013
  */
75096
- renderBrushPreview(ctx, width, height) {
75014
+ renderBrushPreview(ctx, _width, _height) {
75097
75015
  if (this.ctx.gui_states.mode.pencil ||
75098
- this.ctx.gui_states.mode.eraser ||
75099
75016
  !this.ctx.nrrd_states.interaction.mouseOver) {
75100
75017
  return;
75101
75018
  }
75102
- ctx.clearRect(0, 0, width, height);
75103
- ctx.fillStyle = this.ctx.gui_states.drawing.brushColor;
75019
+ const isEraser = this.ctx.gui_states.mode.eraser;
75020
+ // No clearRect here: DrawToolCore.start() clears drawingCtx once per frame
75021
+ // before compositing, and this ring is now drawn on top of the mask so it
75022
+ // stays visible during an active stroke (e.g. while Shift+wheel resizing).
75023
+ ctx.save();
75104
75024
  ctx.beginPath();
75105
75025
  ctx.arc(this.ctx.nrrd_states.interaction.mouseOverX, this.ctx.nrrd_states.interaction.mouseOverY, this.ctx.gui_states.drawing.brushAndEraserSize / 2 + 1, 0, Math.PI * 2);
75106
- ctx.strokeStyle = this.ctx.gui_states.drawing.brushColor;
75026
+ if (isEraser) {
75027
+ ctx.strokeStyle = "#ff4444";
75028
+ ctx.lineWidth = 2;
75029
+ ctx.setLineDash([4, 4]);
75030
+ }
75031
+ else {
75032
+ ctx.strokeStyle = this.ctx.gui_states.drawing.brushColor;
75033
+ }
75107
75034
  ctx.stroke();
75035
+ ctx.restore();
75108
75036
  }
75109
75037
  // ── Brush mode: direct voxel write ────────────────────────────
75110
75038
  /**
@@ -75295,6 +75223,30 @@ class DrawingTool extends BaseTool {
75295
75223
  this.callbacks.compositeAllLayers();
75296
75224
  this.ctx.protectedData.mainPreSlices.mesh.material.map.needsUpdate = true;
75297
75225
  }
75226
+ /**
75227
+ * Notify the backend of the brush stroke by firing onMaskChanged for the
75228
+ * current slice. Brush writes voxels straight into the MaskVolume (no canvas
75229
+ * bake), so we read the authoritative slice back from the volume — mirrors
75230
+ * SphereBrushTool.refreshDisplay's notify path. Brush only ever touches the
75231
+ * current view slice (canvasToVoxel3D fixes the slice axis), so a single
75232
+ * slice notification is sufficient.
75233
+ */
75234
+ notifyMaskChanged() {
75235
+ if (this.ctx.nrrd_states.flags.loadingMaskData)
75236
+ return;
75237
+ try {
75238
+ const layer = this.ctx.gui_states.layerChannel.layer;
75239
+ const vol = this.callbacks.getVolumeForLayer(layer);
75240
+ const channel = this.ctx.gui_states.layerChannel.activeChannel || 1;
75241
+ const axis = this.ctx.protectedData.axis;
75242
+ const sliceIndex = this.ctx.nrrd_states.view.currentSliceIndex;
75243
+ const { data: sliceData, width, height } = vol.getSliceUint8(sliceIndex, axis);
75244
+ this.ctx.callbacks.onMaskChanged(sliceData, layer, channel, sliceIndex, axis, width, height, false);
75245
+ }
75246
+ catch (_a) {
75247
+ // Volume not ready — skip notification
75248
+ }
75249
+ }
75298
75250
  // ── Private helpers ────────────────────────────────────────
75299
75251
  /** Capture pre-draw slice snapshot for undo */
75300
75252
  capturePreDrawSnapshot() {
@@ -76221,7 +76173,6 @@ class DrawToolCore {
76221
76173
  handleOnContrastMouseUp: (ev) => { },
76222
76174
  handleOnContrastMouseLeave: (ev) => { },
76223
76175
  };
76224
- this.eraserUrls = [];
76225
76176
  this.pencilUrls = [];
76226
76177
  this.undoManager = new UndoManager();
76227
76178
  /** Slice index recorded when paintOnCanvas() starts, guards stale-click */
@@ -76296,7 +76247,6 @@ class DrawToolCore {
76296
76247
  filterDrawedImage: (axis, index) => this.renderer.filterDrawedImage(axis, index),
76297
76248
  getVolumeForLayer: (layer) => this.renderer.getVolumeForLayer(layer),
76298
76249
  pushUndoDelta: (delta) => this.undoManager.push(delta),
76299
- getEraserUrls: () => this.eraserUrls,
76300
76250
  renderSliceToCanvas: (layer, axis, sliceIndex, buffer, ctx, w, h) => this.renderer.renderSliceToCanvas(layer, axis, sliceIndex, buffer, ctx, w, h),
76301
76251
  getOrCreateSliceBuffer: (axis) => this.renderer.getOrCreateSliceBuffer(axis),
76302
76252
  });
@@ -76308,7 +76258,6 @@ class DrawToolCore {
76308
76258
  getOrCreateSliceBuffer: (axis) => this.renderer.getOrCreateSliceBuffer(axis),
76309
76259
  setEmptyCanvasSize: (axis) => this.setEmptyCanvasSize(axis),
76310
76260
  reloadMasksFromVolume: () => this.reloadMasksFromVolume(),
76311
- getEraserUrls: () => this.eraserUrls,
76312
76261
  });
76313
76262
  }
76314
76263
  initDrawToolCore() {
@@ -76463,6 +76412,12 @@ class DrawToolCore {
76463
76412
  });
76464
76413
  // Register wheel handler with EventRouter
76465
76414
  this.eventRouter.setWheelHandler((e) => {
76415
+ // Shift + wheel resizes the brush/eraser (like sphereBrush/sphereEraser
76416
+ // radius), independent of whether the left button is held. Consumes the
76417
+ // event so it never falls through to zoom/slice. Pencil is excluded even
76418
+ // though it also uses Shift — see tryBrushEraserResizeWheel.
76419
+ if (this.tryBrushEraserResizeWheel(e))
76420
+ return;
76466
76421
  if (this.activeWheelMode === 'zoom') {
76467
76422
  this.drawingPrameters.handleMouseZoomSliceWheel(e);
76468
76423
  }
@@ -76476,8 +76431,47 @@ class DrawToolCore {
76476
76431
  // Bind all event listeners
76477
76432
  this.eventRouter.bindAll();
76478
76433
  }
76479
- setEraserUrls(urls) {
76480
- this.eraserUrls = urls;
76434
+ /**
76435
+ * Shift + mouse-wheel resize for the brush and eraser tools.
76436
+ *
76437
+ * Mirrors the sphereBrush/sphereEraser "scroll to change radius" UX so the
76438
+ * user no longer has to reach for the B&E Size slider mid-annotation. Active
76439
+ * whenever the EventRouter is in `draw` mode (i.e. Shift is held — with or
76440
+ * without the left button down) and the current tool is brush or eraser.
76441
+ *
76442
+ * Pencil is deliberately excluded: it also draws with Shift, but its stroke
76443
+ * width is fixed and unrelated to `brushAndEraserSize`. Sphere-family tools
76444
+ * keep their own wheel-radius handler (`activeWheelMode === 'sphereBrush'`).
76445
+ *
76446
+ * `gui_states.mode` is the source of truth for the active tool here, not
76447
+ * `eventRouter.getGuiTool()` — the latter is only updated for sphere tools.
76448
+ *
76449
+ * @returns true if the event was handled (and should not fall through to
76450
+ * the zoom/slice/sphere wheel handlers).
76451
+ */
76452
+ tryBrushEraserResizeWheel(e) {
76453
+ // `draw` mode means Shift is held and neither crosshair nor contrast is
76454
+ // active (EventRouter only enters it for drawing tools under those rules),
76455
+ // so it doubles as the "Shift held, not blocked" gate.
76456
+ if (this.eventRouter.getMode() !== 'draw')
76457
+ return false;
76458
+ const mode = this.state.gui_states.mode;
76459
+ // Brush = no flag set; eraser = mode.eraser. Exclude pencil and all
76460
+ // sphere-family tools.
76461
+ if (mode.pencil || mode.sphere || mode.sphereBrush || mode.sphereEraser) {
76462
+ return false;
76463
+ }
76464
+ e.preventDefault();
76465
+ const drawing = this.state.gui_states.drawing;
76466
+ // Step/bounds match the B&E Size slider config (min 5, max 50, step 1).
76467
+ const next = Math.max(5, Math.min(50, drawing.brushAndEraserSize + (e.deltaY < 0 ? 1 : -1)));
76468
+ if (next === drawing.brushAndEraserSize)
76469
+ return true;
76470
+ drawing.brushAndEraserSize = next;
76471
+ // Both brush and eraser draw a size-preview ring in start() that reads
76472
+ // brushAndEraserSize live, so the new size shows up on the next frame with
76473
+ // no extra work here.
76474
+ return true;
76481
76475
  }
76482
76476
  setPencilIconUrls(urls) {
76483
76477
  this.pencilUrls = urls;
@@ -76646,6 +76640,7 @@ class DrawToolCore {
76646
76640
  this.state.protectedData.ctxes.drawingCtx.clearRect(0, 0, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
76647
76641
  this.state.protectedData.ctxes.drawingCtx.globalAlpha =
76648
76642
  this.state.gui_states.drawing.globalAlpha;
76643
+ const currentMode = this.eventRouter.getMode();
76649
76644
  if (this.state.protectedData.isDrawing) {
76650
76645
  this.state.protectedData.ctxes.drawingLayerMasterCtx.lineCap = "round";
76651
76646
  this.state.protectedData.ctxes.drawingLayerMasterCtx.globalAlpha = 1;
@@ -76654,16 +76649,18 @@ class DrawToolCore {
76654
76649
  target.ctx.globalAlpha = 1;
76655
76650
  }
76656
76651
  }
76657
- else {
76658
- const currentMode = this.eventRouter.getMode();
76659
- if (currentMode === 'draw') {
76660
- this.drawingTool.renderBrushPreview(this.state.protectedData.ctxes.drawingCtx, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
76661
- }
76662
- else if (currentMode === 'crosshair' || this.eventRouter.isCrosshairEnabled()) {
76663
- this.crosshairTool.renderCrosshair(this.state.protectedData.ctxes.drawingCtx, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
76664
- }
76652
+ else if (currentMode === 'crosshair' || this.eventRouter.isCrosshairEnabled()) {
76653
+ this.crosshairTool.renderCrosshair(this.state.protectedData.ctxes.drawingCtx, this.state.nrrd_states.view.changedWidth, this.state.nrrd_states.view.changedHeight);
76665
76654
  }
76666
76655
  this.state.protectedData.ctxes.drawingCtx.drawImage(this.state.protectedData.canvases.drawingCanvasLayerMaster, 0, 0);
76656
+ // Brush/eraser size ring is drawn AFTER the mask composite so it stays
76657
+ // visible during an active stroke (not just on hover) — e.g. while the
76658
+ // user resizes with Shift+wheel mid-paint. Gated on draw mode so it only
76659
+ // shows for brush/eraser with Shift held (renderBrushPreview itself
76660
+ // skips pencil and the no-hover case).
76661
+ if (currentMode === 'draw') {
76662
+ this.drawingTool.renderBrushPreview(this.state.protectedData.ctxes.drawingCtx);
76663
+ }
76667
76664
  if (this.state.gui_states.mode.sphere
76668
76665
  || this.state.gui_states.mode.sphereBrush
76669
76666
  || this.state.gui_states.mode.sphereEraser) {
@@ -78375,7 +78372,6 @@ class NrrdTools {
78375
78372
  nrrd_states: this.state.nrrd_states,
78376
78373
  drawingCanvas: this.state.protectedData.canvases.drawingCanvas,
78377
78374
  drawingPrameters: this.drawCore.drawingPrameters,
78378
- eraserUrls: this.drawCore.eraserUrls,
78379
78375
  pencilUrls: this.drawCore.pencilUrls,
78380
78376
  mainPreSlices: this.state.protectedData.mainPreSlices,
78381
78377
  protectedData: this.state.protectedData,
@@ -78408,7 +78404,6 @@ class NrrdTools {
78408
78404
  this.guiCallbacks = {
78409
78405
  updatePencilState: this.guiParameterSettings.pencil.onChange,
78410
78406
  updateEraserState: this.guiParameterSettings.eraser.onChange,
78411
- updateBrushAndEraserSize: this.guiParameterSettings.brushAndEraserSize.onChange,
78412
78407
  updateSphereState: this.guiParameterSettings.sphere.onChange,
78413
78408
  updateCalDistance: this.guiParameterSettings.activeSphereType.onChange,
78414
78409
  updateWindowHigh: this.guiParameterSettings.windowHigh.onChange,
@@ -78521,9 +78516,9 @@ class NrrdTools {
78521
78516
  return this.state.gui_states.drawing.globalAlpha;
78522
78517
  }
78523
78518
  setBrushSize(size) {
78524
- var _a;
78519
+ // No cursor side-effect needed: the brush/eraser preview ring reads this
78520
+ // value live each frame (see DrawingTool.renderBrushPreview).
78525
78521
  this.state.gui_states.drawing.brushAndEraserSize = Math.max(5, Math.min(50, size));
78526
- (_a = this.guiCallbacks) === null || _a === void 0 ? void 0 : _a.updateBrushAndEraserSize();
78527
78522
  }
78528
78523
  getBrushSize() {
78529
78524
  return this.state.gui_states.drawing.brushAndEraserSize;
@@ -78758,9 +78753,6 @@ class NrrdTools {
78758
78753
  enableContrastDragEvents(callback) {
78759
78754
  this.drawCore.setupConrastEvents(callback);
78760
78755
  }
78761
- setEraserUrls(urls) {
78762
- this.drawCore.setEraserUrls(urls);
78763
- }
78764
78756
  setPencilIconUrls(urls) {
78765
78757
  this.drawCore.setPencilIconUrls(urls);
78766
78758
  }
@@ -79450,7 +79442,7 @@ function evaluateElement(element, weights) {
79450
79442
  }
79451
79443
 
79452
79444
  // import * as kiwrious from "copper3d_plugin_heart_k";
79453
- const REVISION = "v3.4.5-beta";
79445
+ const REVISION = "v3.4.7-beta";
79454
79446
  console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
79455
79447
 
79456
79448
  export { CHANNEL_COLORS, CHANNEL_HEX_COLORS, CameraViewPoint, Copper3dTrackballControls, GaussianSmoother, MeshNodeTool, NrrdTools, REVISION, addBoxHelper, addLabelToScene, configKiwriousHeart, convert3DPostoScreenPos, convertScreenPosto3DPos, copperMScene, copperMSceneRenderer, copperRenderer, copperRendererOnDemond, copperScene, copperSceneOnDemond, createTexture2D_NRRD, fullScreenListenner, kiwrious, loading, removeGuiFolderChilden, rgbaToCss, rgbaToHex, setHDRFilePath, throttle };