cloudmr-ux 4.6.2 → 4.6.5

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.
@@ -186,7 +186,7 @@ export function CloudMrNiivuePanel(props) {
186
186
  left: 0,
187
187
  width: "100%",
188
188
  height: "100%"
189
- } }), props.shapeDraft && (_jsx(ShapeDraftOverlay, { nv: props.nv, draft: props.shapeDraft, onDraftChange: props.onShapeDraftChange, onApplyDraft: props.onApplyShapeDraft, overlayKey: props.mms })), props.penDraft && (_jsx(PenDraftOverlay, { nv: props.nv, draft: props.penDraft, onDraftChange: props.onPenDraftChange, onApplyDraft: props.onApplyPenDraft, overlayKey: props.mms }))] }))] })), _jsxs(Box, __assign({ sx: {
189
+ } }), props.shapeDraft && (_jsx(ShapeDraftOverlay, { nv: props.nv, draft: props.shapeDraft, onDraftChange: props.onShapeDraftChange, overlayKey: props.mms })), props.penDraft && (_jsx(PenDraftOverlay, { nv: props.nv, draft: props.penDraft, onDraftChange: props.onPenDraftChange, overlayKey: props.mms }))] }))] })), _jsxs(Box, __assign({ sx: {
190
190
  width: {
191
191
  xs: "100%",
192
192
  md: "35%"
@@ -586,6 +586,7 @@ export default function CloudMrNiivueViewer(props) {
586
586
  setPenDraft(null);
587
587
  penDraftRef.current = null;
588
588
  nv._cloudMrPenDraftActive = false;
589
+ clearActivePenDraftRef();
589
590
  setPolylineVertexCount(0);
590
591
  }
591
592
  if (shapeDraftRef.current) {
@@ -610,6 +611,7 @@ export default function CloudMrNiivueViewer(props) {
610
611
  setPenDraft(null);
611
612
  penDraftRef.current = null;
612
613
  nv._cloudMrPenDraftActive = false;
614
+ clearActivePenDraftRef();
613
615
  }
614
616
  setDrawShapeTool(null);
615
617
  nv.opts.penType = NI_PEN_TYPE.PEN;
@@ -641,6 +643,7 @@ export default function CloudMrNiivueViewer(props) {
641
643
  setPenDraft(null);
642
644
  penDraftRef.current = null;
643
645
  nv._cloudMrPenDraftActive = false;
646
+ clearActivePenDraftRef();
644
647
  }
645
648
  nvUpdateDrawPen({ target: { value: 8 } });
646
649
  nvSetDrawingEnabled(true);
@@ -832,12 +835,16 @@ export default function CloudMrNiivueViewer(props) {
832
835
  (_a = nv.cloudMrCancelPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
833
836
  }
834
837
  }
835
- function syncActiveDraftRefs() {
838
+ function syncActiveShapeDraftRef() {
836
839
  nv._cloudMrActiveShapeDraft = shapeDraftRef.current;
837
- nv._cloudMrActivePenDraft = penDraftRef.current;
838
840
  }
839
- function clearActiveDraftRefs() {
841
+ function clearActiveShapeDraftRef() {
840
842
  nv._cloudMrActiveShapeDraft = null;
843
+ }
844
+ function syncActivePenDraftRef() {
845
+ nv._cloudMrActivePenDraft = penDraftRef.current;
846
+ }
847
+ function clearActivePenDraftRef() {
841
848
  nv._cloudMrActivePenDraft = null;
842
849
  }
843
850
  function cancelPenDraftHandler() {
@@ -853,7 +860,7 @@ export default function CloudMrNiivueViewer(props) {
853
860
  setPenDraft(null);
854
861
  penDraftRef.current = null;
855
862
  nv._cloudMrPenDraftActive = false;
856
- clearActiveDraftRefs();
863
+ clearActivePenDraftRef();
857
864
  if (drawShapeToolRef.current === "pen") {
858
865
  nvSetDrawingEnabled(true);
859
866
  }
@@ -871,7 +878,7 @@ export default function CloudMrNiivueViewer(props) {
871
878
  setPenDraft(null);
872
879
  penDraftRef.current = null;
873
880
  nv._cloudMrPenDraftActive = false;
874
- clearActiveDraftRefs();
881
+ clearActivePenDraftRef();
875
882
  setDrawingChanged(true);
876
883
  if (drawShapeToolRef.current === "pen") {
877
884
  nvSetDrawingEnabled(true);
@@ -888,7 +895,7 @@ export default function CloudMrNiivueViewer(props) {
888
895
  function onPenDraftChange(draft) {
889
896
  setPenDraft(draft);
890
897
  penDraftRef.current = draft;
891
- syncActiveDraftRefs();
898
+ syncActivePenDraftRef();
892
899
  if (draft.kind === "polyline") {
893
900
  syncPolylineDraftToNv(nv, draft);
894
901
  }
@@ -900,7 +907,7 @@ export default function CloudMrNiivueViewer(props) {
900
907
  setPenDraft(draft);
901
908
  penDraftRef.current = draft;
902
909
  nv._cloudMrPenDraftActive = true;
903
- syncActiveDraftRefs();
910
+ syncActivePenDraftRef();
904
911
  nvSetDrawingEnabled(false);
905
912
  };
906
913
  nv.onPolylineChange = function (count) {
@@ -915,13 +922,13 @@ export default function CloudMrNiivueViewer(props) {
915
922
  if (draft) {
916
923
  setPenDraft(draft);
917
924
  penDraftRef.current = draft;
918
- syncActiveDraftRefs();
925
+ syncActivePenDraftRef();
919
926
  }
920
927
  }
921
928
  else if (((_b = penDraftRef.current) === null || _b === void 0 ? void 0 : _b.kind) === "polyline") {
922
929
  setPenDraft(null);
923
930
  penDraftRef.current = null;
924
- syncActiveDraftRefs();
931
+ clearActivePenDraftRef();
925
932
  }
926
933
  };
927
934
  function cancelShapeDraft() {
@@ -934,7 +941,7 @@ export default function CloudMrNiivueViewer(props) {
934
941
  setShapeDraft(null);
935
942
  shapeDraftRef.current = null;
936
943
  nv._cloudMrShapeDraftActive = false;
937
- clearActiveDraftRefs();
944
+ clearActiveShapeDraftRef();
938
945
  if (drawShapeToolRef.current) {
939
946
  nvSetDrawingEnabled(true);
940
947
  }
@@ -947,7 +954,7 @@ export default function CloudMrNiivueViewer(props) {
947
954
  setShapeDraft(null);
948
955
  shapeDraftRef.current = null;
949
956
  nv._cloudMrShapeDraftActive = false;
950
- clearActiveDraftRefs();
957
+ clearActiveShapeDraftRef();
951
958
  if (drawShapeToolRef.current) {
952
959
  nvSetDrawingEnabled(true);
953
960
  }
@@ -956,13 +963,13 @@ export default function CloudMrNiivueViewer(props) {
956
963
  function onShapeDraftChange(draft) {
957
964
  setShapeDraft(draft);
958
965
  shapeDraftRef.current = draft;
959
- syncActiveDraftRefs();
966
+ syncActiveShapeDraftRef();
960
967
  }
961
968
  nv.onShapeDraftReady = function (draft) {
962
969
  setShapeDraft(draft);
963
970
  shapeDraftRef.current = draft;
964
971
  nv._cloudMrShapeDraftActive = true;
965
- syncActiveDraftRefs();
972
+ syncActiveShapeDraftRef();
966
973
  nvSetDrawingEnabled(false);
967
974
  };
968
975
  nv.onApplyActiveDraft = function () {
@@ -23,9 +23,6 @@ import {
23
23
  } from "./polylinePenUtils.js";
24
24
  import {
25
25
  captureFreehandDraft,
26
- capturePenDraftFromClick,
27
- isPenDrawToolActive,
28
- redrawFreehandDraft,
29
26
  shouldDeferFreehandCommit,
30
27
  } from "./penDraftUtils.js";
31
28
 
@@ -1476,89 +1473,133 @@ function cloudMrHasApplyableDraft(nv) {
1476
1473
  return !!(nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive);
1477
1474
  }
1478
1475
 
1479
- function getActiveDraft(nv) {
1480
- if (nv._cloudMrPenDraftActive && nv._cloudMrActivePenDraft) {
1481
- return nv._cloudMrActivePenDraft;
1482
- }
1483
- if (nv._cloudMrShapeDraftActive && nv._cloudMrActiveShapeDraft) {
1484
- return nv._cloudMrActiveShapeDraft;
1485
- }
1486
- return null;
1476
+ function isClickOnActiveShapeDraft(nv) {
1477
+ const draft = nv._cloudMrActiveShapeDraft;
1478
+ if (!draft || !nv._cloudMrShapeDraftActive) return false;
1479
+ const vox = voxFromMouse(nv);
1480
+ if (!vox) return false;
1481
+ return isVoxelPartOfDraft(nv, draft, vox);
1487
1482
  }
1488
1483
 
1489
- function isClickOnActiveDraft(nv) {
1490
- const draft = getActiveDraft(nv);
1491
- if (!draft) return false;
1484
+ function isClickOnActivePenDraft(nv) {
1485
+ const draft = nv._cloudMrActivePenDraft;
1486
+ if (!draft || !nv._cloudMrPenDraftActive) return false;
1492
1487
  const vox = voxFromMouse(nv);
1493
1488
  if (!vox) return false;
1494
1489
  return isVoxelPartOfDraft(nv, draft, vox);
1495
1490
  }
1496
1491
 
1497
- /** Commit the active draft when the user clicks outside the ROI being edited. */
1498
- function cloudMrTryApplyDraftOnClickAway(nv) {
1499
- if (!cloudMrHasApplyableDraft(nv) || !isClickWithoutDrag(nv.uiData)) {
1492
+ function voxelLabelAt(nv, vox) {
1493
+ const dims = nv.back?.dims;
1494
+ if (!dims || !nv.drawBitmap || !vox) return 0;
1495
+ const dx = dims[1];
1496
+ const dy = dims[2];
1497
+ const idx = vox[0] + vox[1] * dx + vox[2] * dx * dy;
1498
+ return nv.drawBitmap[idx] || 0;
1499
+ }
1500
+
1501
+ function canReopenShapeDraftOnClick(nv) {
1502
+ const penType = nv.opts.penType;
1503
+ return (
1504
+ nv.opts.deferShapeCommit &&
1505
+ nv.opts.drawingEnabled &&
1506
+ !nv._cloudMrShapeDraftActive &&
1507
+ !nv._cloudMrPenDraftActive &&
1508
+ (penType === NI_PEN_TYPE.RECTANGLE || penType === NI_PEN_TYPE.ELLIPSE)
1509
+ );
1510
+ }
1511
+
1512
+ function cloudMrOpenShapeDraftFromClick(nv) {
1513
+ const reopenDraft = captureShapeDraftFromClick(nv);
1514
+ if (!reopenDraft) return false;
1515
+ redrawDraftShape(nv, reopenDraft);
1516
+ nv._cloudMrSuppressDrawingChangedMouseUp = true;
1517
+ if (typeof nv.onShapeDraftReady === "function") {
1518
+ nv.onShapeDraftReady(reopenDraft);
1519
+ }
1520
+ return true;
1521
+ }
1522
+
1523
+ /**
1524
+ * Re-enter rectangle/ellipse edit mode when clicking an existing shape ROI.
1525
+ */
1526
+ function cloudMrTryOpenShapeDraftOnMouseDown(nv) {
1527
+ if (nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) {
1500
1528
  return false;
1501
1529
  }
1502
- if (isClickOnActiveDraft(nv)) {
1530
+ if (!canReopenShapeDraftOnClick(nv)) {
1503
1531
  return false;
1504
1532
  }
1505
- if (typeof nv.onApplyActiveDraft === "function") {
1506
- nv._cloudMrSuppressDrawingChangedMouseUp = true;
1507
- nv.onApplyActiveDraft();
1533
+ const vox = voxFromMouse(nv);
1534
+ if (!vox || !voxelLabelAt(nv, vox)) {
1535
+ return false;
1508
1536
  }
1509
- return true;
1537
+ return cloudMrOpenShapeDraftFromClick(nv);
1510
1538
  }
1511
1539
 
1512
1540
  /**
1513
- * When no draft is active and the user clicks an existing ROI, reconstruct a
1514
- * shape or pen draft so the bounding-box overlay reappears for re-editing.
1541
+ * Click-away exits shape edit mode; clicking another shape selects it for editing.
1542
+ * Disconnected same-color shapes are distinguished by flood-fill from the click point.
1515
1543
  */
1516
- function cloudMrTryReopenDraftOnClick(nv) {
1517
- if (
1518
- nv._cloudMrShapeDraftActive ||
1519
- nv._cloudMrPenDraftActive ||
1520
- !isClickWithoutDrag(nv.uiData)
1521
- ) {
1544
+ function cloudMrHandleShapeDraftClickOnMouseDown(nv) {
1545
+ if (!nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) {
1546
+ return false;
1547
+ }
1548
+ if (!nv.opts.deferShapeCommit || !nv.opts.drawingEnabled) {
1522
1549
  return false;
1523
1550
  }
1524
1551
 
1525
- const penType = nv.opts.penType;
1526
- const isShapeTool =
1527
- nv.opts.deferShapeCommit &&
1528
- nv.opts.drawingEnabled &&
1529
- (penType === NI_PEN_TYPE.RECTANGLE || penType === NI_PEN_TYPE.ELLIPSE);
1530
- const isPenTool = isPenDrawToolActive(nv);
1531
-
1532
- if (!isShapeTool && !isPenTool) {
1552
+ const vox = voxFromMouse(nv);
1553
+ if (!vox) {
1554
+ return false;
1555
+ }
1556
+ if (isClickOnActiveShapeDraft(nv)) {
1533
1557
  return false;
1534
1558
  }
1535
1559
 
1536
- if (isShapeTool) {
1537
- const reopenDraft = captureShapeDraftFromClick(nv);
1538
- if (!reopenDraft) return false;
1539
- redrawDraftShape(nv, reopenDraft);
1540
- nv._cloudMrSuppressDrawingChangedMouseUp = true;
1541
- if (typeof nv.onShapeDraftReady === "function") {
1542
- nv.onShapeDraftReady(reopenDraft);
1543
- }
1544
- return true;
1560
+ if (typeof nv.onApplyActiveDraft === "function") {
1561
+ nv.onApplyActiveDraft();
1545
1562
  }
1546
1563
 
1547
- const reopenDraft = capturePenDraftFromClick(nv);
1548
- if (!reopenDraft) return false;
1549
- redrawFreehandDraft(nv, reopenDraft);
1550
- nv._cloudMrSuppressDrawingChangedMouseUp = true;
1551
- if (typeof nv.onPenDraftReady === "function") {
1552
- nv.onPenDraftReady(reopenDraft);
1564
+ if (voxelLabelAt(nv, vox) > 0) {
1565
+ cloudMrOpenShapeDraftFromClick(nv);
1553
1566
  }
1554
- if (nv.opts.polylinePenMode) {
1555
- resetPolylineState(nv);
1567
+ return true;
1568
+ }
1569
+
1570
+ /** Click-away applies a pen draft without starting a new stroke. */
1571
+ function cloudMrHandlePenDraftClickOnMouseDown(nv) {
1572
+ if (!nv._cloudMrPenDraftActive) {
1573
+ return false;
1574
+ }
1575
+
1576
+ const vox = voxFromMouse(nv);
1577
+ if (!vox) {
1578
+ return false;
1579
+ }
1580
+ if (isClickOnActivePenDraft(nv)) {
1581
+ return false;
1582
+ }
1583
+
1584
+ if (typeof nv.onApplyActiveDraft === "function") {
1585
+ nv.onApplyActiveDraft();
1556
1586
  }
1557
1587
  return true;
1558
1588
  }
1559
1589
 
1560
1590
  const _mouseDownListener = Niivue.prototype.mouseDownListener;
1561
1591
  Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
1592
+ if (e.button === 0) {
1593
+ if (cloudMrHandleShapeDraftClickOnMouseDown(this)) {
1594
+ return;
1595
+ }
1596
+ if (cloudMrHandlePenDraftClickOnMouseDown(this)) {
1597
+ return;
1598
+ }
1599
+ if (cloudMrTryOpenShapeDraftOnMouseDown(this)) {
1600
+ return;
1601
+ }
1602
+ }
1562
1603
  if (shouldDeferFreehandCommit(this) && this.drawBitmap) {
1563
1604
  this._cloudMrFreehandSessionStartBitmap = this.drawBitmap.slice();
1564
1605
  this._cloudMrFreehandAxCorSag = -1;
@@ -1622,30 +1663,17 @@ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
1622
1663
  _mouseUpListener.call(this);
1623
1664
 
1624
1665
  if (polylineClick) {
1625
- if (cloudMrTryReopenDraftOnClick(this)) return;
1626
1666
  addPolylineVertex(this);
1627
1667
  return;
1628
1668
  }
1629
1669
 
1630
- if (cloudMrHasApplyableDraft(this) && isClickWithoutDrag(this.uiData)) {
1631
- if (cloudMrTryApplyDraftOnClickAway(this)) {
1632
- cloudMrTryReopenDraftOnClick(this);
1633
- return;
1634
- }
1635
- if (isClickOnActiveDraft(this)) {
1636
- return;
1637
- }
1638
- }
1639
-
1640
1670
  if (!pendingDraft?.baseBitmap) {
1641
- cloudMrTryReopenDraftOnClick(this);
1642
1671
  return;
1643
1672
  }
1644
1673
  if (isDraftTooSmall(pendingDraft.ptA, pendingDraft.ptB)) {
1645
1674
  this.drawBitmap.set(pendingDraft.baseBitmap);
1646
1675
  this.refreshDrawing(true, false);
1647
1676
  this.drawScene();
1648
- cloudMrTryReopenDraftOnClick(this);
1649
1677
  return;
1650
1678
  }
1651
1679
  this._cloudMrSuppressDrawingChangedMouseUp = true;
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Adjust handles for polyline (vertex drag) or freehand (move only) drafts.
3
- * @param {{ nv: any, draft: any, onDraftChange: (d: any) => void, onApplyDraft?: () => void, overlayKey?: unknown }} props
3
+ * @param {{ nv: any, draft: any, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
4
4
  */
5
- export function PenDraftOverlay({ nv, draft, onDraftChange, onApplyDraft, overlayKey }: {
5
+ export function PenDraftOverlay({ nv, draft, onDraftChange, overlayKey }: {
6
6
  nv: any;
7
7
  draft: any;
8
8
  onDraftChange: (d: any) => void;
9
- onApplyDraft?: (() => void) | undefined;
10
9
  overlayKey?: unknown;
11
10
  }): import("react/jsx-runtime").JSX.Element | null;
12
11
  export default PenDraftOverlay;
@@ -29,10 +29,10 @@ function cloneFreehandDraft(draft) {
29
29
  }
30
30
  /**
31
31
  * Adjust handles for polyline (vertex drag) or freehand (move only) drafts.
32
- * @param {{ nv: any, draft: any, onDraftChange: (d: any) => void, onApplyDraft?: () => void, overlayKey?: unknown }} props
32
+ * @param {{ nv: any, draft: any, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
33
33
  */
34
34
  export function PenDraftOverlay(_a) {
35
- var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, onApplyDraft = _a.onApplyDraft, overlayKey = _a.overlayKey;
35
+ var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, overlayKey = _a.overlayKey;
36
36
  var dragRef = useRef(null);
37
37
  var draftRef = useRef(draft);
38
38
  draftRef.current = draft;
@@ -110,7 +110,6 @@ export function PenDraftOverlay(_a) {
110
110
  onDraftChange(nextDraft);
111
111
  }, [nv, onDraftChange]);
112
112
  finishDragRef.current = function () {
113
- var hadDrag = dragRef.current != null;
114
113
  dragRef.current = null;
115
114
  if (onPointerMoveRef.current) {
116
115
  window.removeEventListener("pointermove", onPointerMoveRef.current);
@@ -118,10 +117,6 @@ export function PenDraftOverlay(_a) {
118
117
  if (finishDragRef.current) {
119
118
  window.removeEventListener("pointerup", finishDragRef.current);
120
119
  }
121
- // Auto-apply as soon as the user releases the handle after a move/resize
122
- if (hadDrag && onApplyDraft) {
123
- onApplyDraft();
124
- }
125
120
  };
126
121
  onPointerMoveRef.current = function (event) {
127
122
  var drag = dragRef.current;
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Overlay handles for adjusting a rectangle/ellipse draft before commit.
3
- * @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, onApplyDraft?: () => void, overlayKey?: unknown }} props
3
+ * @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
4
4
  */
5
- export function ShapeDraftOverlay({ nv, draft, onDraftChange, onApplyDraft, overlayKey }: {
5
+ export function ShapeDraftOverlay({ nv, draft, onDraftChange, overlayKey }: {
6
6
  nv: any;
7
7
  draft: import('./shapeDraftUtils').ShapeDraft;
8
8
  onDraftChange: (d: any) => void;
9
- onApplyDraft?: (() => void) | undefined;
10
9
  overlayKey?: unknown;
11
10
  }): import("react/jsx-runtime").JSX.Element | null;
12
11
  export default ShapeDraftOverlay;
@@ -25,11 +25,11 @@ var HANDLE_SIZE = 10;
25
25
  var ACCENT = "#580f8b";
26
26
  /**
27
27
  * Overlay handles for adjusting a rectangle/ellipse draft before commit.
28
- * @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, onApplyDraft?: () => void, overlayKey?: unknown }} props
28
+ * @param {{ nv: any, draft: import('./shapeDraftUtils').ShapeDraft, onDraftChange: (d: any) => void, overlayKey?: unknown }} props
29
29
  */
30
30
  export function ShapeDraftOverlay(_a) {
31
31
  var _b;
32
- var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, onApplyDraft = _a.onApplyDraft, overlayKey = _a.overlayKey;
32
+ var nv = _a.nv, draft = _a.draft, onDraftChange = _a.onDraftChange, overlayKey = _a.overlayKey;
33
33
  var dragRef = useRef(null);
34
34
  var draftRef = useRef(draft);
35
35
  draftRef.current = draft;
@@ -84,7 +84,6 @@ export function ShapeDraftOverlay(_a) {
84
84
  onDraftChange(nextDraft);
85
85
  }, [nv, onDraftChange]);
86
86
  finishDragRef.current = function () {
87
- var hadDrag = dragRef.current != null;
88
87
  dragRef.current = null;
89
88
  if (onPointerMoveRef.current) {
90
89
  window.removeEventListener("pointermove", onPointerMoveRef.current);
@@ -92,10 +91,6 @@ export function ShapeDraftOverlay(_a) {
92
91
  if (finishDragRef.current) {
93
92
  window.removeEventListener("pointerup", finishDragRef.current);
94
93
  }
95
- // Auto-apply as soon as the user releases the handle after a move/resize
96
- if (hadDrag && onApplyDraft) {
97
- onApplyDraft();
98
- }
99
94
  };
100
95
  onPointerMoveRef.current = function (event) {
101
96
  var drag = dragRef.current;
@@ -13,27 +13,6 @@
13
13
  export function isEraserActive(nv: any): any;
14
14
  export function isFreehandPenActive(nv: any): any;
15
15
  export function shouldDeferFreehandCommit(nv: any): any;
16
- export function isPenDrawToolActive(nv: any): any;
17
- /**
18
- * Re-enter pen edit mode when clicking an existing freehand/polyline ROI.
19
- * Reopens as a freehand draft (move via bounding box) since vertex data is
20
- * not stored in the bitmap alone.
21
- */
22
- export function capturePenDraftFromClick(nv: any): {
23
- kind: string;
24
- baseBitmap: Uint8Array;
25
- axCorSag: any;
26
- penValue: number;
27
- strokeVoxels: [number, number, number][];
28
- bounds: {
29
- x1: any;
30
- y1: any;
31
- z1: any;
32
- x2: any;
33
- y2: any;
34
- z2: any;
35
- };
36
- } | null;
37
16
  export function redrawPolylineDraft(nv: any, draft: any): void;
38
17
  export function translatePolylineVertices(vertices: any, delta: any): any;
39
18
  export function updatePolylineVertex(vertices: any, index: any, newVox: any): any;
@@ -18,8 +18,7 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
18
18
  }
19
19
  return to.concat(ar || Array.prototype.slice.call(from));
20
20
  };
21
- import { translatePt, floodFillClusterFromVox, eraseClusterFromBitmap, } from "./shapeDraftUtils";
22
- import { axCorSagFromMouse, voxFromMouse } from "./polylinePenUtils";
21
+ import { translatePt } from "./shapeDraftUtils";
23
22
  import { NI_PEN_TYPE } from "./niivuePenType";
24
23
  /** @typedef {'polyline' | 'freehand'} PenDraftKind */
25
24
  /**
@@ -48,34 +47,6 @@ export function isFreehandPenActive(nv) {
48
47
  export function shouldDeferFreehandCommit(nv) {
49
48
  return !!nv.opts.deferFreehandCommit && isFreehandPenActive(nv);
50
49
  }
51
- export function isPenDrawToolActive(nv) {
52
- return (nv.opts.drawingEnabled &&
53
- nv.opts.penType === NI_PEN_TYPE.PEN &&
54
- nv.opts.penValue > 0 &&
55
- (nv.opts.deferFreehandCommit || nv.opts.polylinePenMode));
56
- }
57
- /**
58
- * Re-enter pen edit mode when clicking an existing freehand/polyline ROI.
59
- * Reopens as a freehand draft (move via bounding box) since vertex data is
60
- * not stored in the bitmap alone.
61
- */
62
- export function capturePenDraftFromClick(nv) {
63
- var seedVox = voxFromMouse(nv);
64
- var cluster = floodFillClusterFromVox(nv, seedVox);
65
- if (!cluster)
66
- return null;
67
- var label = cluster.label, visited = cluster.visited, voxels = cluster.voxels, bounds = cluster.bounds;
68
- var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
69
- var axCorSag = nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : axCorSagFromMouse(nv);
70
- return {
71
- kind: "freehand",
72
- baseBitmap: eraseClusterFromBitmap(nv.drawBitmap, visited),
73
- axCorSag: axCorSag,
74
- penValue: label,
75
- strokeVoxels: voxels,
76
- bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
77
- };
78
- }
79
50
  export function redrawPolylineDraft(nv, draft) {
80
51
  var _a;
81
52
  if (!((_a = draft === null || draft === void 0 ? void 0 : draft.vertices) === null || _a === void 0 ? void 0 : _a.length) || !draft.baseBitmap)
@@ -62,6 +62,8 @@ export function floodFillClusterFromVox(nv: any, seedVox: any): {
62
62
  /** True when a voxel belongs to the live draft overlay (drawn on top of baseBitmap). */
63
63
  export function isVoxelPartOfDraft(nv: any, draft: any, seedVox: any): boolean;
64
64
  export function eraseClusterFromBitmap(bitmap: any, visited: any): Uint8Array;
65
+ /** Guess rectangle vs ellipse from the filled voxel pattern, not the active tool. */
66
+ export function inferShapePenTypeFromCluster(cluster: any, axCorSag: any): 1 | 2;
65
67
  /**
66
68
  * When the user clicks on an existing filled ROI while the rectangle/ellipse tool
67
69
  * is active (but no draft is currently open), flood-fill the clicked cluster to
@@ -74,7 +76,7 @@ export function captureShapeDraftFromClick(nv: any): {
74
76
  ptB: any[];
75
77
  penValue: number;
76
78
  axCorSag: number;
77
- penType: any;
79
+ penType: 1 | 2;
78
80
  baseBitmap: Uint8Array;
79
81
  } | null;
80
82
  export type ShapeDraftKind = 'rectangle' | 'ellipse';
@@ -295,6 +295,65 @@ export function eraseClusterFromBitmap(bitmap, visited) {
295
295
  });
296
296
  return next;
297
297
  }
298
+ function sliceKey(axCorSag, x, y, z) {
299
+ if (axCorSag === 0)
300
+ return "".concat(x, ",").concat(y);
301
+ if (axCorSag === 1)
302
+ return "".concat(x, ",").concat(z);
303
+ return "".concat(y, ",").concat(z);
304
+ }
305
+ /** Guess rectangle vs ellipse from the filled voxel pattern, not the active tool. */
306
+ export function inferShapePenTypeFromCluster(cluster, axCorSag) {
307
+ var bounds = cluster.bounds, voxels = cluster.voxels;
308
+ var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
309
+ var filled = new Set();
310
+ for (var _i = 0, voxels_1 = voxels; _i < voxels_1.length; _i++) {
311
+ var _a = voxels_1[_i], x = _a[0], y = _a[1], z = _a[2];
312
+ filled.add(sliceKey(axCorSag, x, y, z));
313
+ }
314
+ var uMin;
315
+ var uMax;
316
+ var vMin;
317
+ var vMax;
318
+ if (axCorSag === 0) {
319
+ uMin = x1;
320
+ uMax = x2;
321
+ vMin = y1;
322
+ vMax = y2;
323
+ }
324
+ else if (axCorSag === 1) {
325
+ uMin = x1;
326
+ uMax = x2;
327
+ vMin = z1;
328
+ vMax = z2;
329
+ }
330
+ else {
331
+ uMin = y1;
332
+ uMax = y2;
333
+ vMin = z1;
334
+ vMax = z2;
335
+ }
336
+ if (uMax <= uMin || vMax <= vMin) {
337
+ return NI_PEN_TYPE.RECTANGLE;
338
+ }
339
+ var cu = (uMin + uMax) / 2;
340
+ var cv = (vMin + vMax) / 2;
341
+ var ru = Math.max(0.5, (uMax - uMin) / 2);
342
+ var rv = Math.max(0.5, (vMax - vMin) / 2);
343
+ var rectScore = 0;
344
+ var ellipseScore = 0;
345
+ for (var u = uMin; u <= uMax; u++) {
346
+ for (var v = vMin; v <= vMax; v++) {
347
+ var has = filled.has("".concat(u, ",").concat(v));
348
+ var inEllipse = Math.pow(((u - cu) / ru), 2) + Math.pow(((v - cv) / rv), 2) <= 1.05;
349
+ if (has)
350
+ rectScore++;
351
+ if (has === inEllipse)
352
+ ellipseScore++;
353
+ }
354
+ }
355
+ return ellipseScore > rectScore ? NI_PEN_TYPE.ELLIPSE : NI_PEN_TYPE.RECTANGLE;
356
+ }
298
357
  /**
299
358
  * When the user clicks on an existing filled ROI while the rectangle/ellipse tool
300
359
  * is active (but no draft is currently open), flood-fill the clicked cluster to
@@ -311,12 +370,13 @@ export function captureShapeDraftFromClick(nv) {
311
370
  var x1 = bounds.x1, y1 = bounds.y1, z1 = bounds.z1, x2 = bounds.x2, y2 = bounds.y2, z2 = bounds.z2;
312
371
  var baseBitmap = eraseClusterFromBitmap(nv.drawBitmap, visited);
313
372
  var axCorSag = inferAxCorSagFromBounds(x1, y1, z1, x2, y2, z2, nv.drawPenAxCorSag >= 0 ? nv.drawPenAxCorSag : 0);
373
+ var penType = inferShapePenTypeFromCluster(cluster, axCorSag);
314
374
  return {
315
375
  ptA: [x1, y1, z1],
316
376
  ptB: [x2, y2, z2],
317
377
  penValue: label,
318
378
  axCorSag: axCorSag,
319
- penType: nv.opts.penType,
379
+ penType: penType,
320
380
  baseBitmap: baseBitmap
321
381
  };
322
382
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudmr-ux",
3
- "version": "4.6.2",
3
+ "version": "4.6.5",
4
4
  "author": "erosmontin@gmail.com",
5
5
  "license": "MIT",
6
6
  "repository": "erosmontin/cloudmr-ux",