import { Point3, Point2, fromPoint3ToPoint2 } from './mathUtils'; import { utilities as csUtils, Types as CoreTypes, getEnabledElement, } from '@cornerstonejs/core'; import { AnnotationTool, utilities, Types, annotation, drawing, } from '@cornerstonejs/tools'; const { drawHandles, drawLinkedTextBox, drawLine: drawLineSvg, drawCircle: drawCircleSvg, } = drawing; import { calculateMidpoint, vectorSubtract, vectorAdd, calculateDistance, } from './mathUtils'; import { PublicToolProps, ToolProps, EventTypes, SVGDrawingHelper, } from '@cornerstonejs/tools/dist/esm/types'; // 注解数据接口 interface FindMidpointMeasurementAnnotation extends Types.Annotation { data: { handles: { points: CoreTypes.Point3[]; // [A, B, C, D] - 4个点 activeHandleIndex: number | null; }; cachedStats?: { [targetId: string]: { midPointM1: CoreTypes.Point3; // AB中点 midPointM2: CoreTypes.Point3; // CD中点 finalMidpoint: CoreTypes.Point3; // 最终中点 (M1+M2)/2 }; }; }; // 选中状态 isSelected?: boolean; } // 类型断言函数 function isFindMidpointMeasurementAnnotation(annotation: Types.Annotation): annotation is FindMidpointMeasurementAnnotation { return annotation.metadata?.toolName === 'FindMidpointMeasurementTool'; } // 导出的注解数据接口 export interface ExportedFindMidpointData { points: CoreTypes.Point3[]; // [A, B, C, D] 4个点的世界坐标 midpoint: { midPointM1: CoreTypes.Point3; // AB中点 midPointM2: CoreTypes.Point3; // CD中点 finalMidpoint: CoreTypes.Point3; // 最终中点 }; metadata: { viewPlaneNormal: CoreTypes.Point3; viewUp: CoreTypes.Point3; FrameOfReferenceUID: string; referencedImageId: string; }; } export default class FindMidpointMeasurementTool extends AnnotationTool { static toolName = 'FindMidpointMeasurementTool'; editData: { annotation: Types.Annotation; viewportIdsToRender: string[]; handleIndex?: number; newAnnotation?: boolean; hasMoved?: boolean; movingWholeTool?: boolean; // 是否移动整个工具 wholeToolOffset?: CoreTypes.Point2; // 工具移动偏移量 } | null = null; isDrawing: boolean = false; /** * 创建一个预设的注解,带有4个默认点 * @param element HTML元素 * @param viewport Viewport实例 */ static createDefaultAnnotation( element: HTMLDivElement, viewport: CoreTypes.IStackViewport | CoreTypes.IVolumeViewport ): FindMidpointMeasurementAnnotation { const enabledElement = getEnabledElement(element); if (!enabledElement) { throw new Error('Element is not enabled'); } // 获取viewport的尺寸 const canvas = viewport.canvas; const { width, height } = canvas; const centerX = width / 2; const centerY = height / 2; // 创建4个canvas坐标点 // 线段AB: 水平线,在中心上方 const lineAY = centerY - 100; const lineAStartX = centerX - 80; const lineAEndX = centerX + 80; // 线段CD: 水平线,在中心下方 const lineCY = centerY + 100; const lineCStartX = centerX - 60; const lineCEndX = centerX + 60; const canvasPoints: CoreTypes.Point2[] = [ // 线段AB: 点A, 点B [lineAStartX, lineAY], [lineAEndX, lineAY], // 线段CD: 点C, 点D [lineCStartX, lineCY], [lineCEndX, lineCY], ]; // 转换为world坐标 const worldPoints = canvasPoints.map((canvasPoint) => viewport.canvasToWorld(canvasPoint) ); const camera = viewport.getCamera(); const { viewPlaneNormal, viewUp } = camera; if (viewPlaneNormal === undefined) { throw new Error('viewPlaneNormal is undefined'); } if (viewUp === undefined) { throw new Error('viewUp is undefined'); } const annotationData = { invalidated: true, highlighted: false, metadata: { viewPlaneNormal: [...viewPlaneNormal] as CoreTypes.Point3, viewUp: [...viewUp] as CoreTypes.Point3, FrameOfReferenceUID: viewport.getFrameOfReferenceUID(), referencedImageId: viewport.getCurrentImageId?.() || '', toolName: FindMidpointMeasurementTool.toolName, }, data: { label: '', handles: { points: worldPoints, activeHandleIndex: null, }, cachedStats: {}, }, } as FindMidpointMeasurementAnnotation; return annotationData; } /** * 导出注解数据 * @param annotation 要导出的注解 * @returns 导出的数据对象 */ static exportAnnotationData( annotation: FindMidpointMeasurementAnnotation, viewport: CoreTypes.IStackViewport | CoreTypes.IVolumeViewport ): ExportedFindMidpointData | null { const targetId = `imageId:${viewport.getCurrentImageId?.() || ''}`; const cachedStats = annotation.data.cachedStats?.[targetId]; if (!cachedStats) { return null; } return { points: [...annotation.data.handles.points], midpoint: { midPointM1: cachedStats.midPointM1, midPointM2: cachedStats.midPointM2, finalMidpoint: cachedStats.finalMidpoint, }, metadata: { viewPlaneNormal: annotation.metadata?.viewPlaneNormal || [0, 0, 1], viewUp: annotation.metadata?.viewUp || [0, 1, 0], FrameOfReferenceUID: annotation.metadata?.FrameOfReferenceUID || '', referencedImageId: annotation.metadata?.referencedImageId || '', }, }; } /** * 从导出的数据恢复注解 * @param exportedData 导出的数据 * @param element HTML元素 * @param viewport Viewport实例 * @returns 恢复的注解 */ static restoreFromExportedData( exportedData: ExportedFindMidpointData, element: HTMLDivElement, viewport: CoreTypes.IStackViewport | CoreTypes.IVolumeViewport ): FindMidpointMeasurementAnnotation { const enabledElement = getEnabledElement(element); if (!enabledElement) { throw new Error('Element is not enabled'); } const annotationData = { invalidated: true, highlighted: false, metadata: { ...exportedData.metadata, toolName: FindMidpointMeasurementTool.toolName, }, data: { label: '', handles: { points: [...exportedData.points], activeHandleIndex: null, }, cachedStats: {}, }, } as FindMidpointMeasurementAnnotation; return annotationData; } constructor( toolProps: PublicToolProps = {}, defaultToolProps: ToolProps = { supportedInteractionTypes: ['Mouse', 'Touch'], configuration: { shadow: true, preventHandleOutsideImage: false, }, } ) { super(toolProps, defaultToolProps); } /** * 添加新注解 - 禁用此功能,因为我们只使用预设的注解 */ addNewAnnotation( evt: EventTypes.InteractionEventType ): Types.Annotation { // 不创建新注解,直接返回空对象 evt.preventDefault(); return {} as Types.Annotation; } /** * 处理 控制点/端点 选中回调 */ handleSelectedCallback( evt: EventTypes.InteractionEventType, annotation: Types.Annotation ): void { // 实现手柄选中逻辑 if (isFindMidpointMeasurementAnnotation(annotation)) { annotation.highlighted = true; } } /** * 工具选中回调 */ toolSelectedCallback( evt: EventTypes.InteractionEventType, annotation: Types.Annotation ): void { // 实现工具选中逻辑 if (isFindMidpointMeasurementAnnotation(annotation)) { annotation.highlighted = true; console.log(`选中了找中点测量工具`); } } /** * 检查点是否靠近工具 */ isPointNearTool( element: HTMLDivElement, annotation: Types.Annotation, canvasCoords: CoreTypes.Point2, proximity: number ): boolean { if (!isFindMidpointMeasurementAnnotation(annotation)) { return false; } const enabledElement = getEnabledElement(element); if (!enabledElement) { return false; } const { viewport } = enabledElement; const points = annotation.data.handles.points; // 检查是否靠近任意一个手柄点 for (let i = 0; i < points.length; i++) { const point = points[i]; const canvasPoint = viewport.worldToCanvas(point); const distance = Math.sqrt( Math.pow(canvasPoint[0] - canvasCoords[0], 2) + Math.pow(canvasPoint[1] - canvasCoords[1], 2) ); if (distance < proximity) { return true; } } // 检查是否靠近线段 if (points.length >= 2) { for (let i = 0; i < points.length - 1; i += 2) { if (i + 1 < points.length) { const p1Canvas = viewport.worldToCanvas(points[i]); const p2Canvas = viewport.worldToCanvas(points[i + 1]); const dist = this._distanceToSegment(canvasCoords, p1Canvas, p2Canvas); if (dist < proximity) { return true; } } } } // 检查是否靠近最终中点 const targetId = this.getTargetId(viewport); const cachedStats = targetId ? annotation.data.cachedStats?.[targetId] : undefined; if (cachedStats?.finalMidpoint) { const finalMidpointCanvas = viewport.worldToCanvas(cachedStats.finalMidpoint); const distance = Math.sqrt( Math.pow(finalMidpointCanvas[0] - canvasCoords[0], 2) + Math.pow(finalMidpointCanvas[1] - canvasCoords[1], 2) ); if (distance < proximity) { return true; } } return false; } /** * 计算点到线段的距离 */ private _distanceToSegment( point: CoreTypes.Point2, lineStart: CoreTypes.Point2, lineEnd: CoreTypes.Point2 ): number { const x = point[0]; const y = point[1]; const x1 = lineStart[0]; const y1 = lineStart[1]; const x2 = lineEnd[0]; const y2 = lineEnd[1]; const A = x - x1; const B = y - y1; const C = x2 - x1; const D = y2 - y1; const dot = A * C + B * D; const lenSq = C * C + D * D; let param = -1; if (lenSq !== 0) { param = dot / lenSq; } let xx, yy; if (param < 0) { xx = x1; yy = y1; } else if (param > 1) { xx = x2; yy = y2; } else { xx = x1 + param * C; yy = y1 + param * D; } const dx = x - xx; const dy = y - yy; return Math.sqrt(dx * dx + dy * dy); } /** * 取消操作 */ cancel(element: HTMLDivElement): string { if (this.isDrawing) { this.isDrawing = false; this._deactivateDraw(element); this._deactivateModify(element); const enabledElement = getEnabledElement(element); if (enabledElement) { const { renderingEngine } = enabledElement; const viewportIdsToRender = utilities.viewportFilters.getViewportIdsWithToolToRender( element, this.getToolName() ); utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); } this.editData = null; return this.getToolName(); } return ''; } /** * 激活绘制模式 */ _activateDraw(element: HTMLDivElement): void { element.addEventListener( 'CORNERSTONE_TOOLS_MOUSE_DRAG', this._dragCallback as EventListener ); element.addEventListener( 'CORNERSTONE_TOOLS_MOUSE_UP', this._endCallback as EventListener ); element.addEventListener( 'CORNERSTONE_TOOLS_MOUSE_CLICK', this._endCallback as EventListener ); } /** * 取消激活绘制模式 */ _deactivateDraw(element: HTMLDivElement): void { element.removeEventListener( 'CORNERSTONE_TOOLS_MOUSE_DRAG', this._dragCallback as EventListener ); element.removeEventListener( 'CORNERSTONE_TOOLS_MOUSE_UP', this._endCallback as EventListener ); element.removeEventListener( 'CORNERSTONE_TOOLS_MOUSE_CLICK', this._endCallback as EventListener ); } /** * 激活修改模式 */ _activateModify(element: HTMLDivElement): void { element.addEventListener( 'CORNERSTONE_TOOLS_MOUSE_DOWN', this._mouseDownModifyCallback as EventListener ); element.addEventListener( 'CORNERSTONE_TOOLS_MOUSE_DRAG', this._mouseDragModifyCallback as EventListener ); element.addEventListener( 'CORNERSTONE_TOOLS_MOUSE_UP', this._mouseUpModifyCallback as EventListener ); element.addEventListener( 'CORNERSTONE_TOOLS_MOUSE_MOVE', this._mouseMoveModifyCallback as EventListener ); // 添加键盘事件监听用于删除功能 element.addEventListener( 'keydown', this._keyDownCallback as EventListener ); } /** * 取消激活修改模式 */ _deactivateModify(element: HTMLDivElement): void { element.removeEventListener( 'CORNERSTONE_TOOLS_MOUSE_DOWN', this._mouseDownModifyCallback as EventListener ); element.removeEventListener( 'CORNERSTONE_TOOLS_MOUSE_DRAG', this._mouseDragModifyCallback as EventListener ); element.removeEventListener( 'CORNERSTONE_TOOLS_MOUSE_UP', this._mouseUpModifyCallback as EventListener ); element.removeEventListener( 'CORNERSTONE_TOOLS_MOUSE_MOVE', this._mouseMoveModifyCallback as EventListener ); // 移除键盘事件监听 element.removeEventListener( 'keydown', this._keyDownCallback as EventListener ); } /** * 键盘按下回调 - 用于删除功能 */ _keyDownCallback = (evt: KeyboardEvent): void => { const element = evt.currentTarget as HTMLDivElement; if (evt.key === 'Delete' || evt.key === 'Backspace') { const enabledElement = getEnabledElement(element); if (!enabledElement) { return; } const annotations = annotation.state.getAnnotations(this.getToolName(), element); if (!annotations || annotations.length === 0) { return; } // 查找被选中的注解 const selectedAnnotations = annotations.filter( (ann) => isFindMidpointMeasurementAnnotation(ann) && ann.highlighted ); if (selectedAnnotations.length === 0) { return; } // 删除所有选中的注解 for (const ann of selectedAnnotations) { annotation.state.removeAnnotation(ann.annotationUID!); } // 触发渲染更新 const viewportIdsToRender = utilities.viewportFilters.getViewportIdsWithToolToRender( element, this.getToolName() ); utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); evt.preventDefault(); evt.stopPropagation(); } }; /** * 鼠标移动回调 - 用于修改模式,处理悬停检测 */ _mouseMoveModifyCallback = (evt: EventTypes.InteractionEventType): void => { const eventDetail = evt.detail; const { element, currentPoints } = eventDetail; if (!currentPoints || !currentPoints.canvas) { return; } const canvasCoords = currentPoints.canvas; const enabledElement = getEnabledElement(element); if (!enabledElement) { return; } const annotations = annotation.state.getAnnotations(this.getToolName(), element); if (!annotations || annotations.length === 0) { element.style.cursor = 'default'; return; } let isHovering = false; // 检查是否悬停在手柄或线段上 for (const ann of annotations) { if (!isFindMidpointMeasurementAnnotation(ann)) continue; // 检查是否靠近手柄 const handle = this.getHandleNearImagePoint(element, ann, canvasCoords, 6); if (handle) { element.style.cursor = 'crosshair'; ann.highlighted = true; isHovering = true; break; } // 检查是否靠近线段 if (this.isPointNearTool(element, ann, canvasCoords, 10)) { element.style.cursor = 'crosshair'; ann.highlighted = true; isHovering = true; break; } } // 如果没有悬停在任何地方,重置高亮 if (!isHovering) { for (const ann of annotations) { if (isFindMidpointMeasurementAnnotation(ann)) { ann.highlighted = false; } } element.style.cursor = 'default'; } // 触发渲染以更新高亮状态 const viewportIdsToRender = utilities.viewportFilters.getViewportIdsWithToolToRender( element, this.getToolName() ); utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); }; /** * 鼠标按下回调 - 用于修改模式 */ _mouseDownModifyCallback = (evt: EventTypes.InteractionEventType): void => { const eventDetail = evt.detail; const { element, currentPoints } = eventDetail; const canvasCoords = currentPoints.canvas; const enabledElement = getEnabledElement(element); if (!enabledElement) { return; } const annotations = annotation.state.getAnnotations(this.getToolName(), element); if (!annotations || annotations.length === 0) { return; } // 查找最近的手柄 for (const ann of annotations) { if (!isFindMidpointMeasurementAnnotation(ann)) continue; const handle = this.getHandleNearImagePoint( element, ann, canvasCoords, 6 ); if (handle) { const viewportIdsToRender = utilities.viewportFilters.getViewportIdsWithToolToRender( element, this.getToolName() ); this.editData = { annotation: ann, viewportIdsToRender, handleIndex: ann.data.handles.activeHandleIndex || 0, hasMoved: false, }; ann.isSelected = true; ann.highlighted = true; utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); evt.preventDefault(); evt.stopPropagation(); return; } } // 检查是否点击在工具的其他位置(线段或中点上) for (const ann of annotations) { if (!isFindMidpointMeasurementAnnotation(ann)) continue; if (this.isPointNearTool(element, ann, canvasCoords, 10)) { const viewportIdsToRender = utilities.viewportFilters.getViewportIdsWithToolToRender( element, this.getToolName() ); // 计算鼠标点击位置相对于工具的偏移量 const points = ann.data.handles.points; if (points.length >= 4) { const midPoint = calculateMidpoint(points[0], points[1]); const midCanvas = enabledElement.viewport.worldToCanvas(midPoint); const wholeToolOffset: CoreTypes.Point2 = [ canvasCoords[0] - midCanvas[0], canvasCoords[1] - midCanvas[1], ]; this.editData = { annotation: ann, viewportIdsToRender, hasMoved: false, movingWholeTool: true, wholeToolOffset: wholeToolOffset, }; ann.isSelected = true; ann.highlighted = true; utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); evt.preventDefault(); evt.stopPropagation(); return; } } } // 如果没有点击在工具上,取消所有工具的选中状态 for (const ann of annotations) { if (isFindMidpointMeasurementAnnotation(ann)) { ann.isSelected = false; ann.highlighted = false; } } const viewportIdsToRender = utilities.viewportFilters.getViewportIdsWithToolToRender( element, this.getToolName() ); utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); }; /** * 鼠标拖拽回调 - 用于修改模式 */ _mouseDragModifyCallback = (evt: EventTypes.InteractionEventType): void => { if (!this.editData) { return; } const eventDetail = evt.detail; const { currentPoints } = eventDetail; const canvasCoords = currentPoints.canvas; const enabledElement = getEnabledElement(eventDetail.element); if (!enabledElement) { return; } const { annotation: ann, viewportIdsToRender, movingWholeTool } = this.editData; if (!isFindMidpointMeasurementAnnotation(ann)) return; const { data } = ann; // 如果正在移动整个工具 if (movingWholeTool && this.editData.wholeToolOffset) { const newMidCanvas: CoreTypes.Point2 = [ canvasCoords[0] - this.editData.wholeToolOffset[0], canvasCoords[1] - this.editData.wholeToolOffset[1], ]; const newMidWorld = enabledElement.viewport.canvasToWorld(newMidCanvas); const points = data.handles.points; if (points.length >= 4) { const oldMid = calculateMidpoint(points[0], points[1]); const offset = vectorSubtract(newMidWorld, oldMid); // 移动所有点 for (let i = 0; i < points.length; i++) { points[i] = vectorAdd(points[i], offset); } // 重新计算统计数据 this._updateCachedStats(ann, enabledElement); this.editData.hasMoved = true; utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); evt.preventDefault(); evt.stopPropagation(); return; } } // 手柄拖拽 const worldPos = currentPoints.world; const activeHandleIndex = data.handles.activeHandleIndex; if (activeHandleIndex !== null && activeHandleIndex >= 0 && activeHandleIndex < data.handles.points.length) { data.handles.points[activeHandleIndex] = worldPos; this._updateCachedStats(ann, enabledElement); this.editData.hasMoved = true; utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); evt.preventDefault(); evt.stopPropagation(); } }; /** * 鼠标释放回调 - 用于修改模式 */ _mouseUpModifyCallback = (evt: EventTypes.InteractionEventType): void => { if (!this.editData) { return; } const { annotation: ann, hasMoved, movingWholeTool } = this.editData; if (!isFindMidpointMeasurementAnnotation(ann)) return; ann.data.handles.activeHandleIndex = null; // 如果没有拖拽且点击在线段上,则保持选中状态 if (!hasMoved && movingWholeTool) { ann.highlighted = true; } const eventDetail = evt.detail; const { element } = eventDetail; const viewportIdsToRender = utilities.viewportFilters.getViewportIdsWithToolToRender( element, this.getToolName() ); utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); this.editData = null; evt.preventDefault(); evt.stopPropagation(); }; /** * 处理鼠标拖拽 */ _dragCallback = (evt: EventTypes.InteractionEventType): void => { this.isDrawing = true; const eventDetail = evt.detail; const { element } = eventDetail; const enabledElement = getEnabledElement(element); if (!enabledElement || !this.editData) { return; } const { annotation, viewportIdsToRender } = this.editData; if (!isFindMidpointMeasurementAnnotation(annotation)) return; const { data } = annotation; if (annotation) { const { currentPoints } = eventDetail; const worldPos = currentPoints.world; const points = data.handles.points; const activeHandleIndex = data.handles.activeHandleIndex; if (activeHandleIndex !== null && activeHandleIndex < points.length) { points[activeHandleIndex] = worldPos; this._updateCachedStats( annotation, enabledElement ); this.editData!.hasMoved = true; } utilities.triggerAnnotationRenderForViewportIds(viewportIdsToRender); } }; /** * 处理鼠标抬起/点击 */ _endCallback = (evt: EventTypes.InteractionEventType): void => { const eventDetail = evt.detail; const { element } = eventDetail; if (!this.editData) { return; } const { annotation, viewportIdsToRender, newAnnotation, hasMoved } = this.editData; if (!isFindMidpointMeasurementAnnotation(annotation)) return; const { data } = annotation; if (newAnnotation && !hasMoved) { const points = data.handles.points; if (points.length < 4) { const { currentPoints } = eventDetail; const worldPos = currentPoints.world; if (points.length > 0) { points[points.length - 1] = worldPos; } if (points.length < 4) { points.push(worldPos); data.handles.activeHandleIndex = points.length - 1; } if (points.length === 4) { this.isDrawing = false; data.handles.activeHandleIndex = null; annotation.highlighted = false; this.editData.newAnnotation = false; this._deactivateDraw(element); } const enabledElement = getEnabledElement(element); if (enabledElement) { this._updateCachedStats( annotation, enabledElement ); const newViewportIdsToRender = utilities.viewportFilters.getViewportIdsWithToolToRender( element, this.getToolName() ); utilities.triggerAnnotationRenderForViewportIds(newViewportIdsToRender); } } } else if (hasMoved) { this.editData.hasMoved = false; } }; /** * 获取靠近图像点的手柄 */ getHandleNearImagePoint( element: HTMLDivElement, annotation: Types.Annotation, canvasCoords: CoreTypes.Point2, proximity: number ): Types.ToolHandle | undefined { if (!isFindMidpointMeasurementAnnotation(annotation)) return undefined; const enabledElement = getEnabledElement(element); if (!enabledElement) { return undefined; } const { viewport } = enabledElement; const points = annotation.data.handles.points; const handleProximity = Math.max(proximity, 15); for (let i = 0; i < points.length; i++) { const point = points[i]; const canvasPoint = viewport.worldToCanvas(point); const distance = Math.sqrt( Math.pow(canvasPoint[0] - canvasCoords[0], 2) + Math.pow(canvasPoint[1] - canvasCoords[1], 2) ); if (distance < handleProximity) { annotation.data.handles.activeHandleIndex = i; return { worldPosition: point, } as Types.ToolHandle; } } annotation.data.handles.activeHandleIndex = null; return undefined; } /** * 更新缓存的统计数据 */ _updateCachedStats( annotation: FindMidpointMeasurementAnnotation, enabledElement: CoreTypes.IEnabledElement ): void { const { viewport } = enabledElement; const { data } = annotation; const points = data.handles.points; const targetId = this.getTargetId(viewport); if (!targetId) { return; } if (!data.cachedStats) { data.cachedStats = {}; } if (!data.cachedStats[targetId]) { data.cachedStats[targetId] = { midPointM1: [0, 0, 0] as CoreTypes.Point3, midPointM2: [0, 0, 0] as CoreTypes.Point3, finalMidpoint: [0, 0, 0] as CoreTypes.Point3, }; } const stats = data.cachedStats[targetId]; if (points.length >= 4) { // 计算中点 const midPointM1 = calculateMidpoint(points[0], points[1]); const midPointM2 = calculateMidpoint(points[2], points[3]); // 计算最终中点 (M1和M2的中点) const finalMidpoint = calculateMidpoint(midPointM1, midPointM2); // 存储计算结果 stats.midPointM1 = midPointM1; stats.midPointM2 = midPointM2; stats.finalMidpoint = finalMidpoint; } } /** * 渲染注解 */ renderAnnotation = ( enabledElement: CoreTypes.IEnabledElement, svgDrawingHelper: SVGDrawingHelper ): boolean => { let renderStatus = false; const { viewport } = enabledElement; const { element } = viewport; let annotations = annotation.state.getAnnotations(this.getToolName(), element); if (!annotations?.length) { return renderStatus; } for (let i = 0; i < annotations.length; i++) { const annotation = annotations[i]; if (!isFindMidpointMeasurementAnnotation(annotation)) continue; const { annotationUID, data } = annotation; const points = data.handles.points; if (points.length < 4) { continue; } const targetId = this.getTargetId(viewport); const cachedStats = targetId ? data.cachedStats?.[targetId] : undefined; // 转换所有点为canvas坐标 const canvasPoints = points.map((p) => viewport.worldToCanvas(p)); // 绘制线段AB (蓝色) if (annotationUID) { const lineABUID = `${annotationUID}-lineAB`; drawLineSvg( svgDrawingHelper, annotationUID, lineABUID, canvasPoints[0], canvasPoints[1], { color: 'rgb(0, 0, 255)', width: 2, } ); } // 绘制线段CD (红色) if (annotationUID) { const lineCDUID = `${annotationUID}-lineCD`; drawLineSvg( svgDrawingHelper, annotationUID, lineCDUID, canvasPoints[2], canvasPoints[3], { color: 'rgb(255, 0, 0)', width: 2, } ); } // 绘制中线 (绿色,连接M1和M2) if (cachedStats?.midPointM1 && cachedStats?.midPointM2 && annotationUID) { const midM1Canvas = viewport.worldToCanvas(cachedStats.midPointM1); const midM2Canvas = viewport.worldToCanvas(cachedStats.midPointM2); const midlineUID = `${annotationUID}-midline`; drawLineSvg( svgDrawingHelper, annotationUID, midlineUID, midM1Canvas, midM2Canvas, { color: 'rgb(0, 255, 0)', // 绿色 width: 2, } ); } // 绘制最终中点 (红色圆形标记) if (cachedStats?.finalMidpoint && annotationUID) { const finalMidpointCanvas = viewport.worldToCanvas(cachedStats.finalMidpoint); const finalMidpointUID = `${annotationUID}-final-midpoint`; drawCircleSvg( svgDrawingHelper, annotationUID, finalMidpointUID, finalMidpointCanvas, 8, // 固定大小的标记,比手柄稍大 { color: 'rgb(255, 0, 0)', // 红色 fill: 'rgb(255, 0, 0)', // 实心填充 } ); } // 绘制手柄点 if (annotationUID) { const handleGroupUID = '0'; const handleRadius = annotation.highlighted ? 12 : 8; drawHandles( svgDrawingHelper, annotationUID, handleGroupUID, canvasPoints, { color: 'rgb(255, 255, 255)', handleRadius: handleRadius, } ); } renderStatus = true; } return renderStatus; }; }