@cornerstonejs/tools 5.4.16 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/esm/enums/Events.d.ts +7 -0
  2. package/dist/esm/enums/Events.js +7 -0
  3. package/dist/esm/index.d.ts +2 -2
  4. package/dist/esm/index.js +2 -2
  5. package/dist/esm/tools/SliceIntersectionTool.d.ts +150 -0
  6. package/dist/esm/tools/SliceIntersectionTool.js +1065 -0
  7. package/dist/esm/tools/WorldCrosshairTool.d.ts +118 -0
  8. package/dist/esm/tools/WorldCrosshairTool.js +749 -0
  9. package/dist/esm/tools/annotation/LivewireContourTool.d.ts +1 -3
  10. package/dist/esm/tools/annotation/LivewireContourTool.js +0 -18
  11. package/dist/esm/tools/annotation/SplineROITool.d.ts +1 -3
  12. package/dist/esm/tools/annotation/SplineROITool.js +0 -18
  13. package/dist/esm/tools/base/AnnotationTool.d.ts +4 -0
  14. package/dist/esm/tools/base/AnnotationTool.js +20 -0
  15. package/dist/esm/tools/index.d.ts +3 -1
  16. package/dist/esm/tools/index.js +3 -1
  17. package/dist/esm/tools/worldCrosshair/WorldCrosshairLines3D.d.ts +8 -0
  18. package/dist/esm/tools/worldCrosshair/WorldCrosshairLines3D.js +81 -0
  19. package/dist/esm/utilities/genericViewportToolHelpers.d.ts +1 -0
  20. package/dist/esm/utilities/genericViewportToolHelpers.js +31 -0
  21. package/dist/esm/utilities/index.d.ts +3 -1
  22. package/dist/esm/utilities/index.js +3 -1
  23. package/dist/esm/utilities/pickIntensityPointInSlab.d.ts +7 -0
  24. package/dist/esm/utilities/pickIntensityPointInSlab.js +106 -0
  25. package/dist/esm/utilities/spatial/areViewportsSpatiallyLinked.d.ts +3 -0
  26. package/dist/esm/utilities/spatial/areViewportsSpatiallyLinked.js +48 -0
  27. package/dist/esm/utilities/spatial/clipWorldLineToViewportCanvas.d.ts +3 -0
  28. package/dist/esm/utilities/spatial/clipWorldLineToViewportCanvas.js +58 -0
  29. package/dist/esm/utilities/spatial/distancePointToPlane.d.ts +3 -0
  30. package/dist/esm/utilities/spatial/distancePointToPlane.js +6 -0
  31. package/dist/esm/utilities/spatial/getDisplayedCanvasSize.d.ts +5 -0
  32. package/dist/esm/utilities/spatial/getDisplayedCanvasSize.js +12 -0
  33. package/dist/esm/utilities/spatial/getViewportPlane.d.ts +3 -0
  34. package/dist/esm/utilities/spatial/getViewportPlane.js +33 -0
  35. package/dist/esm/utilities/spatial/index.d.ts +11 -0
  36. package/dist/esm/utilities/spatial/index.js +10 -0
  37. package/dist/esm/utilities/spatial/intersectPlanes.d.ts +2 -0
  38. package/dist/esm/utilities/spatial/intersectPlanes.js +30 -0
  39. package/dist/esm/utilities/spatial/projectPointToPlane.d.ts +3 -0
  40. package/dist/esm/utilities/spatial/projectPointToPlane.js +11 -0
  41. package/dist/esm/utilities/spatial/rotateViewportAroundWorldPoint.d.ts +2 -0
  42. package/dist/esm/utilities/spatial/rotateViewportAroundWorldPoint.js +34 -0
  43. package/dist/esm/utilities/spatial/translateViewportAlongNormal.d.ts +2 -0
  44. package/dist/esm/utilities/spatial/translateViewportAlongNormal.js +36 -0
  45. package/dist/esm/utilities/spatial/types.d.ts +19 -0
  46. package/dist/esm/utilities/spatial/types.js +0 -0
  47. package/dist/esm/version.d.ts +1 -1
  48. package/dist/esm/version.js +1 -1
  49. package/package.json +4 -4
