cloudmr-ux 4.7.2 → 4.7.4
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 +47 -10
- package/dist/CmrComponents/niivue-viewer/NiivuePatcher.js +18 -6
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.d.ts +2 -1
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/DrawColorPlatte.js +6 -4
- package/dist/CmrComponents/niivue-viewer/mro-draw-toolkit/MroDrawToolkit.js +1 -1
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.d.ts +25 -13
- package/dist/CmrComponents/niivue-viewer/penDraftUtils.js +175 -2
- 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 = penMode === "freehand";
|
|
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, polylineDraftFromNv, syncPolylineDraftToNv, } from './penDraftUtils';
|
|
66
|
+
import { applyPenDraft, cancelPenDraft, 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';
|
|
@@ -837,7 +837,11 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
837
837
|
var draft = penDraftRef.current;
|
|
838
838
|
if (!draft)
|
|
839
839
|
return;
|
|
840
|
+
var registryId = draft._registryId;
|
|
840
841
|
cancelPenDraft(nv, draft);
|
|
842
|
+
if (registryId && draft.kind === "polyline") {
|
|
843
|
+
restoreCommittedPolyline(nv, registryId);
|
|
844
|
+
}
|
|
841
845
|
if (draft.kind === "polyline") {
|
|
842
846
|
(_a = nv.cloudMrResetPolyline) === null || _a === void 0 ? void 0 : _a.call(nv);
|
|
843
847
|
setPolylineVertexCount(0);
|
|
@@ -850,22 +854,41 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
850
854
|
}
|
|
851
855
|
}
|
|
852
856
|
function applyPenDraftHandler(_a) {
|
|
853
|
-
var _b;
|
|
854
|
-
var
|
|
857
|
+
var _b, _c, _d;
|
|
858
|
+
var _e = _a === void 0 ? {} : _a, _f = _e.keepTool, keepTool = _f === void 0 ? false : _f;
|
|
855
859
|
var draft = penDraftRef.current;
|
|
856
860
|
if (!draft)
|
|
857
861
|
return;
|
|
858
|
-
|
|
862
|
+
if (draft.kind === "polyline" && ((_b = nv._cloudMrPolylineVertices) === null || _b === void 0 ? void 0 : _b.length) >= 2) {
|
|
863
|
+
var fresh = polylineDraftFromNv(nv, { filled: !!draft.filled });
|
|
864
|
+
if (fresh) {
|
|
865
|
+
draft = fresh;
|
|
866
|
+
penDraftRef.current = draft;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
draft = (_c = applyPenDraft(nv, draft)) !== null && _c !== void 0 ? _c : draft;
|
|
870
|
+
if (draft.kind === "polyline" && nv._cloudMrPolylineSessionStartBitmap) {
|
|
871
|
+
draft = __assign(__assign({}, draft), { baseBitmap: new Uint8Array(nv._cloudMrPolylineSessionStartBitmap) });
|
|
872
|
+
}
|
|
873
|
+
penDraftRef.current = draft;
|
|
859
874
|
markPenVoxelKind(draft, draft.kind === "polyline" ? 3 : 2);
|
|
860
875
|
if (draft.kind === "polyline") {
|
|
861
|
-
(
|
|
876
|
+
registerAppliedPolyline(nv, draft, draft._registryId);
|
|
877
|
+
(_d = nv.cloudMrResetPolyline) === null || _d === void 0 ? void 0 : _d.call(nv);
|
|
862
878
|
setPolylineVertexCount(0);
|
|
863
879
|
}
|
|
864
880
|
setPenDraft(null);
|
|
865
881
|
penDraftRef.current = null;
|
|
866
882
|
nv._cloudMrPenDraftActive = false;
|
|
867
883
|
setDrawingChanged(true);
|
|
868
|
-
if (
|
|
884
|
+
if (keepTool) {
|
|
885
|
+
var mode = penDrawModeRef.current;
|
|
886
|
+
nv.opts.polylinePenMode = mode === "polyline";
|
|
887
|
+
nv.opts.isFilledPen = mode === "freehand";
|
|
888
|
+
nv.opts.deferFreehandCommit = mode === "freehand";
|
|
889
|
+
nvSetDrawingEnabled(true);
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
869
892
|
// Deactivate tool entirely — palette closes, user re-enters edit by clicking the ROI
|
|
870
893
|
setDrawShapeTool(null);
|
|
871
894
|
nv.opts.deferFreehandCommit = false;
|
|
@@ -878,7 +901,9 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
878
901
|
var draft = penDraftRef.current;
|
|
879
902
|
if (!draft || draft.kind !== "polyline" || draft.vertices.length < 3)
|
|
880
903
|
return;
|
|
881
|
-
var next =
|
|
904
|
+
var next = draft.filled
|
|
905
|
+
? unfillPolylineDraft(nv, draft)
|
|
906
|
+
: fillPolylineDraft(nv, draft);
|
|
882
907
|
onPenDraftChange(next);
|
|
883
908
|
}
|
|
884
909
|
function onPenDraftChange(draft) {
|
|
@@ -900,14 +925,18 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
900
925
|
// Called by NiivuePatcher when the user clicks an applied pen ROI to re-edit it.
|
|
901
926
|
// penKind: 2=freehand, 3=polyline
|
|
902
927
|
nv.onPenDraftReopenReady = function (draft, penKind) {
|
|
928
|
+
var _a, _b;
|
|
903
929
|
setPenDraft(draft);
|
|
904
930
|
penDraftRef.current = draft;
|
|
905
931
|
nv._cloudMrPenDraftActive = true;
|
|
906
932
|
// Auto-select pen tool so the palette opens
|
|
907
933
|
setDrawShapeTool("pen");
|
|
908
|
-
var mode =
|
|
934
|
+
var mode = draft.kind === "polyline" ? "polyline" : "freehand";
|
|
909
935
|
setPenDrawMode(mode);
|
|
910
936
|
penDrawModeRef.current = mode;
|
|
937
|
+
if (draft.kind === "polyline") {
|
|
938
|
+
setPolylineVertexCount((_b = (_a = draft.vertices) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0);
|
|
939
|
+
}
|
|
911
940
|
nv.opts.deferFreehandCommit = false;
|
|
912
941
|
nv.opts.polylinePenMode = false;
|
|
913
942
|
nvSetDrawingEnabled(false);
|
|
@@ -1010,11 +1039,18 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
1010
1039
|
ensureToolKindBitmap();
|
|
1011
1040
|
var tkb = nv._cloudMrToolKindBitmap;
|
|
1012
1041
|
var dims = (_a = nv.back) === null || _a === void 0 ? void 0 : _a.dims;
|
|
1042
|
+
if (draft.kind === "polyline") {
|
|
1043
|
+
for (var _i = 0, _b = collectPolylineAppliedVoxelIndices(nv, draft); _i < _b.length; _i++) {
|
|
1044
|
+
var idx = _b[_i];
|
|
1045
|
+
tkb[idx] = kind;
|
|
1046
|
+
}
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1013
1049
|
if (draft.kind === "freehand" && draft.strokeVoxels && dims) {
|
|
1014
1050
|
var dx = dims[1];
|
|
1015
1051
|
var dy = dims[2];
|
|
1016
|
-
for (var
|
|
1017
|
-
var
|
|
1052
|
+
for (var _c = 0, _d = draft.strokeVoxels; _c < _d.length; _c++) {
|
|
1053
|
+
var _e = _d[_c], x = _e[0], y = _e[1], z = _e[2];
|
|
1018
1054
|
tkb[x + y * dx + z * dx * dy] = kind;
|
|
1019
1055
|
}
|
|
1020
1056
|
}
|
|
@@ -1621,6 +1657,7 @@ export default function CloudMrNiivueViewer(props) {
|
|
|
1621
1657
|
onCancelPenDraft: cancelPenDraftHandler,
|
|
1622
1658
|
onFillPenDraft: fillPolylineDraftHandler,
|
|
1623
1659
|
penDraftActive: penDraft != null,
|
|
1660
|
+
penDraftFilled: (penDraft === null || penDraft === void 0 ? void 0 : penDraft.filled) === true,
|
|
1624
1661
|
brushSize: brushSize,
|
|
1625
1662
|
updateBrushSize: nvUpdateBrushSize,
|
|
1626
1663
|
onActivateEraser: activateEraser,
|
|
@@ -24,8 +24,12 @@ import {
|
|
|
24
24
|
import {
|
|
25
25
|
captureFreehandDraft,
|
|
26
26
|
capturePenDraftFromClick,
|
|
27
|
+
capturePolylineDraftFromClick,
|
|
28
|
+
isRegisteredPolylineClick,
|
|
27
29
|
redrawFreehandDraft,
|
|
30
|
+
redrawPolylineDraft,
|
|
28
31
|
shouldDeferFreehandCommit,
|
|
32
|
+
syncPolylineDraftToNv,
|
|
29
33
|
} from "./penDraftUtils.js";
|
|
30
34
|
|
|
31
35
|
/*
|
|
@@ -1513,6 +1517,7 @@ function cloudMrTryReopenShapeDraftOnClick(nv) {
|
|
|
1513
1517
|
// If we know this voxel was drawn with the pen, skip shape reopen
|
|
1514
1518
|
const kind = clickedVoxelToolKind(nv);
|
|
1515
1519
|
if (kind === 2 || kind === 3) return false;
|
|
1520
|
+
if (isRegisteredPolylineClick(nv)) return false;
|
|
1516
1521
|
|
|
1517
1522
|
const reopenDraft = captureShapeDraftFromClick(nv);
|
|
1518
1523
|
if (!reopenDraft) return false;
|
|
@@ -1537,14 +1542,21 @@ function cloudMrTryReopenPenDraftOnClick(nv) {
|
|
|
1537
1542
|
const kind = clickedVoxelToolKind(nv);
|
|
1538
1543
|
if (kind === 1) return false;
|
|
1539
1544
|
|
|
1540
|
-
|
|
1541
|
-
|
|
1545
|
+
let draft = capturePolylineDraftFromClick(nv);
|
|
1546
|
+
let penKind = draft ? 3 : kind === 3 ? 3 : 2;
|
|
1542
1547
|
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1548
|
+
if (!draft) {
|
|
1549
|
+
draft = capturePenDraftFromClick(nv);
|
|
1550
|
+
if (!draft) return false;
|
|
1551
|
+
if (kind !== 3) penKind = 2;
|
|
1552
|
+
}
|
|
1546
1553
|
|
|
1547
|
-
|
|
1554
|
+
if (draft.kind === "polyline") {
|
|
1555
|
+
redrawPolylineDraft(nv, draft);
|
|
1556
|
+
syncPolylineDraftToNv(nv, draft);
|
|
1557
|
+
} else {
|
|
1558
|
+
redrawFreehandDraft(nv, draft);
|
|
1559
|
+
}
|
|
1548
1560
|
nv._cloudMrSuppressDrawingChangedMouseUp = true;
|
|
1549
1561
|
if (typeof nv.onPenDraftReopenReady === "function") {
|
|
1550
1562
|
nv.onPenDraftReopenReady(draft, penKind);
|
|
@@ -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, penDraftFilled, onApplyPenDraft, onFillPenDraft, brushSize, updateBrushSize, shapeDraftActive, onApplyShapeDraft, }: {
|
|
2
2
|
expanded: any;
|
|
3
3
|
updateDrawPen: any;
|
|
4
4
|
setDrawingEnabled: any;
|
|
@@ -7,6 +7,7 @@ export default function DrawColorPlatte({ expanded, updateDrawPen, setDrawingEna
|
|
|
7
7
|
onPenDrawModeChange: any;
|
|
8
8
|
polylineVertexCount?: number | undefined;
|
|
9
9
|
penDraftActive?: boolean | undefined;
|
|
10
|
+
penDraftFilled?: boolean | undefined;
|
|
10
11
|
onApplyPenDraft: any;
|
|
11
12
|
onFillPenDraft: any;
|
|
12
13
|
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, _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,14 +53,16 @@ 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 && penDraftActive && 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:
|
|
57
|
-
|
|
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 && penDraftActive && 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
|
|
57
|
+
? "Remove fill (keeps outline editable)"
|
|
58
|
+
: "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: {
|
|
59
|
+
color: penDraftFilled ? "#ffb74d" : "#c9a0e8",
|
|
58
60
|
fontSize: ACTION_FONT_SIZE,
|
|
59
61
|
textTransform: "none",
|
|
60
62
|
minWidth: 0,
|
|
61
63
|
py: 0.25,
|
|
62
64
|
px: 0.75
|
|
63
|
-
} }, { children: "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
|
+
} }, { 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: {
|
|
64
66
|
color: "#c9a0e8",
|
|
65
67
|
fontSize: ACTION_FONT_SIZE,
|
|
66
68
|
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, 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,15 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* @property {[number, number, number][]} [strokeVoxels]
|
|
10
|
-
* @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
|
|
11
|
-
* @property {boolean} [filled]
|
|
12
|
-
*/
|
|
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;
|
|
13
9
|
export function isEraserActive(nv: any): any;
|
|
14
10
|
export function isFreehandPenActive(nv: any): any;
|
|
15
11
|
export function shouldDeferFreehandCommit(nv: any): any;
|
|
@@ -48,7 +44,22 @@ export function polylineDraftFromNv(nv: any, { filled }?: {
|
|
|
48
44
|
} | null;
|
|
49
45
|
/** Fill polyline interior without closing the outline or committing the draft. */
|
|
50
46
|
export function fillPolylineDraft(nv: any, draft: any): any;
|
|
51
|
-
|
|
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;
|
|
52
63
|
/**
|
|
53
64
|
* Flood-fill from the clicked voxel to reconstruct a freehand PenDraft for re-editing.
|
|
54
65
|
* Returns null if the click didn't land on a labeled voxel.
|
|
@@ -79,4 +90,5 @@ export type PenDraft = {
|
|
|
79
90
|
z2: number;
|
|
80
91
|
} | undefined;
|
|
81
92
|
filled?: boolean | undefined;
|
|
93
|
+
_registryId?: number | undefined;
|
|
82
94
|
};
|
|
@@ -32,7 +32,127 @@ import { voxFromMouse } from "./polylinePenUtils";
|
|
|
32
32
|
* @property {[number, number, number][]} [strokeVoxels]
|
|
33
33
|
* @property {{ x1: number, y1: number, x2: number, y2: number, z1: number, z2: number }} [bounds]
|
|
34
34
|
* @property {boolean} [filled]
|
|
35
|
+
* @property {number} [_registryId]
|
|
35
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
|
+
}
|
|
36
156
|
export function isEraserActive(nv) {
|
|
37
157
|
return (nv.opts.drawingEnabled &&
|
|
38
158
|
nv.opts.penType === NI_PEN_TYPE.PEN &&
|
|
@@ -130,10 +250,16 @@ export function redrawFreehandDraft(nv, draft) {
|
|
|
130
250
|
if (!((_a = draft === null || draft === void 0 ? void 0 : draft.strokeVoxels) === null || _a === void 0 ? void 0 : _a.length) || !draft.baseBitmap)
|
|
131
251
|
return;
|
|
132
252
|
nv.drawBitmap.set(draft.baseBitmap);
|
|
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;
|
|
133
258
|
for (var _i = 0, _b = draft.strokeVoxels; _i < _b.length; _i++) {
|
|
134
259
|
var _c = _b[_i], x = _c[0], y = _c[1], z = _c[2];
|
|
135
260
|
nv.drawPt(x, y, z, draft.penValue);
|
|
136
261
|
}
|
|
262
|
+
nv.opts.penBounds = savedPenBounds;
|
|
137
263
|
nv.refreshDrawing(false, false);
|
|
138
264
|
nv.drawScene();
|
|
139
265
|
}
|
|
@@ -254,9 +380,35 @@ export function fillPolylineDraft(nv, draft) {
|
|
|
254
380
|
syncPolylineDraftToNv(nv, next);
|
|
255
381
|
return next;
|
|
256
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
|
+
}
|
|
257
394
|
export function applyPenDraft(nv, draft) {
|
|
395
|
+
var _a;
|
|
258
396
|
if (draft.kind === "polyline") {
|
|
259
|
-
|
|
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
|
+
}
|
|
260
412
|
}
|
|
261
413
|
else {
|
|
262
414
|
redrawFreehandDraft(nv, draft);
|
|
@@ -265,6 +417,27 @@ export function applyPenDraft(nv, draft) {
|
|
|
265
417
|
if (typeof nv.onDrawingChanged === "function") {
|
|
266
418
|
nv.onDrawingChanged("draw");
|
|
267
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
|
+
};
|
|
268
441
|
}
|
|
269
442
|
/**
|
|
270
443
|
* Flood-fill from the clicked voxel to reconstruct a freehand PenDraft for re-editing.
|
|
@@ -272,7 +445,7 @@ export function applyPenDraft(nv, draft) {
|
|
|
272
445
|
*/
|
|
273
446
|
export function capturePenDraftFromClick(nv) {
|
|
274
447
|
var seedVox = voxFromMouse(nv);
|
|
275
|
-
var cluster = floodFillClusterFromVox(nv, seedVox);
|
|
448
|
+
var cluster = floodFillClusterFromVox(nv, seedVox, { connectivity: 26 });
|
|
276
449
|
if (!cluster)
|
|
277
450
|
return null;
|
|
278
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);
|