cloudmr-ux 4.7.3 → 4.7.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.
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivuePanel.js +11 -3
- package/dist/CmrComponents/niivue-viewer/CloudMrNiivueViewer.js +99 -33
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +61 -33
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.d.ts +3 -1
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.js +9 -5
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MroDrawToolkit.js +1 -1
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.d.ts +25 -18
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.js +184 -89
- package/dist/CmrComponents/niivue-viewer/shapeDraftUtils.d.ts +4 -1
- package/dist/CmrComponents/niivue-viewer/shapeDraftUtils.js +33 -14
- package/package.json +1 -1
|
@@ -107,7 +107,7 @@ export function CloudMrNiivuePanel(props) {
|
|
|
107
107
|
props.resampleImage();
|
|
108
108
|
}, [histogram]);
|
|
109
109
|
function applyDrawShapeTool(tool) {
|
|
110
|
-
var _a, _b, _c, _d;
|
|
110
|
+
var _a, _b, _c, _d, _e;
|
|
111
111
|
if (props.shapeDraft) {
|
|
112
112
|
// Apply (commit) the current draft rather than discarding it
|
|
113
113
|
(_a = props.onApplyShapeDraftKeepTool) === null || _a === void 0 ? void 0 : _a.call(props);
|
|
@@ -120,9 +120,17 @@ export function CloudMrNiivuePanel(props) {
|
|
|
120
120
|
}
|
|
121
121
|
props.setDrawShapeTool(tool);
|
|
122
122
|
var nv = props.nv;
|
|
123
|
+
var penMode = (_e = props.drawToolkitProps.penDrawMode) !== null && _e !== void 0 ? _e : "freehand";
|
|
123
124
|
nv.opts.deferShapeCommit = tool === "rectangle" || tool === "ellipse";
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
if (tool === "pen") {
|
|
126
|
+
nv.opts.polylinePenMode = penMode === "polyline";
|
|
127
|
+
nv.opts.isFilledPen = penMode === "freehand";
|
|
128
|
+
nv.opts.deferFreehandCommit = false;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
nv.opts.polylinePenMode = false;
|
|
132
|
+
nv.opts.deferFreehandCommit = false;
|
|
133
|
+
}
|
|
126
134
|
nv.opts.penType =
|
|
127
135
|
tool === "rectangle"
|
|
128
136
|
? NI_PEN_TYPE.RECTANGLE
|
|
@@ -63,7 +63,7 @@ import { SettingsPanel } from './SettingsPanel';
|
|
|
63
63
|
import { NumberPicker } from './NumberPicker';
|
|
64
64
|
import { ColorPicker } from './ColorPicker';
|
|
65
65
|
import { LayersPanel } from './LayersPanel';
|
|
66
|
-
import { applyPenDraft, cancelPenDraft, fillPolylineDraft,
|
|
66
|
+
import { applyPenDraft, cancelPenDraft, captureFreehandDraft, fillPolylineDraft, unfillPolylineDraft, polylineDraftFromNv, syncPolylineDraftToNv, registerAppliedPolyline, restoreCommittedPolyline, collectPolylineAppliedVoxelIndices, } from './penDraftUtils';
|
|
67
67
|
import { CloudMrNiivuePanel } from './CloudMrNiivuePanel';
|
|
68
68
|
import { Niivue } from './NiivuePatcher';
|
|
69
69
|
import NVSwitch from './Switch';
|
|
@@ -104,7 +104,7 @@ export var nv = new Niivue({
|
|
|
104
104
|
crosshairColor: [1, 1, 0],
|
|
105
105
|
fontColor: [0.00, 0.94, 0.37, 1],
|
|
106
106
|
isNearestInterpolation: true,
|
|
107
|
-
isFilledPen:
|
|
107
|
+
isFilledPen: true,
|
|
108
108
|
penValue: 1,
|
|
109
109
|
penType: NI_PEN_TYPE.PEN
|
|
110
110
|
});
|
|
@@ -593,9 +593,9 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
593
593
|
}
|
|
594
594
|
}
|
|
595
595
|
else if (drawShapeToolRef.current === "pen") {
|
|
596
|
-
nv.opts.deferFreehandCommit =
|
|
596
|
+
nv.opts.deferFreehandCommit = false;
|
|
597
597
|
nv.opts.polylinePenMode = penDrawModeRef.current === "polyline";
|
|
598
|
-
nv.opts.isFilledPen =
|
|
598
|
+
nv.opts.isFilledPen = penDrawModeRef.current === "freehand";
|
|
599
599
|
}
|
|
600
600
|
}
|
|
601
601
|
function deactivateDrawTools() {
|
|
@@ -821,23 +821,53 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
821
821
|
}
|
|
822
822
|
}
|
|
823
823
|
function syncPenDrawMode(mode) {
|
|
824
|
-
var _a;
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
824
|
+
var _a, _b;
|
|
825
|
+
var prevMode = penDrawModeRef.current;
|
|
826
|
+
// Commit an in-progress freehand before switching to polyline so it stays
|
|
827
|
+
// marked as pen (kind 2) and is not misidentified as a shape on click.
|
|
828
|
+
if (prevMode === "freehand" && mode === "polyline") {
|
|
829
|
+
var draft = penDraftRef.current;
|
|
830
|
+
if ((draft === null || draft === void 0 ? void 0 : draft.kind) === "freehand") {
|
|
831
|
+
applyPenDraft(nv, draft);
|
|
832
|
+
markPenVoxelKind(draft, 2);
|
|
833
|
+
setPenDraft(null);
|
|
834
|
+
penDraftRef.current = null;
|
|
835
|
+
nv._cloudMrPenDraftActive = false;
|
|
836
|
+
}
|
|
837
|
+
else if (nv._cloudMrFreehandSessionStartBitmap) {
|
|
838
|
+
var axCorSag = nv._cloudMrFreehandAxCorSag >= 0 ? nv._cloudMrFreehandAxCorSag : nv.drawPenAxCorSag;
|
|
839
|
+
var captured = captureFreehandDraft(nv, nv._cloudMrFreehandSessionStartBitmap, axCorSag);
|
|
840
|
+
nv._cloudMrFreehandSessionStartBitmap = null;
|
|
841
|
+
if (captured)
|
|
842
|
+
markPenVoxelKind(captured, 2);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
831
845
|
if (mode === "freehand") {
|
|
832
846
|
(_a = nv.cloudMrCancelPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
|
|
847
|
+
if (((_b = penDraftRef.current) === null || _b === void 0 ? void 0 : _b.kind) === "polyline") {
|
|
848
|
+
cancelPenDraft(nv, penDraftRef.current);
|
|
849
|
+
setPenDraft(null);
|
|
850
|
+
penDraftRef.current = null;
|
|
851
|
+
nv._cloudMrPenDraftActive = false;
|
|
852
|
+
setPolylineVertexCount(0);
|
|
853
|
+
}
|
|
833
854
|
}
|
|
855
|
+
setPenDrawMode(mode);
|
|
856
|
+
penDrawModeRef.current = mode;
|
|
857
|
+
nv.opts.polylinePenMode = mode === "polyline";
|
|
858
|
+
nv.opts.isFilledPen = mode === "freehand";
|
|
859
|
+
nv.opts.deferFreehandCommit = false;
|
|
834
860
|
}
|
|
835
861
|
function cancelPenDraftHandler() {
|
|
836
862
|
var _a;
|
|
837
863
|
var draft = penDraftRef.current;
|
|
838
864
|
if (!draft)
|
|
839
865
|
return;
|
|
866
|
+
var registryId = draft._registryId;
|
|
840
867
|
cancelPenDraft(nv, draft);
|
|
868
|
+
if (registryId && draft.kind === "polyline") {
|
|
869
|
+
restoreCommittedPolyline(nv, registryId);
|
|
870
|
+
}
|
|
841
871
|
if (draft.kind === "polyline") {
|
|
842
872
|
(_a = nv.cloudMrResetPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
|
|
843
873
|
setPolylineVertexCount(0);
|
|
@@ -850,22 +880,41 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
850
880
|
}
|
|
851
881
|
}
|
|
852
882
|
function applyPenDraftHandler(_a) {
|
|
853
|
-
var _b;
|
|
854
|
-
var
|
|
883
|
+
var _b, _c, _d;
|
|
884
|
+
var _e = _a === void 0 ? {} : _a, _f = _e.keepTool, keepTool = _f === void 0 ? false : _f;
|
|
855
885
|
var draft = penDraftRef.current;
|
|
856
886
|
if (!draft)
|
|
857
887
|
return;
|
|
858
|
-
|
|
888
|
+
if (draft.kind === "polyline" && ((_b = nv._cloudMrPolylineVertices) === null || _b === void 0 ? void 0 : _b.length) >= 2) {
|
|
889
|
+
var fresh = polylineDraftFromNv(nv, { filled: !!draft.filled });
|
|
890
|
+
if (fresh) {
|
|
891
|
+
draft = fresh;
|
|
892
|
+
penDraftRef.current = draft;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
draft = (_c = applyPenDraft(nv, draft)) !== null && _c !== void 0 ? _c : draft;
|
|
896
|
+
if (draft.kind === "polyline" && nv._cloudMrPolylineSessionStartBitmap) {
|
|
897
|
+
draft = __assign(__assign({}, draft), { baseBitmap: new Uint8Array(nv._cloudMrPolylineSessionStartBitmap) });
|
|
898
|
+
}
|
|
899
|
+
penDraftRef.current = draft;
|
|
859
900
|
markPenVoxelKind(draft, draft.kind === "polyline" ? 3 : 2);
|
|
860
901
|
if (draft.kind === "polyline") {
|
|
861
|
-
(
|
|
902
|
+
registerAppliedPolyline(nv, draft, draft._registryId);
|
|
903
|
+
(_d = nv.cloudMrResetPolyline) === null || _d === void 0 ? void 0 : _d.call(nv);
|
|
862
904
|
setPolylineVertexCount(0);
|
|
863
905
|
}
|
|
864
906
|
setPenDraft(null);
|
|
865
907
|
penDraftRef.current = null;
|
|
866
908
|
nv._cloudMrPenDraftActive = false;
|
|
867
909
|
setDrawingChanged(true);
|
|
868
|
-
if (
|
|
910
|
+
if (keepTool) {
|
|
911
|
+
var mode = penDrawModeRef.current;
|
|
912
|
+
nv.opts.polylinePenMode = mode === "polyline";
|
|
913
|
+
nv.opts.isFilledPen = mode === "freehand";
|
|
914
|
+
nv.opts.deferFreehandCommit = false;
|
|
915
|
+
nvSetDrawingEnabled(true);
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
869
918
|
// Deactivate tool entirely — palette closes, user re-enters edit by clicking the ROI
|
|
870
919
|
setDrawShapeTool(null);
|
|
871
920
|
nv.opts.deferFreehandCommit = false;
|
|
@@ -874,20 +923,14 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
874
923
|
}
|
|
875
924
|
resampleImage();
|
|
876
925
|
}
|
|
877
|
-
function
|
|
926
|
+
function fillPolylineDraftHandler() {
|
|
878
927
|
var draft = penDraftRef.current;
|
|
879
|
-
if (!draft)
|
|
928
|
+
if (!draft || draft.kind !== "polyline" || draft.vertices.length < 3)
|
|
880
929
|
return;
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
}
|
|
886
|
-
else if (draft.kind === "freehand") {
|
|
887
|
-
if (!draft.pathVertices || draft.pathVertices.length < 3)
|
|
888
|
-
return;
|
|
889
|
-
onPenDraftChange(fillFreehandDraft(nv, draft));
|
|
890
|
-
}
|
|
930
|
+
var next = draft.filled
|
|
931
|
+
? unfillPolylineDraft(nv, draft)
|
|
932
|
+
: fillPolylineDraft(nv, draft);
|
|
933
|
+
onPenDraftChange(next);
|
|
891
934
|
}
|
|
892
935
|
function onPenDraftChange(draft) {
|
|
893
936
|
setPenDraft(draft);
|
|
@@ -899,6 +942,17 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
899
942
|
function cancelPolylineDraft() {
|
|
900
943
|
cancelPenDraftHandler();
|
|
901
944
|
}
|
|
945
|
+
nv.onShapeCommitted = function (draft) {
|
|
946
|
+
nv.drawAddUndoBitmap(nv.drawFillOverwrites);
|
|
947
|
+
markShapeVoxelKind(draft);
|
|
948
|
+
setDrawingChanged(true);
|
|
949
|
+
resampleImage();
|
|
950
|
+
};
|
|
951
|
+
nv.onFreehandCommitted = function (draft) {
|
|
952
|
+
markPenVoxelKind(draft, 2);
|
|
953
|
+
setDrawingChanged(true);
|
|
954
|
+
resampleImage();
|
|
955
|
+
};
|
|
902
956
|
nv.onPenDraftReady = function (draft) {
|
|
903
957
|
setPenDraft(draft);
|
|
904
958
|
penDraftRef.current = draft;
|
|
@@ -908,14 +962,18 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
908
962
|
// Called by NiivuePatcher when the user clicks an applied pen ROI to re-edit it.
|
|
909
963
|
// penKind: 2=freehand, 3=polyline
|
|
910
964
|
nv.onPenDraftReopenReady = function (draft, penKind) {
|
|
965
|
+
var _a, _b;
|
|
911
966
|
setPenDraft(draft);
|
|
912
967
|
penDraftRef.current = draft;
|
|
913
968
|
nv._cloudMrPenDraftActive = true;
|
|
914
969
|
// Auto-select pen tool so the palette opens
|
|
915
970
|
setDrawShapeTool("pen");
|
|
916
|
-
var mode =
|
|
971
|
+
var mode = draft.kind === "polyline" ? "polyline" : "freehand";
|
|
917
972
|
setPenDrawMode(mode);
|
|
918
973
|
penDrawModeRef.current = mode;
|
|
974
|
+
if (draft.kind === "polyline") {
|
|
975
|
+
setPolylineVertexCount((_b = (_a = draft.vertices) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0);
|
|
976
|
+
}
|
|
919
977
|
nv.opts.deferFreehandCommit = false;
|
|
920
978
|
nv.opts.polylinePenMode = false;
|
|
921
979
|
nvSetDrawingEnabled(false);
|
|
@@ -959,7 +1017,6 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
959
1017
|
nv._cloudMrPolylineVertices = null;
|
|
960
1018
|
nv._cloudMrPolylineBaseBitmap = null;
|
|
961
1019
|
nv._cloudMrPolylineSessionStartBitmap = null;
|
|
962
|
-
nv._cloudMrFreehandPath = null;
|
|
963
1020
|
nv._cloudMrToolKindBitmap = null;
|
|
964
1021
|
}
|
|
965
1022
|
function clearDrawingHandler() {
|
|
@@ -1019,11 +1076,18 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
1019
1076
|
ensureToolKindBitmap();
|
|
1020
1077
|
var tkb = nv._cloudMrToolKindBitmap;
|
|
1021
1078
|
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
1079
|
+
if (draft.kind === "polyline") {
|
|
1080
|
+
for (var _i = 0, _b = collectPolylineAppliedVoxelIndices(nv, draft); _i < _b.length; _i++) {
|
|
1081
|
+
var idx = _b[_i];
|
|
1082
|
+
tkb[idx] = kind;
|
|
1083
|
+
}
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1022
1086
|
if (draft.kind === "freehand" && draft.strokeVoxels && dims) {
|
|
1023
1087
|
var dx = dims[1];
|
|
1024
1088
|
var dy = dims[2];
|
|
1025
|
-
for (var
|
|
1026
|
-
var
|
|
1089
|
+
for (var _c = 0, _d = draft.strokeVoxels; _c < _d.length; _c++) {
|
|
1090
|
+
var _e = _d[_c], x = _e[0], y = _e[1], z = _e[2];
|
|
1027
1091
|
tkb[x + y * dx + z * dx * dy] = kind;
|
|
1028
1092
|
}
|
|
1029
1093
|
}
|
|
@@ -1628,8 +1692,10 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
1628
1692
|
onCancelPolyline: cancelPolylineDraft,
|
|
1629
1693
|
onApplyPenDraft: applyPenDraftHandler,
|
|
1630
1694
|
onCancelPenDraft: cancelPenDraftHandler,
|
|
1631
|
-
onFillPenDraft:
|
|
1695
|
+
onFillPenDraft: fillPolylineDraftHandler,
|
|
1632
1696
|
penDraftActive: penDraft != null,
|
|
1697
|
+
penDraftKind: penDraft === null || penDraft === void 0 ? void 0 : penDraft.kind,
|
|
1698
|
+
penDraftFilled: (penDraft === null || penDraft === void 0 ? void 0 : penDraft.filled) === true,
|
|
1633
1699
|
brushSize: brushSize,
|
|
1634
1700
|
updateBrushSize: nvUpdateBrushSize,
|
|
1635
1701
|
onActivateEraser: activateEraser,
|
|
@@ -24,8 +24,13 @@ import {
|
|
|
24
24
|
import {
|
|
25
25
|
captureFreehandDraft,
|
|
26
26
|
capturePenDraftFromClick,
|
|
27
|
+
capturePolylineDraftFromClick,
|
|
28
|
+
isFreehandPenActive,
|
|
29
|
+
isRegisteredPolylineClick,
|
|
27
30
|
redrawFreehandDraft,
|
|
31
|
+
redrawPolylineDraft,
|
|
28
32
|
shouldDeferFreehandCommit,
|
|
33
|
+
syncPolylineDraftToNv,
|
|
29
34
|
} from "./penDraftUtils.js";
|
|
30
35
|
|
|
31
36
|
/*
|
|
@@ -533,6 +538,19 @@ Niivue.prototype.drawPenFilled = function () {
|
|
|
533
538
|
return;
|
|
534
539
|
}
|
|
535
540
|
this.drawAddUndoBitmap()
|
|
541
|
+
if (this._cloudMrFreehandSessionStartBitmap && isFreehandPenActive(this)) {
|
|
542
|
+
const axCorSag =
|
|
543
|
+
this.drawPenAxCorSag >= 0 ? this.drawPenAxCorSag : this._cloudMrFreehandAxCorSag;
|
|
544
|
+
const draft = captureFreehandDraft(
|
|
545
|
+
this,
|
|
546
|
+
this._cloudMrFreehandSessionStartBitmap,
|
|
547
|
+
axCorSag,
|
|
548
|
+
);
|
|
549
|
+
this._cloudMrFreehandSessionStartBitmap = null;
|
|
550
|
+
if (draft && typeof this.onFreehandCommitted === "function") {
|
|
551
|
+
this.onFreehandCommitted(draft);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
536
554
|
// Post-processing to hide hidden voxels
|
|
537
555
|
this.hiddenBitmap = new Uint8Array(this.drawBitmap.length);
|
|
538
556
|
for (let i = 0; i < this.drawBitmap.length; i++) {
|
|
@@ -1502,6 +1520,28 @@ function clickedVoxelToolKind(nv) {
|
|
|
1502
1520
|
return bitmap[idx] || 0;
|
|
1503
1521
|
}
|
|
1504
1522
|
|
|
1523
|
+
/**
|
|
1524
|
+
* Re-open an applied ROI for editing based on stored (or inferred) tool kind.
|
|
1525
|
+
* Unmarked pen strokes (kind 0) must try pen reopen before shape inference —
|
|
1526
|
+
* otherwise freehand blobs get misclassified as ellipse/rectangle shapes.
|
|
1527
|
+
*/
|
|
1528
|
+
function cloudMrTryReopenDraftOnClick(nv) {
|
|
1529
|
+
if (nv._cloudMrShapeDraftActive || nv._cloudMrPenDraftActive) return;
|
|
1530
|
+
if (!isClickWithoutDrag(nv.uiData)) return;
|
|
1531
|
+
|
|
1532
|
+
const kind = clickedVoxelToolKind(nv);
|
|
1533
|
+
if (kind === 1) {
|
|
1534
|
+
cloudMrTryReopenShapeDraftOnClick(nv);
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
if (kind === 2 || kind === 3) {
|
|
1538
|
+
cloudMrTryReopenPenDraftOnClick(nv);
|
|
1539
|
+
return;
|
|
1540
|
+
}
|
|
1541
|
+
if (cloudMrTryReopenPenDraftOnClick(nv)) return;
|
|
1542
|
+
cloudMrTryReopenShapeDraftOnClick(nv);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1505
1545
|
/**
|
|
1506
1546
|
* Re-enter rectangle/ellipse edit mode when clicking an existing applied shape ROI.
|
|
1507
1547
|
* Skips voxels that were drawn with the pen tool (those are handled by pen reopen).
|
|
@@ -1513,6 +1553,7 @@ function cloudMrTryReopenShapeDraftOnClick(nv) {
|
|
|
1513
1553
|
// If we know this voxel was drawn with the pen, skip shape reopen
|
|
1514
1554
|
const kind = clickedVoxelToolKind(nv);
|
|
1515
1555
|
if (kind === 2 || kind === 3) return false;
|
|
1556
|
+
if (isRegisteredPolylineClick(nv)) return false;
|
|
1516
1557
|
|
|
1517
1558
|
const reopenDraft = captureShapeDraftFromClick(nv);
|
|
1518
1559
|
if (!reopenDraft) return false;
|
|
@@ -1537,14 +1578,21 @@ function cloudMrTryReopenPenDraftOnClick(nv) {
|
|
|
1537
1578
|
const kind = clickedVoxelToolKind(nv);
|
|
1538
1579
|
if (kind === 1) return false;
|
|
1539
1580
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1581
|
+
let draft = capturePolylineDraftFromClick(nv);
|
|
1582
|
+
let penKind = draft ? 3 : kind === 3 ? 3 : 2;
|
|
1542
1583
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1584
|
+
if (!draft) {
|
|
1585
|
+
draft = capturePenDraftFromClick(nv);
|
|
1586
|
+
if (!draft) return false;
|
|
1587
|
+
if (kind !== 3) penKind = 2;
|
|
1588
|
+
}
|
|
1546
1589
|
|
|
1547
|
-
|
|
1590
|
+
if (draft.kind === "polyline") {
|
|
1591
|
+
redrawPolylineDraft(nv, draft);
|
|
1592
|
+
syncPolylineDraftToNv(nv, draft);
|
|
1593
|
+
} else {
|
|
1594
|
+
redrawFreehandDraft(nv, draft);
|
|
1595
|
+
}
|
|
1548
1596
|
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1549
1597
|
if (typeof nv.onPenDraftReopenReady === "function") {
|
|
1550
1598
|
nv.onPenDraftReopenReady(draft, penKind);
|
|
@@ -1557,21 +1605,16 @@ Niivue.prototype.mouseDownListener = function cloudMrMouseDownListener(e) {
|
|
|
1557
1605
|
if (e.button === RIGHT_MOUSE_BUTTON && cloudMrTryApplyDraftOnRightClick(this, e)) {
|
|
1558
1606
|
return;
|
|
1559
1607
|
}
|
|
1560
|
-
if (
|
|
1608
|
+
if (isFreehandPenActive(this) && this.drawBitmap) {
|
|
1561
1609
|
this._cloudMrFreehandSessionStartBitmap = this.drawBitmap.slice();
|
|
1562
1610
|
this._cloudMrFreehandAxCorSag = -1;
|
|
1563
|
-
this._cloudMrFreehandPath = [];
|
|
1564
1611
|
}
|
|
1565
1612
|
_mouseDownListener.call(this, e);
|
|
1566
|
-
if (
|
|
1613
|
+
if (isFreehandPenActive(this) && this._cloudMrFreehandSessionStartBitmap) {
|
|
1567
1614
|
const axCorSag = axCorSagFromMouse(this);
|
|
1568
1615
|
if (axCorSag >= 0) {
|
|
1569
1616
|
this._cloudMrFreehandAxCorSag = axCorSag;
|
|
1570
1617
|
}
|
|
1571
|
-
const vox = voxFromMouse(this);
|
|
1572
|
-
if (vox && this._cloudMrFreehandPath) {
|
|
1573
|
-
this._cloudMrFreehandPath.push([vox[0], vox[1], vox[2]]);
|
|
1574
|
-
}
|
|
1575
1618
|
}
|
|
1576
1619
|
};
|
|
1577
1620
|
|
|
@@ -1598,19 +1641,6 @@ Niivue.prototype.mouseClick = function cloudMrMouseClick(...args) {
|
|
|
1598
1641
|
const _mouseMoveListener = Niivue.prototype.mouseMoveListener;
|
|
1599
1642
|
Niivue.prototype.mouseMoveListener = function cloudMrMouseMoveListener(event) {
|
|
1600
1643
|
const result = _mouseMoveListener.call(this, event);
|
|
1601
|
-
if (shouldDeferFreehandCommit(this) && this.uiData?.isDragging) {
|
|
1602
|
-
const vox = voxFromMouse(this);
|
|
1603
|
-
if (vox) {
|
|
1604
|
-
if (!this._cloudMrFreehandPath) {
|
|
1605
|
-
this._cloudMrFreehandPath = [];
|
|
1606
|
-
}
|
|
1607
|
-
const path = this._cloudMrFreehandPath;
|
|
1608
|
-
const last = path[path.length - 1];
|
|
1609
|
-
if (!last || last[0] !== vox[0] || last[1] !== vox[1] || last[2] !== vox[2]) {
|
|
1610
|
-
path.push([vox[0], vox[1], vox[2]]);
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1613
|
-
}
|
|
1614
1644
|
if (isPolylinePenActive(this) && this._cloudMrPolylineVertices?.length > 0) {
|
|
1615
1645
|
previewPolylineSegment(this);
|
|
1616
1646
|
}
|
|
@@ -1643,22 +1673,20 @@ Niivue.prototype.mouseUpListener = function cloudMrMouseUpListener() {
|
|
|
1643
1673
|
}
|
|
1644
1674
|
|
|
1645
1675
|
if (!pendingDraft?.baseBitmap) {
|
|
1646
|
-
|
|
1647
|
-
cloudMrTryReopenPenDraftOnClick(this);
|
|
1648
|
-
}
|
|
1676
|
+
cloudMrTryReopenDraftOnClick(this);
|
|
1649
1677
|
return;
|
|
1650
1678
|
}
|
|
1651
1679
|
if (isDraftTooSmall(pendingDraft.ptA, pendingDraft.ptB)) {
|
|
1652
1680
|
this.drawBitmap.set(pendingDraft.baseBitmap);
|
|
1653
1681
|
this.refreshDrawing(true, false);
|
|
1654
1682
|
this.drawScene();
|
|
1655
|
-
|
|
1656
|
-
cloudMrTryReopenPenDraftOnClick(this);
|
|
1657
|
-
}
|
|
1683
|
+
cloudMrTryReopenDraftOnClick(this);
|
|
1658
1684
|
return;
|
|
1659
1685
|
}
|
|
1660
1686
|
this._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1661
|
-
if (typeof this.
|
|
1687
|
+
if (typeof this.onShapeCommitted === "function") {
|
|
1688
|
+
this.onShapeCommitted(pendingDraft);
|
|
1689
|
+
} else if (typeof this.onShapeDraftReady === "function") {
|
|
1662
1690
|
this.onShapeDraftReady(pendingDraft);
|
|
1663
1691
|
}
|
|
1664
1692
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, penDraftActive, onApplyPenDraft, onFillPenDraft, brushSize, updateBrushSize, shapeDraftActive, onApplyShapeDraft, }: {
|
|
1
|
+
export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEnabled, showPenModes, penDrawMode, onPenDrawModeChange, polylineVertexCount, penDraftActive, penDraftKind, penDraftFilled, onApplyPenDraft, onFillPenDraft, brushSize, updateBrushSize, shapeDraftActive, onApplyShapeDraft, }: {
|
|
2
2
|
expanded: any;
|
|
3
3
|
updateDrawPen: any;
|
|
4
4
|
setDrawingEnabled: any;
|
|
@@ -7,6 +7,8 @@ export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEna
|
|
|
7
7
|
onPenDrawModeChange: any;
|
|
8
8
|
polylineVertexCount?: number | undefined;
|
|
9
9
|
penDraftActive?: boolean | undefined;
|
|
10
|
+
penDraftKind: any;
|
|
11
|
+
penDraftFilled?: boolean | undefined;
|
|
10
12
|
onApplyPenDraft: any;
|
|
11
13
|
onFillPenDraft: any;
|
|
12
14
|
brushSize?: number | undefined;
|
|
@@ -36,7 +36,7 @@ var modeBtnSx = function (active) { return ({
|
|
|
36
36
|
px: 0.75
|
|
37
37
|
}); };
|
|
38
38
|
export default function DrawColorPlatte(_a) {
|
|
39
|
-
var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.showPenModes, showPenModes = _b === void 0 ? false : _b, _c = _a.penDrawMode, penDrawMode = _c === void 0 ? "freehand" : _c, onPenDrawModeChange = _a.onPenDrawModeChange, _d = _a.polylineVertexCount, polylineVertexCount = _d === void 0 ? 0 : _d, _e = _a.penDraftActive, penDraftActive = _e === void 0 ? false : _e, onApplyPenDraft = _a.onApplyPenDraft, onFillPenDraft = _a.onFillPenDraft,
|
|
39
|
+
var expanded = _a.expanded, updateDrawPen = _a.updateDrawPen, setDrawingEnabled = _a.setDrawingEnabled, _b = _a.showPenModes, showPenModes = _b === void 0 ? false : _b, _c = _a.penDrawMode, penDrawMode = _c === void 0 ? "freehand" : _c, onPenDrawModeChange = _a.onPenDrawModeChange, _d = _a.polylineVertexCount, polylineVertexCount = _d === void 0 ? 0 : _d, _e = _a.penDraftActive, penDraftActive = _e === void 0 ? false : _e, penDraftKind = _a.penDraftKind, _f = _a.penDraftFilled, penDraftFilled = _f === void 0 ? false : _f, onApplyPenDraft = _a.onApplyPenDraft, onFillPenDraft = _a.onFillPenDraft, _g = _a.brushSize, brushSize = _g === void 0 ? 1 : _g, updateBrushSize = _a.updateBrushSize, _h = _a.shapeDraftActive, shapeDraftActive = _h === void 0 ? false : _h, onApplyShapeDraft = _a.onApplyShapeDraft;
|
|
40
40
|
return (_jsxs(Stack, __assign({ style: {
|
|
41
41
|
position: "absolute",
|
|
42
42
|
top: "100%",
|
|
@@ -53,15 +53,19 @@ export default function DrawColorPlatte(_a) {
|
|
|
53
53
|
}, direction: "column", spacing: 0.5, sx: { py: expanded ? 0.5 : 0 } }, { children: [showPenModes && expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", spacing: 0.5, sx: { px: 0.75, pt: 0.25 } }, { children: [_jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("freehand"); }, sx: modeBtnSx(penDrawMode === "freehand") }, { children: "Freehand" })), _jsx(Button, __assign({ size: "small", onClick: function () { return onPenDrawModeChange === null || onPenDrawModeChange === void 0 ? void 0 : onPenDrawModeChange("polyline"); }, sx: modeBtnSx(penDrawMode === "polyline") }, { children: "Polyline" }))] }))), showPenModes && expanded && updateBrushSize && (_jsx(BrushSizeSlider, { label: "Line thickness", brushSize: brushSize, updateBrushSize: updateBrushSize })), _jsx(Stack, __assign({ direction: "row" }, { children: FILLED_COLORS.map(function (color, index) { return (_jsx(IconButton, __assign({ onClick: function () {
|
|
54
54
|
updateDrawPen({ target: { value: index + 1 } });
|
|
55
55
|
setDrawingEnabled(true);
|
|
56
|
-
} }, { children: _jsx(FiberManualRecordIcon, { sx: color.sx }) }), index)); }) })), showPenModes && penDrawMode === "polyline" && expanded && polylineVertexCount === 0 && (_jsx(Typography, __assign({ sx: { px: 1, pb: 0.5, fontSize: "0.68rem", color: "#aaa", userSelect: "none" } }, { children: "Click each vertex to draw connected line segments" }))), showPenModes &&
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
} }, { children: _jsx(FiberManualRecordIcon, { sx: color.sx }) }), index)); }) })), showPenModes && penDrawMode === "polyline" && expanded && polylineVertexCount === 0 && (_jsx(Typography, __assign({ sx: { px: 1, pb: 0.5, fontSize: "0.68rem", color: "#aaa", userSelect: "none" } }, { children: "Click each vertex to draw connected line segments" }))), showPenModes &&
|
|
57
|
+
penDraftActive &&
|
|
58
|
+
(penDrawMode === "polyline" || penDraftKind === "freehand") &&
|
|
59
|
+
expanded && (_jsxs(Stack, __assign({ direction: "row", alignItems: "center", justifyContent: "flex-end", spacing: 1, sx: { px: 1, py: 0.5, borderTop: "1px solid #555", width: "100%" } }, { children: [penDrawMode === "polyline" && polylineVertexCount >= 3 && (_jsx(Tooltip, __assign({ title: penDraftFilled
|
|
60
|
+
? "Remove fill (keeps outline editable)"
|
|
61
|
+
: "Fill interior (keeps outline editable until Apply)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": penDraftFilled ? "undo fill polyline" : "fill polyline", onClick: function () { return onFillPenDraft === null || onFillPenDraft === void 0 ? void 0 : onFillPenDraft(); }, sx: {
|
|
62
|
+
color: penDraftFilled ? "#ffb74d" : "#c9a0e8",
|
|
59
63
|
fontSize: ACTION_FONT_SIZE,
|
|
60
64
|
textTransform: "none",
|
|
61
65
|
minWidth: 0,
|
|
62
66
|
py: 0.25,
|
|
63
67
|
px: 0.75
|
|
64
|
-
} }, { children: "Fill" })) })))
|
|
68
|
+
} }, { children: penDraftFilled ? "Undo Fill" : "Fill" })) }))), _jsx(Tooltip, __assign({ title: "Apply shape (enter or right-click)" }, { children: _jsx(Button, __assign({ size: "small", "aria-label": "apply pen draft", onClick: function () { return onApplyPenDraft === null || onApplyPenDraft === void 0 ? void 0 : onApplyPenDraft(); }, startIcon: _jsx(CheckIcon, { sx: { fontSize: ACTION_ICON_SIZE } }), sx: {
|
|
65
69
|
color: "#c9a0e8",
|
|
66
70
|
fontSize: ACTION_FONT_SIZE,
|
|
67
71
|
textTransform: "none",
|
|
@@ -189,7 +189,7 @@ export function MroDrawToolkit(props) {
|
|
|
189
189
|
alignItems: "center",
|
|
190
190
|
gap: 4,
|
|
191
191
|
overflow: "visible"
|
|
192
|
-
} }, { children: [_jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "d" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Pen" }, { children: _jsx(IconButton, __assign({ "aria-label": "pen", size: "small", onClick: clickPen, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("pen")) }, { children: _jsx(DrawIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "d", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, showPenModes: true, penDrawMode: props.penDrawMode, onPenDrawModeChange: props.onPenDrawModeChange, polylineVertexCount: props.polylineVertexCount, onCancelPolyline: props.onCancelPolyline, penDraftActive: props.penDraftActive, onApplyPenDraft: props.onApplyPenDraft, onFillPenDraft: props.onFillPenDraft, brushSize: props.brushSize, updateBrushSize: props.updateBrushSize })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "r" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Rectangle" }, { children: _jsx(IconButton, __assign({ "aria-label": "rectangle", size: "small", onClick: clickRectangle, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("rectangle")) }, { children: _jsx(CropSquareOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "r", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "rectangle", onApplyShapeDraft: props.onApplyShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "l" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Ellipse" }, { children: _jsx(IconButton, __assign({ "aria-label": "ellipse", size: "small", onClick: clickEllipse, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("ellipse")) }, { children: _jsx(CircleOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "l", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "ellipse", onApplyShapeDraft: props.onApplyShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "e" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Eraser" }, { children: _jsx(IconButton, __assign({ "aria-label": "erase", size: "small", onClick: clickEraser, sx: __assign(__assign({}, toolBtnSx), (eraserActive
|
|
192
|
+
} }, { children: [_jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "d" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Pen" }, { children: _jsx(IconButton, __assign({ "aria-label": "pen", size: "small", onClick: clickPen, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("pen")) }, { children: _jsx(DrawIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "d", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, showPenModes: true, penDrawMode: props.penDrawMode, onPenDrawModeChange: props.onPenDrawModeChange, polylineVertexCount: props.polylineVertexCount, onCancelPolyline: props.onCancelPolyline, penDraftActive: props.penDraftActive, penDraftKind: props.penDraftKind, penDraftFilled: props.penDraftFilled, onApplyPenDraft: props.onApplyPenDraft, onFillPenDraft: props.onFillPenDraft, brushSize: props.brushSize, updateBrushSize: props.updateBrushSize })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "r" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Rectangle" }, { children: _jsx(IconButton, __assign({ "aria-label": "rectangle", size: "small", onClick: clickRectangle, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("rectangle")) }, { children: _jsx(CropSquareOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "r", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "rectangle", onApplyShapeDraft: props.onApplyShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "l" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Ellipse" }, { children: _jsx(IconButton, __assign({ "aria-label": "ellipse", size: "small", onClick: clickEllipse, sx: __assign(__assign({}, toolBtnSx), shapeSelectedSx("ellipse")) }, { children: _jsx(CircleOutlinedIcon, { sx: { color: "inherit" } }) })) })), _jsx(DrawColorPlatte, { expanded: expandedOption === "l", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, shapeDraftActive: props.shapeDraftActive && drawShapeTool === "ellipse", onApplyShapeDraft: props.onApplyShapeDraft })] })), _jsxs(Box, __assign({ sx: { position: "relative", zIndex: expandedOption === "e" ? 1600 : "auto", display: "inline-flex", alignItems: "center" } }, { children: [_jsx(Tooltip, __assign({ title: "Eraser" }, { children: _jsx(IconButton, __assign({ "aria-label": "erase", size: "small", onClick: clickEraser, sx: __assign(__assign({}, toolBtnSx), (eraserActive
|
|
193
193
|
? { backgroundColor: "rgba(88, 15, 139, 0.12)", color: "#580f8b" }
|
|
194
194
|
: {})) }, { children: filled || !eraserActive ? (_jsx(EraserIcon, {})) : (_jsx(AutoFixNormalOutlinedIcon, { sx: { color: ICON_COLOR } })) })) })), _jsx(EraserPlatte, { expandEraseOptions: expandedOption === "e", updateDrawPen: props.updateDrawPen, setDrawingEnabled: props.setDrawingEnabled, brushSize: props.brushSize, updateBrushSize: props.updateBrushSize })] })), _jsx(Tooltip, __assign({ title: "Undo" }, { children: _jsx(IconButton, __assign({ "aria-label": "revert", size: "small", onClick: function () { return props.drawUndo(); }, sx: toolBtnSx }, { children: _jsx(ReplyIcon, { sx: { color: ICON_COLOR } }) })) })), _jsx(Tooltip, __assign({ title: "Save screenshot" }, { children: _jsx("span", { children: _jsx(IconButton, __assign({ "aria-label": "capture", size: "small", disabled: !vol, onClick: function () { return vol && props.nv.saveScene("".concat(vol.name, "_drawing.png")); }, sx: toolBtnSx }, { children: _jsx(CameraAltIcon, { sx: { color: ICON_COLOR } }) })) }) })), _jsx(Tooltip, __assign({ title: "Clear drawing" }, { children: _jsx(IconButton, __assign({ "aria-label": "delete", size: "small", onClick: function () {
|
|
195
195
|
var _a;
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* @property {[number, number, number][]} [strokeVoxels]
|
|
10
|
-
* @property {[number, number, number][]} [pathVertices]
|
|
11
|
-
* @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
|
|
12
|
-
* @property {boolean} [filled]
|
|
13
|
-
*/
|
|
1
|
+
/** Voxel indices added by a pen draft relative to its base bitmap. */
|
|
2
|
+
export function collectPenDraftVoxelIndices(nv: any, draft: any): Set<any>;
|
|
3
|
+
/** Collect applied polyline voxels using session baseline and vertex redraw fallbacks. */
|
|
4
|
+
export function collectPolylineAppliedVoxelIndices(nv: any, draft: any): Set<any>;
|
|
5
|
+
export function isRegisteredPolylineClick(nv: any, seedVox?: any[] | null): boolean;
|
|
6
|
+
/** Persist applied polyline vertices so reopen can restore the full line. */
|
|
7
|
+
export function registerAppliedPolyline(nv: any, draft: any, existingId: any): any;
|
|
8
|
+
export function restoreCommittedPolyline(nv: any, registryId: any): void;
|
|
14
9
|
export function isEraserActive(nv: any): any;
|
|
15
10
|
export function isFreehandPenActive(nv: any): any;
|
|
16
11
|
export function shouldDeferFreehandCommit(nv: any): any;
|
|
@@ -24,7 +19,6 @@ export function captureFreehandDraft(nv: any, sessionStartBitmap: any, axCorSag:
|
|
|
24
19
|
axCorSag: any;
|
|
25
20
|
penValue: any;
|
|
26
21
|
strokeVoxels: number[][];
|
|
27
|
-
pathVertices: any;
|
|
28
22
|
bounds: {
|
|
29
23
|
x1: number;
|
|
30
24
|
y1: number;
|
|
@@ -48,11 +42,24 @@ export function polylineDraftFromNv(nv: any, { filled }?: {
|
|
|
48
42
|
penValue: any;
|
|
49
43
|
filled: boolean;
|
|
50
44
|
} | null;
|
|
51
|
-
/** Fill freehand interior from the traced stroke path (outline stays editable until Apply). */
|
|
52
|
-
export function fillFreehandDraft(nv: any, draft: any): any;
|
|
53
45
|
/** Fill polyline interior without closing the outline or committing the draft. */
|
|
54
46
|
export function fillPolylineDraft(nv: any, draft: any): any;
|
|
55
|
-
|
|
47
|
+
/** Undo a previous fill — revert to outline-only without committing the draft. */
|
|
48
|
+
export function unfillPolylineDraft(nv: any, draft: any): any;
|
|
49
|
+
export function applyPenDraft(nv: any, draft: any): any;
|
|
50
|
+
/**
|
|
51
|
+
* Reconstruct a polyline PenDraft from stored vertices (not flood-fill).
|
|
52
|
+
* Returns null if the click didn't land on a registered polyline.
|
|
53
|
+
*/
|
|
54
|
+
export function capturePolylineDraftFromClick(nv: any): {
|
|
55
|
+
kind: string;
|
|
56
|
+
baseBitmap: Uint8Array;
|
|
57
|
+
axCorSag: any;
|
|
58
|
+
penValue: any;
|
|
59
|
+
vertices: any;
|
|
60
|
+
filled: any;
|
|
61
|
+
_registryId: any;
|
|
62
|
+
} | null;
|
|
56
63
|
/**
|
|
57
64
|
* Flood-fill from the clicked voxel to reconstruct a freehand PenDraft for re-editing.
|
|
58
65
|
* Returns null if the click didn't land on a labeled voxel.
|
|
@@ -74,7 +81,6 @@ export type PenDraft = {
|
|
|
74
81
|
penValue: number;
|
|
75
82
|
vertices?: [number, number, number][] | undefined;
|
|
76
83
|
strokeVoxels?: [number, number, number][] | undefined;
|
|
77
|
-
pathVertices?: [number, number, number][] | undefined;
|
|
78
84
|
bounds?: {
|
|
79
85
|
x1: number;
|
|
80
86
|
y1: number;
|
|
@@ -84,4 +90,5 @@ export type PenDraft = {
|
|
|
84
90
|
z2: number;
|
|
85
91
|
} | undefined;
|
|
86
92
|
filled?: boolean | undefined;
|
|
93
|
+
_registryId?: number | undefined;
|
|
87
94
|
};
|
|
@@ -30,17 +30,137 @@ import { voxFromMouse } from "./polylinePenUtils";
|
|
|
30
30
|
* @property {number} penValue
|
|
31
31
|
* @property {[number, number, number][]} [vertices]
|
|
32
32
|
* @property {[number, number, number][]} [strokeVoxels]
|
|
33
|
-
* @property {[number, number, number][]} [pathVertices]
|
|
34
33
|
* @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
|
|
35
34
|
* @property {boolean} [filled]
|
|
35
|
+
* @property {number} [_registryId]
|
|
36
36
|
*/
|
|
37
|
+
function voxelIndexFromVox(vox, dx, dy) {
|
|
38
|
+
return vox[0] + vox[1] * dx + vox[2] * dx * dy;
|
|
39
|
+
}
|
|
40
|
+
/** Voxel indices added by a pen draft relative to its base bitmap. */
|
|
41
|
+
export function collectPenDraftVoxelIndices(nv, draft) {
|
|
42
|
+
var indices = new Set();
|
|
43
|
+
if (!nv.drawBitmap || !(draft === null || draft === void 0 ? void 0 : draft.baseBitmap))
|
|
44
|
+
return indices;
|
|
45
|
+
var penValue = draft.penValue;
|
|
46
|
+
for (var i = 0; i < nv.drawBitmap.length; i++) {
|
|
47
|
+
if (nv.drawBitmap[i] === penValue && draft.baseBitmap[i] !== penValue) {
|
|
48
|
+
indices.add(i);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return indices;
|
|
52
|
+
}
|
|
53
|
+
/** Collect applied polyline voxels using session baseline and vertex redraw fallbacks. */
|
|
54
|
+
export function collectPolylineAppliedVoxelIndices(nv, draft) {
|
|
55
|
+
var _a;
|
|
56
|
+
var indices = collectPenDraftVoxelIndices(nv, draft);
|
|
57
|
+
if (indices.size)
|
|
58
|
+
return indices;
|
|
59
|
+
var session = nv._cloudMrPolylineSessionStartBitmap;
|
|
60
|
+
if (session && nv.drawBitmap && (draft === null || draft === void 0 ? void 0 : draft.penValue) != null) {
|
|
61
|
+
indices = new Set();
|
|
62
|
+
var penValue = draft.penValue;
|
|
63
|
+
for (var i = 0; i < nv.drawBitmap.length; i++) {
|
|
64
|
+
if (nv.drawBitmap[i] === penValue && session[i] !== penValue) {
|
|
65
|
+
indices.add(i);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (indices.size)
|
|
69
|
+
return indices;
|
|
70
|
+
}
|
|
71
|
+
if (((_a = draft === null || draft === void 0 ? void 0 : draft.vertices) === null || _a === void 0 ? void 0 : _a.length) >= 2 && session) {
|
|
72
|
+
var baseBitmap = new Uint8Array(session);
|
|
73
|
+
redrawPolylineDraft(nv, __assign(__assign({}, draft), { baseBitmap: baseBitmap, filled: !!draft.filled }));
|
|
74
|
+
indices = collectPenDraftVoxelIndices(nv, __assign(__assign({}, draft), { baseBitmap: baseBitmap }));
|
|
75
|
+
}
|
|
76
|
+
return indices;
|
|
77
|
+
}
|
|
78
|
+
export function isRegisteredPolylineClick(nv, seedVox) {
|
|
79
|
+
if (seedVox === void 0) { seedVox = voxFromMouse(nv); }
|
|
80
|
+
return !!findPolylineRegistryEntry(nv, seedVox);
|
|
81
|
+
}
|
|
82
|
+
function findPolylineRegistryEntryById(nv, registryId) {
|
|
83
|
+
var _a, _b;
|
|
84
|
+
return (_b = (_a = nv._cloudMrPolylineRegistry) === null || _a === void 0 ? void 0 : _a.find(function (entry) { return entry.id === registryId; })) !== null && _b !== void 0 ? _b : null;
|
|
85
|
+
}
|
|
86
|
+
function findPolylineRegistryEntry(nv, seedVox) {
|
|
87
|
+
var _a;
|
|
88
|
+
var registry = nv._cloudMrPolylineRegistry;
|
|
89
|
+
if (!(registry === null || registry === void 0 ? void 0 : registry.length) || !seedVox || !((_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims))
|
|
90
|
+
return null;
|
|
91
|
+
var dx = nv.back.dims[1];
|
|
92
|
+
var dy = nv.back.dims[2];
|
|
93
|
+
var seedIdx = voxelIndexFromVox(seedVox, dx, dy);
|
|
94
|
+
var direct = registry.find(function (entry) { return entry.voxelIndices.has(seedIdx); });
|
|
95
|
+
if (direct)
|
|
96
|
+
return direct;
|
|
97
|
+
var cluster = floodFillClusterFromVox(nv, seedVox, { connectivity: 26 });
|
|
98
|
+
if (!cluster)
|
|
99
|
+
return null;
|
|
100
|
+
var best = null;
|
|
101
|
+
var bestOverlap = 0;
|
|
102
|
+
for (var _i = 0, registry_1 = registry; _i < registry_1.length; _i++) {
|
|
103
|
+
var entry = registry_1[_i];
|
|
104
|
+
var overlap = 0;
|
|
105
|
+
for (var _b = 0, _c = cluster.visited; _b < _c.length; _b++) {
|
|
106
|
+
var idx = _c[_b];
|
|
107
|
+
if (entry.voxelIndices.has(idx))
|
|
108
|
+
overlap++;
|
|
109
|
+
}
|
|
110
|
+
if (overlap > bestOverlap) {
|
|
111
|
+
bestOverlap = overlap;
|
|
112
|
+
best = entry;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return bestOverlap > 0 ? best : null;
|
|
116
|
+
}
|
|
117
|
+
/** Persist applied polyline vertices so reopen can restore the full line. */
|
|
118
|
+
export function registerAppliedPolyline(nv, draft, existingId) {
|
|
119
|
+
var _a;
|
|
120
|
+
var voxelIndices = collectPolylineAppliedVoxelIndices(nv, draft);
|
|
121
|
+
if (!voxelIndices.size || !((_a = draft.vertices) === null || _a === void 0 ? void 0 : _a.length))
|
|
122
|
+
return null;
|
|
123
|
+
nv._cloudMrPolylineRegistry = nv._cloudMrPolylineRegistry || [];
|
|
124
|
+
var nextId = existingId !== null && existingId !== void 0 ? existingId : ((nv._cloudMrPolylineNextId = (nv._cloudMrPolylineNextId || 0) + 1));
|
|
125
|
+
var entry = {
|
|
126
|
+
id: nextId,
|
|
127
|
+
vertices: draft.vertices.map(function (v) { return __spreadArray([], v, true); }),
|
|
128
|
+
axCorSag: draft.axCorSag,
|
|
129
|
+
penValue: draft.penValue,
|
|
130
|
+
filled: !!draft.filled,
|
|
131
|
+
voxelIndices: voxelIndices
|
|
132
|
+
};
|
|
133
|
+
var existingIndex = nv._cloudMrPolylineRegistry.findIndex(function (e) { return e.id === nextId; });
|
|
134
|
+
if (existingIndex >= 0) {
|
|
135
|
+
nv._cloudMrPolylineRegistry[existingIndex] = entry;
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
nv._cloudMrPolylineRegistry.push(entry);
|
|
139
|
+
}
|
|
140
|
+
return nextId;
|
|
141
|
+
}
|
|
142
|
+
export function restoreCommittedPolyline(nv, registryId) {
|
|
143
|
+
var entry = findPolylineRegistryEntryById(nv, registryId);
|
|
144
|
+
if (!entry || !nv.drawBitmap)
|
|
145
|
+
return;
|
|
146
|
+
var draft = {
|
|
147
|
+
kind: "polyline",
|
|
148
|
+
vertices: entry.vertices.map(function (v) { return __spreadArray([], v, true); }),
|
|
149
|
+
baseBitmap: nv.drawBitmap.slice(),
|
|
150
|
+
axCorSag: entry.axCorSag,
|
|
151
|
+
penValue: entry.penValue,
|
|
152
|
+
filled: entry.filled
|
|
153
|
+
};
|
|
154
|
+
redrawPolylineDraft(nv, draft);
|
|
155
|
+
}
|
|
37
156
|
export function isEraserActive(nv) {
|
|
38
157
|
return (nv.opts.drawingEnabled &&
|
|
39
158
|
nv.opts.penType === NI_PEN_TYPE.PEN &&
|
|
40
159
|
nv.opts.penValue === 0);
|
|
41
160
|
}
|
|
42
161
|
export function isFreehandPenActive(nv) {
|
|
43
|
-
return (nv.opts.
|
|
162
|
+
return (nv.opts.isFilledPen &&
|
|
163
|
+
nv.opts.drawingEnabled &&
|
|
44
164
|
nv.opts.penType === NI_PEN_TYPE.PEN &&
|
|
45
165
|
!nv.opts.polylinePenMode &&
|
|
46
166
|
nv.opts.penValue > 0);
|
|
@@ -82,7 +202,7 @@ export function syncPolylineDraftToNv(nv, draft) {
|
|
|
82
202
|
nv._cloudMrPolylineAxCorSag = draft.axCorSag;
|
|
83
203
|
}
|
|
84
204
|
export function captureFreehandDraft(nv, sessionStartBitmap, axCorSag) {
|
|
85
|
-
var _a
|
|
205
|
+
var _a;
|
|
86
206
|
if (!nv.drawBitmap || !sessionStartBitmap)
|
|
87
207
|
return null;
|
|
88
208
|
var penValue = nv.opts.penValue;
|
|
@@ -108,7 +228,7 @@ export function captureFreehandDraft(nv, sessionStartBitmap, axCorSag) {
|
|
|
108
228
|
var y2 = -Infinity;
|
|
109
229
|
var z2 = -Infinity;
|
|
110
230
|
for (var _i = 0, strokeVoxels_1 = strokeVoxels; _i < strokeVoxels_1.length; _i++) {
|
|
111
|
-
var
|
|
231
|
+
var _b = strokeVoxels_1[_i], x = _b[0], y = _b[1], z = _b[2];
|
|
112
232
|
x1 = Math.min(x1, x);
|
|
113
233
|
y1 = Math.min(y1, y);
|
|
114
234
|
z1 = Math.min(z1, z);
|
|
@@ -116,87 +236,30 @@ export function captureFreehandDraft(nv, sessionStartBitmap, axCorSag) {
|
|
|
116
236
|
y2 = Math.max(y2, y);
|
|
117
237
|
z2 = Math.max(z2, z);
|
|
118
238
|
}
|
|
119
|
-
var pathVertices = ((_b = nv._cloudMrFreehandPath) === null || _b === void 0 ? void 0 : _b.length)
|
|
120
|
-
? nv._cloudMrFreehandPath.map(function (v) { return __spreadArray([], v, true); })
|
|
121
|
-
: undefined;
|
|
122
|
-
nv._cloudMrFreehandPath = [];
|
|
123
239
|
return {
|
|
124
240
|
kind: "freehand",
|
|
125
241
|
baseBitmap: new Uint8Array(sessionStartBitmap),
|
|
126
242
|
axCorSag: axCorSag,
|
|
127
243
|
penValue: penValue,
|
|
128
244
|
strokeVoxels: strokeVoxels,
|
|
129
|
-
pathVertices: pathVertices,
|
|
130
245
|
bounds: { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 }
|
|
131
246
|
};
|
|
132
247
|
}
|
|
133
|
-
function collectStrokeVoxelsFromBitmap(nv, draft) {
|
|
134
|
-
var _a;
|
|
135
|
-
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
136
|
-
if (!dims || !nv.drawBitmap || !(draft === null || draft === void 0 ? void 0 : draft.baseBitmap))
|
|
137
|
-
return draft.strokeVoxels || [];
|
|
138
|
-
var strokeVoxels = [];
|
|
139
|
-
for (var i = 0; i < nv.drawBitmap.length; i++) {
|
|
140
|
-
if (nv.drawBitmap[i] === draft.penValue && draft.baseBitmap[i] !== draft.penValue) {
|
|
141
|
-
var z = Math.floor(i / (dims[1] * dims[2]));
|
|
142
|
-
var rem = i - z * dims[1] * dims[2];
|
|
143
|
-
var y = Math.floor(rem / dims[1]);
|
|
144
|
-
var x = rem % dims[1];
|
|
145
|
-
strokeVoxels.push([x, y, z]);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return strokeVoxels;
|
|
149
|
-
}
|
|
150
|
-
function boundsFromVoxels(voxels) {
|
|
151
|
-
var x1 = Infinity;
|
|
152
|
-
var y1 = Infinity;
|
|
153
|
-
var z1 = Infinity;
|
|
154
|
-
var x2 = -Infinity;
|
|
155
|
-
var y2 = -Infinity;
|
|
156
|
-
var z2 = -Infinity;
|
|
157
|
-
for (var _i = 0, voxels_1 = voxels; _i < voxels_1.length; _i++) {
|
|
158
|
-
var _a = voxels_1[_i], x = _a[0], y = _a[1], z = _a[2];
|
|
159
|
-
x1 = Math.min(x1, x);
|
|
160
|
-
y1 = Math.min(y1, y);
|
|
161
|
-
z1 = Math.min(z1, z);
|
|
162
|
-
x2 = Math.max(x2, x);
|
|
163
|
-
y2 = Math.max(y2, y);
|
|
164
|
-
z2 = Math.max(z2, z);
|
|
165
|
-
}
|
|
166
|
-
return { x1: x1, y1: y1, z1: z1, x2: x2, y2: y2, z2: z2 };
|
|
167
|
-
}
|
|
168
248
|
export function redrawFreehandDraft(nv, draft) {
|
|
169
|
-
var _a
|
|
170
|
-
if (!(draft === null || draft === void 0 ? void 0 : draft.baseBitmap)
|
|
249
|
+
var _a;
|
|
250
|
+
if (!((_a = draft === null || draft === void 0 ? void 0 : draft.strokeVoxels) === null || _a === void 0 ? void 0 : _a.length) || !draft.baseBitmap)
|
|
171
251
|
return;
|
|
172
252
|
nv.drawBitmap.set(draft.baseBitmap);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
else if (((_b = draft.pathVertices) === null || _b === void 0 ? void 0 : _b.length) >= 3) {
|
|
183
|
-
for (var _f = 0, _g = draft.strokeVoxels || []; _f < _g.length; _f++) {
|
|
184
|
-
var _h = _g[_f], x = _h[0], y = _h[1], z = _h[2];
|
|
185
|
-
nv.drawPt(x, y, z, draft.penValue);
|
|
186
|
-
}
|
|
187
|
-
nv.drawPenFillPts = draft.pathVertices.map(function (v) { return __spreadArray([], v, true); });
|
|
188
|
-
nv._cloudMrSkipNextUndoBitmap = true;
|
|
189
|
-
nv.drawPenFilled();
|
|
190
|
-
}
|
|
191
|
-
else if ((_c = draft.strokeVoxels) === null || _c === void 0 ? void 0 : _c.length) {
|
|
192
|
-
for (var _j = 0, _k = draft.strokeVoxels; _j < _k.length; _j++) {
|
|
193
|
-
var _l = _k[_j], x = _l[0], y = _l[1], z = _l[2];
|
|
194
|
-
nv.drawPt(x, y, z, draft.penValue);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
return;
|
|
253
|
+
// strokeVoxels already contains every expanded/thickened voxel of the original
|
|
254
|
+
// stroke. Replaying them with penBounds=0 stamps each voxel directly without
|
|
255
|
+
// re-expanding, preventing thickness from accumulating across edits.
|
|
256
|
+
var savedPenBounds = nv.opts.penBounds;
|
|
257
|
+
nv.opts.penBounds = 0;
|
|
258
|
+
for (var _i = 0, _b = draft.strokeVoxels; _i < _b.length; _i++) {
|
|
259
|
+
var _c = _b[_i], x = _c[0], y = _c[1], z = _c[2];
|
|
260
|
+
nv.drawPt(x, y, z, draft.penValue);
|
|
199
261
|
}
|
|
262
|
+
nv.opts.penBounds = savedPenBounds;
|
|
200
263
|
nv.refreshDrawing(false, false);
|
|
201
264
|
nv.drawScene();
|
|
202
265
|
}
|
|
@@ -302,21 +365,6 @@ export function polylineDraftFromNv(nv, _a) {
|
|
|
302
365
|
filled: filled
|
|
303
366
|
};
|
|
304
367
|
}
|
|
305
|
-
/** Fill freehand interior from the traced stroke path (outline stays editable until Apply). */
|
|
306
|
-
export function fillFreehandDraft(nv, draft) {
|
|
307
|
-
var path = draft.pathVertices;
|
|
308
|
-
if (!path || path.length < 3)
|
|
309
|
-
return draft;
|
|
310
|
-
redrawFreehandDraft(nv, __assign(__assign({}, draft), { filled: false }));
|
|
311
|
-
nv.drawPenAxCorSag = draft.axCorSag;
|
|
312
|
-
nv.drawPenFillPts = path.map(function (v) { return __spreadArray([], v, true); });
|
|
313
|
-
nv._cloudMrSkipNextUndoBitmap = true;
|
|
314
|
-
nv.drawPenFilled();
|
|
315
|
-
nv.refreshDrawing(false, false);
|
|
316
|
-
nv.drawScene();
|
|
317
|
-
var strokeVoxels = collectStrokeVoxelsFromBitmap(nv, draft);
|
|
318
|
-
return __assign(__assign({}, draft), { filled: true, strokeVoxels: strokeVoxels, bounds: boundsFromVoxels(strokeVoxels) });
|
|
319
|
-
}
|
|
320
368
|
/** Fill polyline interior without closing the outline or committing the draft. */
|
|
321
369
|
export function fillPolylineDraft(nv, draft) {
|
|
322
370
|
if (!(draft === null || draft === void 0 ? void 0 : draft.vertices) || draft.vertices.length < 3)
|
|
@@ -332,9 +380,35 @@ export function fillPolylineDraft(nv, draft) {
|
|
|
332
380
|
syncPolylineDraftToNv(nv, next);
|
|
333
381
|
return next;
|
|
334
382
|
}
|
|
383
|
+
/** Undo a previous fill — revert to outline-only without committing the draft. */
|
|
384
|
+
export function unfillPolylineDraft(nv, draft) {
|
|
385
|
+
if (!(draft === null || draft === void 0 ? void 0 : draft.vertices))
|
|
386
|
+
return draft;
|
|
387
|
+
var next = __assign(__assign({}, draft), { filled: false });
|
|
388
|
+
redrawPolylineDraft(nv, next);
|
|
389
|
+
nv.refreshDrawing(false, false);
|
|
390
|
+
nv.drawScene();
|
|
391
|
+
syncPolylineDraftToNv(nv, next);
|
|
392
|
+
return next;
|
|
393
|
+
}
|
|
335
394
|
export function applyPenDraft(nv, draft) {
|
|
395
|
+
var _a;
|
|
336
396
|
if (draft.kind === "polyline") {
|
|
337
|
-
|
|
397
|
+
// Prefer live vertex state; draft snapshots can lag behind the canvas.
|
|
398
|
+
if (((_a = nv._cloudMrPolylineVertices) === null || _a === void 0 ? void 0 : _a.length) >= 2) {
|
|
399
|
+
var fresh = polylineDraftFromNv(nv, { filled: !!draft.filled });
|
|
400
|
+
if (fresh)
|
|
401
|
+
draft = fresh;
|
|
402
|
+
}
|
|
403
|
+
// Commit the incrementally drawn bitmap (drops rubber-band preview to cursor).
|
|
404
|
+
if (nv._cloudMrPolylineBaseBitmap) {
|
|
405
|
+
nv.drawBitmap.set(nv._cloudMrPolylineBaseBitmap);
|
|
406
|
+
nv.refreshDrawing(false, false);
|
|
407
|
+
nv.drawScene();
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
redrawPolylineDraft(nv, draft);
|
|
411
|
+
}
|
|
338
412
|
}
|
|
339
413
|
else {
|
|
340
414
|
redrawFreehandDraft(nv, draft);
|
|
@@ -343,6 +417,27 @@ export function applyPenDraft(nv, draft) {
|
|
|
343
417
|
if (typeof nv.onDrawingChanged === "function") {
|
|
344
418
|
nv.onDrawingChanged("draw");
|
|
345
419
|
}
|
|
420
|
+
return draft;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Reconstruct a polyline PenDraft from stored vertices (not flood-fill).
|
|
424
|
+
* Returns null if the click didn't land on a registered polyline.
|
|
425
|
+
*/
|
|
426
|
+
export function capturePolylineDraftFromClick(nv) {
|
|
427
|
+
var seedVox = voxFromMouse(nv);
|
|
428
|
+
var entry = findPolylineRegistryEntry(nv, seedVox);
|
|
429
|
+
if (!entry)
|
|
430
|
+
return null;
|
|
431
|
+
var baseBitmap = eraseClusterFromBitmap(nv.drawBitmap, entry.voxelIndices);
|
|
432
|
+
return {
|
|
433
|
+
kind: "polyline",
|
|
434
|
+
baseBitmap: baseBitmap,
|
|
435
|
+
axCorSag: entry.axCorSag,
|
|
436
|
+
penValue: entry.penValue,
|
|
437
|
+
vertices: entry.vertices.map(function (v) { return __spreadArray([], v, true); }),
|
|
438
|
+
filled: entry.filled,
|
|
439
|
+
_registryId: entry.id
|
|
440
|
+
};
|
|
346
441
|
}
|
|
347
442
|
/**
|
|
348
443
|
* Flood-fill from the clicked voxel to reconstruct a freehand PenDraft for re-editing.
|
|
@@ -350,7 +445,7 @@ export function applyPenDraft(nv, draft) {
|
|
|
350
445
|
*/
|
|
351
446
|
export function capturePenDraftFromClick(nv) {
|
|
352
447
|
var seedVox = voxFromMouse(nv);
|
|
353
|
-
var cluster = floodFillClusterFromVox(nv, seedVox);
|
|
448
|
+
var cluster = floodFillClusterFromVox(nv, seedVox, { connectivity: 26 });
|
|
354
449
|
if (!cluster)
|
|
355
450
|
return null;
|
|
356
451
|
var label = cluster.label, visited = cluster.visited, voxels = cluster.voxels, bounds = cluster.bounds;
|
|
@@ -52,9 +52,12 @@ export function shouldDeferShapeCommit(nv: any): any;
|
|
|
52
52
|
export function inferAxCorSagFromBounds(x1: any, y1: any, z1: any, x2: any, y2: any, z2: any, fallback?: number): number;
|
|
53
53
|
/**
|
|
54
54
|
* Flood-fill a connected voxel cluster from a seed.
|
|
55
|
+
* @param {{ connectivity?: 6 | 26 }} [options]
|
|
55
56
|
* @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
|
|
56
57
|
*/
|
|
57
|
-
export function floodFillClusterFromVox(nv: any, seedVox: any
|
|
58
|
+
export function floodFillClusterFromVox(nv: any, seedVox: any, { connectivity }?: {
|
|
59
|
+
connectivity?: number | undefined;
|
|
60
|
+
}): {
|
|
58
61
|
label: number;
|
|
59
62
|
visited: Set<number>;
|
|
60
63
|
voxels: [number, number, number][];
|
|
@@ -217,13 +217,36 @@ function sliceKey(axCorSag, x, y, z) {
|
|
|
217
217
|
return "".concat(x, ",").concat(z);
|
|
218
218
|
return "".concat(y, ",").concat(z);
|
|
219
219
|
}
|
|
220
|
+
var NEIGHBORS_6 = [
|
|
221
|
+
[1, 0, 0],
|
|
222
|
+
[-1, 0, 0],
|
|
223
|
+
[0, 1, 0],
|
|
224
|
+
[0, -1, 0],
|
|
225
|
+
[0, 0, 1],
|
|
226
|
+
[0, 0, -1],
|
|
227
|
+
];
|
|
228
|
+
var NEIGHBORS_26 = (function () {
|
|
229
|
+
var out = [];
|
|
230
|
+
for (var dx = -1; dx <= 1; dx++) {
|
|
231
|
+
for (var dy = -1; dy <= 1; dy++) {
|
|
232
|
+
for (var dz = -1; dz <= 1; dz++) {
|
|
233
|
+
if (dx === 0 && dy === 0 && dz === 0)
|
|
234
|
+
continue;
|
|
235
|
+
out.push([dx, dy, dz]);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
})();
|
|
220
241
|
/**
|
|
221
242
|
* Flood-fill a connected voxel cluster from a seed.
|
|
243
|
+
* @param {{ connectivity?: 6 | 26 }} [options]
|
|
222
244
|
* @returns {{ label: number, visited: Set<number>, voxels: [number,number,number][], bounds: object } | null}
|
|
223
245
|
*/
|
|
224
|
-
export function floodFillClusterFromVox(nv, seedVox) {
|
|
225
|
-
var
|
|
226
|
-
var
|
|
246
|
+
export function floodFillClusterFromVox(nv, seedVox, _a) {
|
|
247
|
+
var _b;
|
|
248
|
+
var _c = _a === void 0 ? {} : _a, _d = _c.connectivity, connectivity = _d === void 0 ? 6 : _d;
|
|
249
|
+
var dims = (_b = nv.back) === null || _b === void 0 ? void 0 : _b.dims;
|
|
227
250
|
if (!dims || !nv.drawBitmap || !seedVox)
|
|
228
251
|
return null;
|
|
229
252
|
var dx = dims[1];
|
|
@@ -233,6 +256,7 @@ export function floodFillClusterFromVox(nv, seedVox) {
|
|
|
233
256
|
var label = nv.drawBitmap[seedIdx];
|
|
234
257
|
if (!label)
|
|
235
258
|
return null;
|
|
259
|
+
var neighborOffsets = connectivity === 26 ? NEIGHBORS_26 : NEIGHBORS_6;
|
|
236
260
|
var visited = new Set();
|
|
237
261
|
var queue = [seedIdx];
|
|
238
262
|
visited.add(seedIdx);
|
|
@@ -245,7 +269,7 @@ export function floodFillClusterFromVox(nv, seedVox) {
|
|
|
245
269
|
var z2 = -Infinity;
|
|
246
270
|
while (queue.length > 0) {
|
|
247
271
|
var idx = queue.shift();
|
|
248
|
-
var
|
|
272
|
+
var _e = decodeVoxelIndex(idx, dx, dy), x = _e[0], y = _e[1], z = _e[2];
|
|
249
273
|
voxels.push([x, y, z]);
|
|
250
274
|
x1 = Math.min(x1, x);
|
|
251
275
|
y1 = Math.min(y1, y);
|
|
@@ -253,16 +277,11 @@ export function floodFillClusterFromVox(nv, seedVox) {
|
|
|
253
277
|
x2 = Math.max(x2, x);
|
|
254
278
|
y2 = Math.max(y2, y);
|
|
255
279
|
z2 = Math.max(z2, z);
|
|
256
|
-
var
|
|
257
|
-
[
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
[x, y, z + 1],
|
|
262
|
-
[x, y, z - 1],
|
|
263
|
-
];
|
|
264
|
-
for (var _i = 0, neighbors_1 = neighbors; _i < neighbors_1.length; _i++) {
|
|
265
|
-
var _c = neighbors_1[_i], nx = _c[0], ny = _c[1], nz = _c[2];
|
|
280
|
+
for (var _i = 0, neighborOffsets_1 = neighborOffsets; _i < neighborOffsets_1.length; _i++) {
|
|
281
|
+
var _f = neighborOffsets_1[_i], ox = _f[0], oy = _f[1], oz = _f[2];
|
|
282
|
+
var nx = x + ox;
|
|
283
|
+
var ny = y + oy;
|
|
284
|
+
var nz = z + oz;
|
|
266
285
|
if (nx < 0 || ny < 0 || nz < 0 || nx >= dx || ny >= dy || nz >= dz)
|
|
267
286
|
continue;
|
|
268
287
|
var nIdx = voxelIndex(nx, ny, nz, dx, dy);
|