@@ -0,0 +1,1065 @@
1
+ import { mat4, vec2, vec3 } from 'gl-matrix';
2
+ import { getEnabledElement, getEnabledElementByIds, eventTarget, triggerEvent, utilities as csUtils, CONSTANTS, Enums, } from '@cornerstonejs/core';
3
+ import { AnnotationTool } from './base/index.js';
4
+ import { getToolGroup } from '../store/ToolGroupManager/index.js';
5
+ import { state } from '../store/state.js';
6
+ import { Events } from '../enums/index.js';
7
+ import { addAnnotation, getAnnotations, removeAnnotation, } from '../stateManagement/annotation/annotationState.js';
8
+ import { drawCircle as drawCircleSvg, drawLine as drawLineSvg, } from '../drawingSvg/index.js';
9
+ import { resetElementCursor, hideElementCursor, } from '../cursors/elementCursor.js';
10
+ import triggerAnnotationRenderForViewportIds from '../utilities/triggerAnnotationRenderForViewportIds.js';
11
+ import getViewportICamera from '../utilities/getViewportICamera.js';
12
+ import { navigatePlanarViewportToPoint } from '../utilities/genericViewportToolHelpers.js';
13
+ import * as lineSegment from '../utilities/math/line/index.js';
14
+ import { getViewportPlane, intersectPlanes, distancePointToPlane, clipWorldLineToViewportCanvas, translateViewportAlongNormal, } from '../utilities/spatial/index.js';
15
+ const { ViewportType } = Enums;
16
+ const { RENDERING_DEFAULTS } = CONSTANTS;
17
+ const LINE_PROXIMITY = 6;
18
+ const MIN_SLAB_HANDLE_CANVAS_OFFSET = 12;
19
+ const SLAB_HANDLE_LINE_FRACTION = 0.85;
20
+ const MAX_INTERMEDIATE_LINES = 50;
21
+ class SliceIntersectionTool extends AnnotationTool {
22
+ constructor(toolProps = {}, defaultToolProps = {
23
+ supportedInteractionTypes: ['Mouse', 'Touch'],
24
+ configuration: {
25
+ showCurrentSliceLine: true,
26
+ showSlabThickness: true,
27
+ showBoundaryLines: false,
28
+ showIntermediateLines: false,
29
+ draggable: true,
30
+ rotatable: true,
31
+ slabThicknessControls: true,
32
+ lineWidth: 1,
33
+ activeLineWidth: 2.5,
34
+ handleRadius: 3,
35
+ alignGroupsOnEnable: true,
36
+ rotationPivotPolicy: 'currentIntersection',
37
+ familyColors: {
38
+ axial: 'rgb(200, 60, 60)',
39
+ sagittal: 'rgb(220, 200, 60)',
40
+ coronal: 'rgb(60, 200, 60)',
41
+ },
42
+ setWorldCrosshairOnIntersectionDoubleClick: false,
43
+ jumpToIntersectionOnDoubleClick: false,
44
+ slabThicknessBlendMode: Enums.BlendModes.MAXIMUM_INTENSITY_BLEND,
45
+ perViewportLines: false,
46
+ },
47
+ }) {
48
+ super(toolProps, defaultToolProps);
49
+ this._activeOperation = null;
50
+ this._renderedLines = new Map();
51
+ this._pendingHandle = null;
52
+ this._editData = null;
53
+ this._mprFamilyByViewportId = new Map();
54
+ this._passViewportsById = null;
55
+ this._passPlaneGroups = null;
56
+ this._passCacheClearScheduled = false;
57
+ this.onCameraModified = (evt) => {
58
+ const viewportId = evt.detail?.viewportId;
59
+ if (!viewportId) {
60
+ this._renderToolViewports();
61
+ return;
62
+ }
63
+ const viewportIdsToRender = new Set();
64
+ for (const [targetViewportId, lines] of this._renderedLines) {
65
+ if (targetViewportId === viewportId) {
66
+ if (lines.length) {
67
+ viewportIdsToRender.add(targetViewportId);
68
+ }
69
+ continue;
70
+ }
71
+ if (lines.some((line) => line.leaderViewportId === viewportId)) {
72
+ viewportIdsToRender.add(targetViewportId);
73
+ }
74
+ }
75
+ if (viewportIdsToRender.size) {
76
+ triggerAnnotationRenderForViewportIds([...viewportIdsToRender]);
77
+ }
78
+ };
79
+ this.renderAnnotation = (enabledElement, svgDrawingHelper) => {
80
+ const { viewport: targetViewport } = enabledElement;
81
+ const lines = this._computeLinesForTarget(targetViewport);
82
+ this._renderedLines.set(targetViewport.id, lines);
83
+ if (!lines.length) {
84
+ return false;
85
+ }
86
+ const annotation = this._getOrCreateViewportAnnotation(targetViewport);
87
+ const annotationUID = annotation?.annotationUID ?? `SliceIntersection-${targetViewport.id}`;
88
+ const { lineWidth, activeLineWidth, handleRadius, showCurrentSliceLine } = this.configuration;
89
+ lines.forEach((lineInfo) => {
90
+ const { groupId, canvasPoints, color, isActive } = lineInfo;
91
+ const width = isActive ? activeLineWidth : lineWidth;
92
+ if (showCurrentSliceLine) {
93
+ drawLineSvg(svgDrawingHelper, annotationUID, `line-${groupId}`, canvasPoints[0], canvasPoints[1], {
94
+ color,
95
+ width,
96
+ }, `${annotationUID}-line-${groupId}`);
97
+ }
98
+ lineInfo.slabLineSegments.forEach((segment, index) => {
99
+ drawLineSvg(svgDrawingHelper, annotationUID, `slab-${groupId}-${index}`, segment[0], segment[1], {
100
+ color,
101
+ width: 1,
102
+ lineDash: '4,4',
103
+ strokeOpacity: 0.8,
104
+ });
105
+ });
106
+ lineInfo.boundaryLineSegments.forEach((segment, index) => {
107
+ drawLineSvg(svgDrawingHelper, annotationUID, `boundary-${groupId}-${index}`, segment[0], segment[1], {
108
+ color,
109
+ width: 1,
110
+ lineDash: '4,4',
111
+ strokeOpacity: 0.8,
112
+ });
113
+ });
114
+ lineInfo.rotateHandles.forEach((handleCanvas, index) => {
115
+ drawCircleSvg(svgDrawingHelper, annotationUID, `rotate-handle-${groupId}-${index}`, handleCanvas, handleRadius, {
116
+ color,
117
+ fill: color,
118
+ });
119
+ });
120
+ lineInfo.slabHandles.forEach((handleCanvas, index) => {
121
+ drawCircleSvg(svgDrawingHelper, annotationUID, `slab-handle-${groupId}-${index}`, handleCanvas, handleRadius, {
122
+ color,
123
+ fill: 'transparent',
124
+ });
125
+ });
126
+ });
127
+ return true;
128
+ };
129
+ this.addNewAnnotation = (evt) => {
130
+ const { element } = evt.detail;
131
+ const enabledElement = getEnabledElement(element);
132
+ if (!enabledElement) {
133
+ return this._createDetachedAnnotation();
134
+ }
135
+ return (this._getOrCreateViewportAnnotation(enabledElement.viewport) ??
136
+ this._createDetachedAnnotation());
137
+ };
138
+ this.isPointNearTool = (element, _annotation, canvasCoords, _proximity, _interactionType) => {
139
+ const enabledElement = getEnabledElement(element);
140
+ if (!enabledElement) {
141
+ return false;
142
+ }
143
+ return !!this._findLineNear(enabledElement.viewport.id, canvasCoords, LINE_PROXIMITY, true);
144
+ };
145
+ this.toolSelectedCallback = (evt, _annotation) => {
146
+ const { element, currentPoints } = evt.detail;
147
+ const enabledElement = getEnabledElement(element);
148
+ if (!enabledElement) {
149
+ return;
150
+ }
151
+ const lineInfo = this._findLineNear(enabledElement.viewport.id, currentPoints.canvas, LINE_PROXIMITY);
152
+ if (!lineInfo) {
153
+ return;
154
+ }
155
+ this._selectLine(lineInfo);
156
+ if (this.configuration.draggable) {
157
+ this._startManipulation(element, lineInfo, 'translate');
158
+ }
159
+ evt.preventDefault();
160
+ };
161
+ this.handleSelectedCallback = (evt, _annotation, _handle, _interactionType = 'Mouse') => {
162
+ const { element } = evt.detail;
163
+ const pendingHandle = this._pendingHandle;
164
+ this._pendingHandle = null;
165
+ if (!pendingHandle) {
166
+ return;
167
+ }
168
+ const { operation, lineInfo } = pendingHandle;
169
+ if (operation === 'rotate' && !this.configuration.rotatable) {
170
+ return;
171
+ }
172
+ if (operation === 'slabThickness' &&
173
+ !this.configuration.slabThicknessControls) {
174
+ return;
175
+ }
176
+ this._selectLine(lineInfo);
177
+ this._startManipulation(element, lineInfo, operation);
178
+ evt.preventDefault();
179
+ };
180
+ this.doubleClickCallback = (evt) => {
181
+ const { setWorldCrosshairOnIntersectionDoubleClick, jumpToIntersectionOnDoubleClick, } = this.configuration;
182
+ if (!setWorldCrosshairOnIntersectionDoubleClick &&
183
+ !jumpToIntersectionOnDoubleClick) {
184
+ return;
185
+ }
186
+ const { element, currentPoints } = evt.detail;
187
+ const enabledElement = getEnabledElement(element);
188
+ if (!enabledElement) {
189
+ return;
190
+ }
191
+ const { viewport } = enabledElement;
192
+ const lineInfo = this._findLineNear(viewport.id, currentPoints.canvas, LINE_PROXIMITY);
193
+ if (!lineInfo) {
194
+ return;
195
+ }
196
+ const pointOnLine = this._closestPointOnLine(lineInfo.line, currentPoints.world);
197
+ if (jumpToIntersectionOnDoubleClick) {
198
+ this._centerViewportOnPoint(viewport, pointOnLine);
199
+ }
200
+ if (setWorldCrosshairOnIntersectionDoubleClick) {
201
+ const worldCrosshairInstance = getToolGroup(this.toolGroupId)?.getToolInstance?.('WorldCrosshair');
202
+ worldCrosshairInstance?.setWorldPoint?.(pointOnLine, {
203
+ sourceViewportId: viewport.id,
204
+ sourceRenderingEngineId: enabledElement.renderingEngine?.id,
205
+ frameOfReferenceUID: enabledElement.FrameOfReferenceUID,
206
+ });
207
+ }
208
+ evt.preventDefault();
209
+ };
210
+ this.mouseMoveCallback = (evt, _filteredToolAnnotations) => {
211
+ const { element, currentPoints } = evt.detail;
212
+ const enabledElement = getEnabledElement(element);
213
+ if (!enabledElement) {
214
+ return false;
215
+ }
216
+ const { viewport } = enabledElement;
217
+ if (!this._isPlanarViewport(viewport)) {
218
+ return false;
219
+ }
220
+ const lineInfo = this._findLineNear(viewport.id, currentPoints.canvas, LINE_PROXIMITY, true);
221
+ let needsRedraw = false;
222
+ if (lineInfo) {
223
+ if (this._activeGroupId !== lineInfo.groupId ||
224
+ this._activeTargetViewportId !== viewport.id) {
225
+ this._activeGroupId = lineInfo.groupId;
226
+ this._activeTargetViewportId = viewport.id;
227
+ needsRedraw = true;
228
+ }
229
+ return needsRedraw;
230
+ }
231
+ if (this._activeTargetViewportId === viewport.id) {
232
+ this._activeTargetViewportId = undefined;
233
+ needsRedraw = true;
234
+ }
235
+ return needsRedraw;
236
+ };
237
+ this.filterInteractableAnnotationsForElement = (element, annotations) => {
238
+ if (!annotations?.length) {
239
+ return [];
240
+ }
241
+ const enabledElement = getEnabledElement(element);
242
+ if (!enabledElement) {
243
+ return [];
244
+ }
245
+ const { viewportId } = enabledElement;
246
+ return annotations.filter((annotation) => annotation.data.viewportId ===
247
+ viewportId);
248
+ };
249
+ this.cancel = (element) => {
250
+ if (this._editData) {
251
+ this._endManipulation(element, { suppressEvents: false });
252
+ }
253
+ };
254
+ this._endCallback = (evt) => {
255
+ const { element } = evt.detail;
256
+ this._endManipulation(element);
257
+ };
258
+ this._dragCallback = (evt) => {
259
+ const editData = this._editData;
260
+ if (!editData) {
261
+ return;
262
+ }
263
+ const members = editData.memberViewportIds
264
+ .map((viewportId) => this._getViewportById(viewportId))
265
+ .filter(Boolean);
266
+ if (!members.length) {
267
+ return;
268
+ }
269
+ switch (editData.operation) {
270
+ case 'translate':
271
+ this._applyTranslate(members, evt, editData);
272
+ break;
273
+ case 'rotate':
274
+ this._applyRotate(members, evt, editData);
275
+ break;
276
+ case 'slabThickness':
277
+ this._applySlabThickness(members, evt, editData);
278
+ break;
279
+ }
280
+ this._renderToolViewports();
281
+ };
282
+ }
283
+ getState() {
284
+ return {
285
+ toolGroupId: this.toolGroupId,
286
+ activeGroupId: this._activeGroupId,
287
+ activeTargetViewportId: this._activeTargetViewportId,
288
+ activeOperation: this._activeOperation,
289
+ };
290
+ }
291
+ getPlaneGroups() {
292
+ return this._getPlaneGroups().map((group) => ({
293
+ ...group,
294
+ viewportIds: [...group.viewportIds],
295
+ }));
296
+ }
297
+ refreshPlaneFamilies() {
298
+ this._mprFamilyByViewportId.clear();
299
+ this._clearPassCaches();
300
+ this._renderToolViewports();
301
+ }
302
+ onSetToolEnabled() {
303
+ if (this.configuration.alignGroupsOnEnable) {
304
+ this._alignPlaneGroups();
305
+ }
306
+ this._renderToolViewports();
307
+ }
308
+ onSetToolActive() {
309
+ this.onSetToolEnabled();
310
+ }
311
+ onSetToolPassive() {
312
+ this.onSetToolEnabled();
313
+ }
314
+ onSetToolDisabled() {
315
+ this._editData = null;
316
+ this._pendingHandle = null;
317
+ this._activeOperation = null;
318
+ this._activeGroupId = undefined;
319
+ this._activeTargetViewportId = undefined;
320
+ state.isInteractingWithTool = false;
321
+ this._renderedLines.clear();
322
+ this._mprFamilyByViewportId.clear();
323
+ this._clearPassCaches();
324
+ this._getViewportsInfo().forEach(({ viewportId, renderingEngineId }) => {
325
+ const enabledElement = getEnabledElementByIds(viewportId, renderingEngineId);
326
+ if (!enabledElement) {
327
+ return;
328
+ }
329
+ const annotations = getAnnotations(this.getToolName(), enabledElement.viewport.element);
330
+ annotations?.forEach((annotation) => {
331
+ removeAnnotation(annotation.annotationUID);
332
+ });
333
+ });
334
+ }
335
+ _getMprFamily(viewport) {
336
+ const cached = this._mprFamilyByViewportId.get(viewport.id);
337
+ if (cached) {
338
+ return cached;
339
+ }
340
+ const plane = getViewportPlane(viewport);
341
+ if (!plane) {
342
+ return null;
343
+ }
344
+ const [x, y, z] = plane.normal.map(Math.abs);
345
+ const family = z >= x && z >= y ? 'axial' : x >= y ? 'sagittal' : 'coronal';
346
+ this._mprFamilyByViewportId.set(viewport.id, family);
347
+ return family;
348
+ }
349
+ _getPlaneGroups() {
350
+ if (!this._passPlaneGroups) {
351
+ this._passPlaneGroups = this._computePlaneGroups();
352
+ this._schedulePassCacheClear();
353
+ }
354
+ return this._passPlaneGroups;
355
+ }
356
+ _computePlaneGroups() {
357
+ const planarViewports = this._getToolGroupViewports().filter((viewport) => this._isPlanarViewport(viewport));
358
+ if (this.configuration.perViewportLines) {
359
+ return planarViewports
360
+ .map((viewport) => {
361
+ const family = this._getMprFamily(viewport);
362
+ const frameOfReferenceUID = viewport.getFrameOfReferenceUID?.();
363
+ if (!family || !frameOfReferenceUID) {
364
+ return null;
365
+ }
366
+ return {
367
+ id: viewport.id,
368
+ family,
369
+ frameOfReferenceUID,
370
+ viewportIds: [viewport.id],
371
+ leaderViewportId: viewport.id,
372
+ };
373
+ })
374
+ .filter(Boolean);
375
+ }
376
+ const groups = new Map();
377
+ planarViewports.forEach((viewport) => {
378
+ const family = this._getMprFamily(viewport);
379
+ const frameOfReferenceUID = viewport.getFrameOfReferenceUID?.();
380
+ if (!family || !frameOfReferenceUID) {
381
+ return;
382
+ }
383
+ const id = `${frameOfReferenceUID}:${family}`;
384
+ let group = groups.get(id);
385
+ if (!group) {
386
+ group = {
387
+ id,
388
+ family,
389
+ frameOfReferenceUID,
390
+ viewportIds: [],
391
+ leaderViewportId: viewport.id,
392
+ };
393
+ groups.set(id, group);
394
+ }
395
+ group.viewportIds.push(viewport.id);
396
+ const currentLeader = this._getViewportById(group.leaderViewportId);
397
+ if (!this._isVolumeModeViewport(currentLeader) &&
398
+ this._isVolumeModeViewport(viewport)) {
399
+ group.leaderViewportId = viewport.id;
400
+ }
401
+ });
402
+ return [...groups.values()];
403
+ }
404
+ _alignPlaneGroups() {
405
+ const previousInteracting = state.isInteractingWithTool;
406
+ state.isInteractingWithTool = true;
407
+ try {
408
+ this._getPlaneGroups().forEach((group) => {
409
+ if (group.viewportIds.length < 2) {
410
+ return;
411
+ }
412
+ const leader = this._getViewportById(group.leaderViewportId);
413
+ const leaderPlane = leader && getViewportPlane(leader);
414
+ if (!leaderPlane) {
415
+ return;
416
+ }
417
+ group.viewportIds.forEach((viewportId) => {
418
+ if (viewportId === group.leaderViewportId) {
419
+ return;
420
+ }
421
+ const member = this._getViewportById(viewportId);
422
+ if (!member || !this._isPlanarViewport(member)) {
423
+ return;
424
+ }
425
+ if (navigatePlanarViewportToPoint(member, leaderPlane.point)) {
426
+ member.render();
427
+ }
428
+ });
429
+ });
430
+ }
431
+ finally {
432
+ state.isInteractingWithTool = previousInteracting;
433
+ }
434
+ }
435
+ _computeLinesForTarget(targetViewport) {
436
+ if (!targetViewport || !this._isPlanarViewport(targetViewport)) {
437
+ return [];
438
+ }
439
+ const targetPlane = getViewportPlane(targetViewport);
440
+ if (!targetPlane) {
441
+ return [];
442
+ }
443
+ const targetFrameOfReferenceUID = targetViewport.getFrameOfReferenceUID?.();
444
+ if (!targetFrameOfReferenceUID) {
445
+ return [];
446
+ }
447
+ const lines = [];
448
+ this._getPlaneGroups().forEach((group) => {
449
+ if (group.frameOfReferenceUID !== targetFrameOfReferenceUID ||
450
+ group.viewportIds.includes(targetViewport.id)) {
451
+ return;
452
+ }
453
+ const lineInfo = this._computeLineForGroup(targetViewport, targetPlane, group);
454
+ if (lineInfo) {
455
+ lines.push(lineInfo);
456
+ }
457
+ });
458
+ return lines;
459
+ }
460
+ _computeLineForGroup(targetViewport, targetPlane, group) {
461
+ const leaderViewport = this._getViewportById(group.leaderViewportId);
462
+ if (!leaderViewport) {
463
+ return null;
464
+ }
465
+ const leaderPlane = getViewportPlane(leaderViewport);
466
+ if (!leaderPlane) {
467
+ return null;
468
+ }
469
+ const line = intersectPlanes(targetPlane, leaderPlane);
470
+ if (!line) {
471
+ return null;
472
+ }
473
+ const canvasPoints = clipWorldLineToViewportCanvas(line, targetViewport);
474
+ if (!canvasPoints) {
475
+ return null;
476
+ }
477
+ const { familyColors, getLineColor } = this.configuration;
478
+ const color = getLineColor?.(group.family, group.id, targetViewport.id) ??
479
+ familyColors?.[group.family] ??
480
+ 'rgb(0, 200, 200)';
481
+ const isActive = this._activeGroupId === group.id &&
482
+ this._activeTargetViewportId === targetViewport.id;
483
+ const lineInfo = {
484
+ groupId: group.id,
485
+ family: group.family,
486
+ leaderViewportId: group.leaderViewportId,
487
+ memberViewportIds: [...group.viewportIds],
488
+ targetViewportId: targetViewport.id,
489
+ line,
490
+ leaderPlane,
491
+ canvasPoints,
492
+ slabLineSegments: [],
493
+ boundaryLineSegments: [],
494
+ rotateHandles: [],
495
+ slabHandles: [],
496
+ color,
497
+ isActive,
498
+ };
499
+ this._appendSlabGeometry(lineInfo, targetViewport, targetPlane, leaderViewport, leaderPlane);
500
+ this._appendBoundaryGeometry(lineInfo, targetViewport, targetPlane, leaderViewport, leaderPlane);
501
+ if (isActive) {
502
+ this._appendHandles(lineInfo, targetViewport, leaderViewport);
503
+ }
504
+ return lineInfo;
505
+ }
506
+ _clipOffsetPlaneIntersection(targetViewport, targetPlane, leaderPlane, offset) {
507
+ const offsetPlane = {
508
+ normal: leaderPlane.normal,
509
+ point: [
510
+ leaderPlane.point[0] + leaderPlane.normal[0] * offset,
511
+ leaderPlane.point[1] + leaderPlane.normal[1] * offset,
512
+ leaderPlane.point[2] + leaderPlane.normal[2] * offset,
513
+ ],
514
+ };
515
+ const line = intersectPlanes(targetPlane, offsetPlane);
516
+ if (!line) {
517
+ return null;
518
+ }
519
+ return clipWorldLineToViewportCanvas(line, targetViewport);
520
+ }
521
+ _appendSlabGeometry(lineInfo, targetViewport, targetPlane, leaderViewport, leaderPlane) {
522
+ if (!this.configuration.showSlabThickness ||
523
+ !this._isVolumeModeViewport(leaderViewport)) {
524
+ return;
525
+ }
526
+ const slabThickness = this._getSourceSlabThickness(leaderViewport);
527
+ if (!slabThickness ||
528
+ slabThickness <= RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS) {
529
+ return;
530
+ }
531
+ [slabThickness / 2, -slabThickness / 2].forEach((offset) => {
532
+ const segment = this._clipOffsetPlaneIntersection(targetViewport, targetPlane, leaderPlane, offset);
533
+ if (segment) {
534
+ lineInfo.slabLineSegments.push(segment);
535
+ }
536
+ });
537
+ }
538
+ _appendBoundaryGeometry(lineInfo, targetViewport, targetPlane, leaderViewport, leaderPlane) {
539
+ const { showBoundaryLines, showIntermediateLines } = this.configuration;
540
+ if (!showBoundaryLines && !showIntermediateLines) {
541
+ return;
542
+ }
543
+ const extent = this._getSourceExtentAlongNormal(leaderViewport, leaderPlane);
544
+ if (!extent) {
545
+ return;
546
+ }
547
+ const { minOffset, maxOffset, spacing } = extent;
548
+ const offsets = [];
549
+ if (showBoundaryLines) {
550
+ offsets.push(minOffset, maxOffset);
551
+ }
552
+ if (showIntermediateLines && spacing > 0) {
553
+ const range = maxOffset - minOffset;
554
+ const step = Math.max(spacing, range / MAX_INTERMEDIATE_LINES);
555
+ for (let offset = Math.ceil(minOffset / step) * step; offset < maxOffset; offset += step) {
556
+ if (Math.abs(offset) > 1e-6) {
557
+ offsets.push(offset);
558
+ }
559
+ }
560
+ }
561
+ offsets.forEach((offset) => {
562
+ const segment = this._clipOffsetPlaneIntersection(targetViewport, targetPlane, leaderPlane, offset);
563
+ if (segment) {
564
+ lineInfo.boundaryLineSegments.push(segment);
565
+ }
566
+ });
567
+ }
568
+ _getSourceExtentAlongNormal(sourceViewport, sourcePlane) {
569
+ let imageData;
570
+ try {
571
+ imageData = sourceViewport.getImageData?.();
572
+ }
573
+ catch {
574
+ return null;
575
+ }
576
+ const bounds = imageData?.imageData?.getBounds?.();
577
+ if (!bounds || bounds.length !== 6) {
578
+ return null;
579
+ }
580
+ const [xMin, xMax, yMin, yMax, zMin, zMax] = bounds;
581
+ const corners = [
582
+ [xMin, yMin, zMin],
583
+ [xMax, yMin, zMin],
584
+ [xMin, yMax, zMin],
585
+ [xMax, yMax, zMin],
586
+ [xMin, yMin, zMax],
587
+ [xMax, yMin, zMax],
588
+ [xMin, yMax, zMax],
589
+ [xMax, yMax, zMax],
590
+ ];
591
+ let minOffset = Infinity;
592
+ let maxOffset = -Infinity;
593
+ corners.forEach((corner) => {
594
+ const distance = distancePointToPlane(corner, sourcePlane);
595
+ minOffset = Math.min(minOffset, distance);
596
+ maxOffset = Math.max(maxOffset, distance);
597
+ });
598
+ if (!Number.isFinite(minOffset) || !Number.isFinite(maxOffset)) {
599
+ return null;
600
+ }
601
+ const spacingArray = imageData?.spacing ?? [];
602
+ const spacing = Math.min(...spacingArray.filter((s) => Number.isFinite(s) && s > 0), Infinity);
603
+ return {
604
+ minOffset,
605
+ maxOffset,
606
+ spacing: Number.isFinite(spacing) ? spacing : 0,
607
+ };
608
+ }
609
+ _appendHandles(lineInfo, targetViewport, leaderViewport) {
610
+ const { rotatable, slabThicknessControls, showSlabThickness } = this.configuration;
611
+ const [start, end] = lineInfo.canvasPoints;
612
+ const volumeMode = this._isVolumeModeViewport(leaderViewport);
613
+ if (rotatable && volumeMode) {
614
+ lineInfo.rotateHandles = [0.25, 0.75].map((fraction) => [
615
+ start[0] + (end[0] - start[0]) * fraction,
616
+ start[1] + (end[1] - start[1]) * fraction,
617
+ ]);
618
+ }
619
+ if (slabThicknessControls && showSlabThickness && volumeMode) {
620
+ lineInfo.slabHandles = this._computeSlabHandles(lineInfo, targetViewport, leaderViewport);
621
+ }
622
+ }
623
+ _computeSlabHandles(lineInfo, targetViewport, leaderViewport) {
624
+ const [start, end] = lineInfo.canvasPoints;
625
+ const anchorCanvas = [
626
+ start[0] + (end[0] - start[0]) * SLAB_HANDLE_LINE_FRACTION,
627
+ start[1] + (end[1] - start[1]) * SLAB_HANDLE_LINE_FRACTION,
628
+ ];
629
+ const slabThickness = this._getSourceSlabThickness(leaderViewport);
630
+ const anchorWorld = targetViewport.canvasToWorld(anchorCanvas);
631
+ const { normal } = lineInfo.leaderPlane;
632
+ const plusWorld = [
633
+ anchorWorld[0] + normal[0] * (slabThickness / 2),
634
+ anchorWorld[1] + normal[1] * (slabThickness / 2),
635
+ anchorWorld[2] + normal[2] * (slabThickness / 2),
636
+ ];
637
+ const plusCanvas = targetViewport.worldToCanvas(plusWorld);
638
+ const canvasOffset = Math.hypot(plusCanvas[0] - anchorCanvas[0], plusCanvas[1] - anchorCanvas[1]);
639
+ if (canvasOffset >= MIN_SLAB_HANDLE_CANVAS_OFFSET) {
640
+ const minusWorld = [
641
+ anchorWorld[0] - normal[0] * (slabThickness / 2),
642
+ anchorWorld[1] - normal[1] * (slabThickness / 2),
643
+ anchorWorld[2] - normal[2] * (slabThickness / 2),
644
+ ];
645
+ return [plusCanvas, targetViewport.worldToCanvas(minusWorld)];
646
+ }
647
+ const direction = vec2.normalize(vec2.create(), vec2.subtract(vec2.create(), end, start));
648
+ const perpendicular = [-direction[1], direction[0]];
649
+ return [
650
+ [
651
+ anchorCanvas[0] + perpendicular[0] * MIN_SLAB_HANDLE_CANVAS_OFFSET,
652
+ anchorCanvas[1] + perpendicular[1] * MIN_SLAB_HANDLE_CANVAS_OFFSET,
653
+ ],
654
+ [
655
+ anchorCanvas[0] - perpendicular[0] * MIN_SLAB_HANDLE_CANVAS_OFFSET,
656
+ anchorCanvas[1] - perpendicular[1] * MIN_SLAB_HANDLE_CANVAS_OFFSET,
657
+ ],
658
+ ];
659
+ }
660
+ getHandleNearImagePoint(element, _annotation, canvasCoords, proximity) {
661
+ this._pendingHandle = null;
662
+ const enabledElement = getEnabledElement(element);
663
+ if (!enabledElement) {
664
+ return undefined;
665
+ }
666
+ const lines = this._renderedLines.get(enabledElement.viewport.id) ?? [];
667
+ for (const lineInfo of lines) {
668
+ const nearRotate = lineInfo.rotateHandles.some((handle) => Math.hypot(handle[0] - canvasCoords[0], handle[1] - canvasCoords[1]) <=
669
+ this.configuration.handleRadius + proximity);
670
+ if (nearRotate) {
671
+ this._pendingHandle = { operation: 'rotate', lineInfo };
672
+ return lineInfo.line.point;
673
+ }
674
+ const nearSlab = lineInfo.slabHandles.some((handle) => Math.hypot(handle[0] - canvasCoords[0], handle[1] - canvasCoords[1]) <=
675
+ this.configuration.handleRadius + proximity);
676
+ if (nearSlab) {
677
+ this._pendingHandle = { operation: 'slabThickness', lineInfo };
678
+ return lineInfo.line.point;
679
+ }
680
+ }
681
+ return undefined;
682
+ }
683
+ _startManipulation(element, lineInfo, operation) {
684
+ const targetViewport = this._getViewportById(lineInfo.targetViewportId);
685
+ this._editData = {
686
+ targetViewportId: lineInfo.targetViewportId,
687
+ groupId: lineInfo.groupId,
688
+ family: lineInfo.family,
689
+ memberViewportIds: [...lineInfo.memberViewportIds],
690
+ operation,
691
+ };
692
+ if (operation === 'rotate' && targetViewport) {
693
+ const targetPlane = getViewportPlane(targetViewport);
694
+ this._editData.rotationAxis = targetPlane?.normal;
695
+ this._editData.pivotWorld = this._resolveRotationPivot(targetViewport, lineInfo);
696
+ }
697
+ this._activeOperation = operation;
698
+ hideElementCursor(element);
699
+ this._activateModify(element);
700
+ triggerEvent(eventTarget, Events.SLICE_INTERSECTION_MANIPULATION_STARTED, this._getManipulationDetail(operation));
701
+ }
702
+ _endManipulation(element, { suppressEvents = false } = {}) {
703
+ const editData = this._editData;
704
+ this._deactivateModify(element);
705
+ resetElementCursor(element);
706
+ this._editData = null;
707
+ this._activeOperation = null;
708
+ if (editData && !suppressEvents) {
709
+ triggerEvent(eventTarget, Events.SLICE_INTERSECTION_MANIPULATION_ENDED, this._getManipulationDetail(editData.operation, editData));
710
+ }
711
+ this._renderToolViewports();
712
+ }
713
+ _applyTranslate(members, evt, editData) {
714
+ const startWorld = evt.detail.startPoints?.world;
715
+ const currentWorld = evt.detail.currentPoints?.world;
716
+ const deltaWorld = evt.detail.deltaPoints?.world;
717
+ const totalDelta = startWorld && currentWorld
718
+ ? vec3.subtract(vec3.create(), currentWorld, startWorld)
719
+ : deltaWorld;
720
+ if (!totalDelta) {
721
+ return;
722
+ }
723
+ const usingTotalDelta = !!(startWorld && currentWorld);
724
+ const memberStartPlanePoints = editData?.memberStartPlanePoints ?? new Map();
725
+ if (editData) {
726
+ editData.memberStartPlanePoints = memberStartPlanePoints;
727
+ }
728
+ members.forEach((member) => {
729
+ const memberPlane = getViewportPlane(member);
730
+ if (!memberPlane) {
731
+ return;
732
+ }
733
+ if (!usingTotalDelta) {
734
+ const scrollDistance = vec3.dot(totalDelta, memberPlane.normal);
735
+ if (translateViewportAlongNormal(member, scrollDistance)) {
736
+ member.render();
737
+ }
738
+ return;
739
+ }
740
+ let startPlanePoint = memberStartPlanePoints.get(member.id);
741
+ if (!startPlanePoint) {
742
+ startPlanePoint = [...memberPlane.point];
743
+ memberStartPlanePoints.set(member.id, startPlanePoint);
744
+ }
745
+ const distance = vec3.dot(totalDelta, memberPlane.normal);
746
+ const targetPoint = [
747
+ startPlanePoint[0] + memberPlane.normal[0] * distance,
748
+ startPlanePoint[1] + memberPlane.normal[1] * distance,
749
+ startPlanePoint[2] + memberPlane.normal[2] * distance,
750
+ ];
751
+ if (navigatePlanarViewportToPoint(member, targetPoint)) {
752
+ member.render();
753
+ }
754
+ });
755
+ }
756
+ _applyRotate(members, evt, editData) {
757
+ const { pivotWorld, rotationAxis, targetViewportId } = editData;
758
+ if (!pivotWorld || !rotationAxis) {
759
+ return;
760
+ }
761
+ const targetViewport = this._getViewportById(targetViewportId);
762
+ if (!targetViewport) {
763
+ return;
764
+ }
765
+ const eventDetail = evt.detail;
766
+ const currentCanvas = eventDetail.currentPoints?.canvas;
767
+ const deltaCanvas = eventDetail.deltaPoints?.canvas;
768
+ if (!currentCanvas || !deltaCanvas) {
769
+ return;
770
+ }
771
+ const pivotCanvas = targetViewport.worldToCanvas(pivotWorld);
772
+ const previousCanvas = [
773
+ currentCanvas[0] - deltaCanvas[0],
774
+ currentCanvas[1] - deltaCanvas[1],
775
+ ];
776
+ const dir1 = vec2.subtract(vec2.create(), previousCanvas, pivotCanvas);
777
+ const dir2 = vec2.subtract(vec2.create(), currentCanvas, pivotCanvas);
778
+ if (vec2.length(dir1) < 1e-6 || vec2.length(dir2) < 1e-6) {
779
+ return;
780
+ }
781
+ let angle = vec2.angle(dir1, dir2);
782
+ if (!Number.isFinite(angle) || angle === 0) {
783
+ return;
784
+ }
785
+ const cross = dir1[0] * dir2[1] - dir1[1] * dir2[0];
786
+ if (cross > 0) {
787
+ angle *= -1;
788
+ }
789
+ angle = Math.round(angle * 100) / 100;
790
+ if (angle === 0) {
791
+ return;
792
+ }
793
+ const normalizedAxis = vec3.normalize(vec3.create(), rotationAxis);
794
+ if (vec3.length(normalizedAxis) < 1e-10) {
795
+ return;
796
+ }
797
+ const rotation = mat4.fromRotation(mat4.create(), angle, normalizedAxis);
798
+ if (!rotation) {
799
+ return;
800
+ }
801
+ editData.memberStartScales ??= new Map();
802
+ members.forEach((member) => {
803
+ if (!this._isVolumeModeViewport(member)) {
804
+ return;
805
+ }
806
+ const { viewPlaneNormal, viewUp } = getViewportICamera(member);
807
+ if (!viewPlaneNormal || !viewUp) {
808
+ return;
809
+ }
810
+ const scalableMember = member;
811
+ if (!editData.memberStartScales.has(member.id)) {
812
+ editData.memberStartScales.set(member.id, scalableMember.getScale?.() ?? null);
813
+ }
814
+ const newViewPlaneNormal = vec3.transformMat4(vec3.create(), viewPlaneNormal, rotation);
815
+ const newViewUp = vec3.transformMat4(vec3.create(), viewUp, rotation);
816
+ member.setViewReference({
817
+ viewPlaneNormal: [
818
+ newViewPlaneNormal[0],
819
+ newViewPlaneNormal[1],
820
+ newViewPlaneNormal[2],
821
+ ],
822
+ viewUp: [newViewUp[0], newViewUp[1], newViewUp[2]],
823
+ cameraFocalPoint: [pivotWorld[0], pivotWorld[1], pivotWorld[2]],
824
+ });
825
+ const startScale = editData.memberStartScales.get(member.id);
826
+ if (startScale) {
827
+ scalableMember.setScale?.(startScale);
828
+ }
829
+ member.render();
830
+ });
831
+ }
832
+ _applySlabThickness(members, evt, editData) {
833
+ const currentWorld = evt.detail.currentPoints?.world;
834
+ if (!currentWorld) {
835
+ return;
836
+ }
837
+ const leader = members.find((member) => this._isVolumeModeViewport(member));
838
+ const leaderPlane = leader && getViewportPlane(leader);
839
+ if (!leaderPlane) {
840
+ return;
841
+ }
842
+ const slabThickness = Math.max(RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS, Math.abs(distancePointToPlane(currentWorld, leaderPlane)) * 2);
843
+ let changed = false;
844
+ members.forEach((member) => {
845
+ if (this._setViewportSlabThickness(member, slabThickness)) {
846
+ changed = true;
847
+ }
848
+ });
849
+ if (changed) {
850
+ triggerEvent(eventTarget, Events.SLICE_INTERSECTION_SLAB_THICKNESS_CHANGED, this._getManipulationDetail('slabThickness', editData));
851
+ }
852
+ }
853
+ _setViewportSlabThickness(viewport, slabThickness) {
854
+ if (!this._isVolumeModeViewport(viewport)) {
855
+ return false;
856
+ }
857
+ const dataId = this._getSourceDataId(viewport);
858
+ if (!dataId) {
859
+ return false;
860
+ }
861
+ const currentSlabThickness = this._getSourceSlabThickness(viewport);
862
+ if (Math.abs(currentSlabThickness - slabThickness) < 1e-6) {
863
+ return false;
864
+ }
865
+ let blendMode = this.configuration.slabThicknessBlendMode;
866
+ if (slabThickness <= RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS) {
867
+ blendMode = Enums.BlendModes.COMPOSITE;
868
+ }
869
+ viewport.setDisplaySetPresentation(dataId, {
870
+ slabThickness,
871
+ blendMode,
872
+ });
873
+ viewport.render();
874
+ return true;
875
+ }
876
+ _isPlanarViewport(viewport) {
877
+ return (viewport?.type === ViewportType.PLANAR_NEXT &&
878
+ csUtils.isGenericViewport(viewport));
879
+ }
880
+ _isVolumeModeViewport(viewport) {
881
+ if (!viewport || !this._isPlanarViewport(viewport)) {
882
+ return false;
883
+ }
884
+ try {
885
+ return viewport.getCurrentMode?.() === 'volume';
886
+ }
887
+ catch {
888
+ return false;
889
+ }
890
+ }
891
+ _getSourceDataId(viewport) {
892
+ if (!csUtils.viewportSupportsDisplaySetPresentation(viewport)) {
893
+ return undefined;
894
+ }
895
+ return viewport.getSourceDataId();
896
+ }
897
+ _getSourceSlabThickness(viewport) {
898
+ const dataId = this._getSourceDataId(viewport);
899
+ if (!dataId) {
900
+ return RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS;
901
+ }
902
+ const presentation = viewport.getDisplaySetPresentation(dataId);
903
+ const slabThickness = presentation?.slabThickness;
904
+ return Number.isFinite(slabThickness) && slabThickness > 0
905
+ ? slabThickness
906
+ : RENDERING_DEFAULTS.MINIMUM_SLAB_THICKNESS;
907
+ }
908
+ _resolveRotationPivot(targetViewport, lineInfo) {
909
+ const { rotationPivotPolicy } = this.configuration;
910
+ if (rotationPivotPolicy === 'worldCrosshairPointIfAvailable') {
911
+ const worldCrosshairInstance = getToolGroup(this.toolGroupId)?.getToolInstance?.('WorldCrosshair');
912
+ const worldPoint = worldCrosshairInstance?.getWorldPoint?.();
913
+ if (worldPoint) {
914
+ return worldPoint;
915
+ }
916
+ }
917
+ if (rotationPivotPolicy === 'viewportCenter') {
918
+ const { focalPoint } = getViewportICamera(targetViewport);
919
+ if (focalPoint) {
920
+ return focalPoint;
921
+ }
922
+ }
923
+ const { focalPoint } = getViewportICamera(targetViewport);
924
+ return this._closestPointOnLine(lineInfo.line, focalPoint ?? lineInfo.line.point);
925
+ }
926
+ _closestPointOnLine(line, point) {
927
+ const toPoint = vec3.subtract(vec3.create(), point, line.point);
928
+ const projection = vec3.dot(toPoint, line.direction);
929
+ return [
930
+ line.point[0] + line.direction[0] * projection,
931
+ line.point[1] + line.direction[1] * projection,
932
+ line.point[2] + line.direction[2] * projection,
933
+ ];
934
+ }
935
+ _centerViewportOnPoint(viewport, worldPoint) {
936
+ if (!this._isPlanarViewport(viewport)) {
937
+ return;
938
+ }
939
+ viewport.setViewState({
940
+ anchorWorld: [worldPoint[0], worldPoint[1], worldPoint[2]],
941
+ anchorCanvas: [0.5, 0.5],
942
+ });
943
+ viewport.render();
944
+ }
945
+ _selectLine(lineInfo) {
946
+ this._activeGroupId = lineInfo.groupId;
947
+ this._activeTargetViewportId = lineInfo.targetViewportId;
948
+ triggerEvent(eventTarget, Events.SLICE_INTERSECTION_LINE_SELECTED, {
949
+ toolGroupId: this.toolGroupId,
950
+ groupId: lineInfo.groupId,
951
+ targetViewportId: lineInfo.targetViewportId,
952
+ });
953
+ }
954
+ _findLineNear(viewportId, canvasCoords, proximity, includeHandles = false) {
955
+ const lines = this._renderedLines.get(viewportId) ?? [];
956
+ const handleReach = this.configuration.handleRadius + proximity;
957
+ for (const lineInfo of lines) {
958
+ const distance = lineSegment.distanceToPoint(lineInfo.canvasPoints[0], lineInfo.canvasPoints[1], canvasCoords);
959
+ if (distance <= proximity) {
960
+ return lineInfo;
961
+ }
962
+ if (includeHandles) {
963
+ const nearHandle = [
964
+ ...lineInfo.rotateHandles,
965
+ ...lineInfo.slabHandles,
966
+ ].some((handle) => Math.hypot(handle[0] - canvasCoords[0], handle[1] - canvasCoords[1]) <= handleReach);
967
+ if (nearHandle) {
968
+ return lineInfo;
969
+ }
970
+ }
971
+ }
972
+ return null;
973
+ }
974
+ _getViewportsInfo() {
975
+ return getToolGroup(this.toolGroupId)?.viewportsInfo ?? [];
976
+ }
977
+ _getToolGroupViewports() {
978
+ return this._getViewportsInfo()
979
+ .map(({ viewportId, renderingEngineId }) => getEnabledElementByIds(viewportId, renderingEngineId)?.viewport)
980
+ .filter(Boolean);
981
+ }
982
+ _getViewportById(viewportId) {
983
+ if (!viewportId) {
984
+ return undefined;
985
+ }
986
+ if (!this._passViewportsById) {
987
+ this._passViewportsById = new Map(this._getToolGroupViewports().map((viewport) => [viewport.id, viewport]));
988
+ this._schedulePassCacheClear();
989
+ }
990
+ return this._passViewportsById.get(viewportId);
991
+ }
992
+ _clearPassCaches() {
993
+ this._passViewportsById = null;
994
+ this._passPlaneGroups = null;
995
+ }
996
+ _schedulePassCacheClear() {
997
+ if (this._passCacheClearScheduled) {
998
+ return;
999
+ }
1000
+ this._passCacheClearScheduled = true;
1001
+ queueMicrotask(() => {
1002
+ this._passCacheClearScheduled = false;
1003
+ this._clearPassCaches();
1004
+ });
1005
+ }
1006
+ _renderToolViewports() {
1007
+ const viewportIds = this._getViewportsInfo().map(({ viewportId }) => viewportId);
1008
+ if (viewportIds.length) {
1009
+ triggerAnnotationRenderForViewportIds(viewportIds);
1010
+ }
1011
+ }
1012
+ _getOrCreateViewportAnnotation(viewport) {
1013
+ const { element } = viewport;
1014
+ if (!element) {
1015
+ return null;
1016
+ }
1017
+ const annotations = getAnnotations(this.getToolName(), element);
1018
+ const existing = annotations?.find((annotation) => annotation.data.viewportId ===
1019
+ viewport.id);
1020
+ if (existing) {
1021
+ return existing;
1022
+ }
1023
+ const annotation = {
1024
+ highlighted: false,
1025
+ invalidated: false,
1026
+ metadata: {
1027
+ toolName: this.getToolName(),
1028
+ FrameOfReferenceUID: viewport.getFrameOfReferenceUID?.(),
1029
+ },
1030
+ data: {
1031
+ viewportId: viewport.id,
1032
+ handles: { points: [] },
1033
+ },
1034
+ };
1035
+ addAnnotation(annotation, element);
1036
+ return annotation;
1037
+ }
1038
+ _createDetachedAnnotation() {
1039
+ return {
1040
+ annotationUID: csUtils.uuidv4(),
1041
+ highlighted: false,
1042
+ invalidated: false,
1043
+ metadata: {
1044
+ toolName: this.getToolName(),
1045
+ },
1046
+ data: {
1047
+ viewportId: '',
1048
+ handles: { points: [] },
1049
+ },
1050
+ };
1051
+ }
1052
+ _getManipulationDetail(operation, editData = this._editData) {
1053
+ return {
1054
+ toolGroupId: this.toolGroupId,
1055
+ groupId: editData?.groupId ?? this._activeGroupId ?? '',
1056
+ family: editData?.family,
1057
+ viewportIds: editData?.memberViewportIds ?? [],
1058
+ targetViewportId: editData?.targetViewportId ?? this._activeTargetViewportId ?? '',
1059
+ operation,
1060
+ };
1061
+ }
1062
+ }
1063
+ SliceIntersectionTool.toolLabel = 'Slice Intersections';
1064
+ SliceIntersectionTool.toolName = 'SliceIntersection';
1065
+ export default SliceIntersectionTool;