From d6183a00365c9c7c6594cd7cecb07fc0c596625b Mon Sep 17 00:00:00 2001 From: zhangheng Date: Thu, 9 Apr 2026 18:21:12 +0800 Subject: [PATCH] feat(tool): eclipse --- components/label/LabelNossr.tsx | 9 +- .../label/components/PaperContainer.tsx | 181 +++++++++++++++++- components/label/components/TopTools.tsx | 16 +- components/label/usePaperStore.ts | 3 + components/label/useTopToolsStore.ts | 9 + 5 files changed, 210 insertions(+), 8 deletions(-) diff --git a/components/label/LabelNossr.tsx b/components/label/LabelNossr.tsx index e1375f3..e3330a8 100644 --- a/components/label/LabelNossr.tsx +++ b/components/label/LabelNossr.tsx @@ -267,6 +267,7 @@ const LabelPage = ({ setShowMultiFrame, isView, setIsView, + renderMode, scale, setScale, objectOperations, @@ -1307,8 +1308,8 @@ const LabelPage = ({ ) const setDrawType = useCallback(() => { - if (isView) { - setPaperMode("pan") + if (isView || renderMode) { + setPaperMode(null) } else { let operationSchema = getOperationSchema(activeOperation) if (operationSchema) { @@ -1371,6 +1372,7 @@ const LabelPage = ({ editMode, getOperationSchema, isView, + renderMode, setPaperMode, setToolOption, ]) @@ -2117,7 +2119,7 @@ const LabelPage = ({ return } - if (isView) return + if (isView || renderMode) return if (!e.ctrlKey && !e.metaKey && !e.altKey && lowerKey === "h") { e.preventDefault() @@ -2426,6 +2428,7 @@ const LabelPage = ({ handleContinueSelectedPolygon, handleShortcut, isView, + renderMode, projectDetail?.label_type, setEditMode, setGroupScale, diff --git a/components/label/components/PaperContainer.tsx b/components/label/components/PaperContainer.tsx index 4143de7..85e75fc 100644 --- a/components/label/components/PaperContainer.tsx +++ b/components/label/components/PaperContainer.tsx @@ -51,6 +51,7 @@ import { useBottomToolsStore } from "../useBottomToolsStore" import { useKeyboardStore } from "../useKeyBoardStore" import { useOtherToolsStore } from "../useOtherToolsStore" import { clearSupportAnnotationItem, usePaperStore } from "../usePaperStore" +import { usePaperSupportStore } from "../usePaperSupportStore" import { renderGroupPath } from "../useRenderGroupPath" import useRenderTag from "../useRenderTag" import { useRightToolsStore } from "../useRightToolsStore" @@ -443,6 +444,7 @@ const PaperContainer = ( crosshairStatus, assistToolEnabled, assistToolSize, + renderMode, showTags, showTagsConfigs, } = useTopToolsStore() @@ -452,6 +454,8 @@ const PaperContainer = ( selectedPath, updateSelectedPath, } = useObjectStore() + const currentSelectedPath = selectedPath[activeImage] || [] + const currentSelectedPathKey = currentSelectedPath.join(",") const { showContextMenu, @@ -511,6 +515,11 @@ const PaperContainer = ( token: number imageName: string } | null>(null) + const renderModePanRef = useRef({ + active: false, + lastX: 0, + lastY: 0, + }) const paperScope = useRef(null) useEffect(() => { @@ -1812,6 +1821,52 @@ const PaperContainer = ( ] ) + const syncRenderModeVisuals = useCallback( + (targetGroup?: paper.Group | null) => { + const group = targetGroup ?? usePaperStore.getState().group + if (!group) return + + group.children.forEach((item) => { + const itemData = item.data || {} + const itemType = itemData.type + + if ( + itemData.name === "pic" || + itemData.id === "support" || + !itemData.fillColor || + !itemData.blankColor || + [ + "mask", + "text", + "tag", + "groupPath", + "pathCircle", + "pathBuff", + "circle", + ].includes(itemType) + ) { + return + } + + const nextFillState = renderMode || itemData.selected ? "fill" : "blank" + if (itemData.renderModeFillState === nextFillState) { + return + } + itemData.renderModeFillState = nextFillState + + const nextFillColor = + renderMode || itemData.selected + ? itemData.fillColor + : itemData.blankColor + const pathItem = item as paper.PathItem + pathItem.fillColor = nextFillColor + ? new paper.Color(nextFillColor) + : null + }) + }, + [renderMode] + ) + // 生成辅助标注结果数据并绘制 const saveSupportAnnotationData = useCallback(() => { const group = usePaperStore.getState().group @@ -2163,6 +2218,7 @@ const PaperContainer = ( if (useBottomToolsStore.getState().activeImage !== expectedImage) return renderGroupPath() renderTag() + syncRenderModeVisuals() return } @@ -2194,6 +2250,7 @@ const PaperContainer = ( if (useBottomToolsStore.getState().activeImage !== expectedImage) return renderGroupPath() renderTag() + syncRenderModeVisuals() return } @@ -2238,6 +2295,7 @@ const PaperContainer = ( if (useBottomToolsStore.getState().activeImage !== expectedImage) return renderGroupPath() renderTag() + syncRenderModeVisuals() } if (typeof globalThis.requestAnimationFrame === "function") { @@ -2246,7 +2304,13 @@ const PaperContainer = ( setTimeout(runBatch, 0) } }, - [clearRenderedAnnotationItems, objectOperations, renderPolygons, renderTag] + [ + clearRenderedAnnotationItems, + objectOperations, + renderPolygons, + renderTag, + syncRenderModeVisuals, + ] ) const withPaperGroup = useCallback( @@ -2365,6 +2429,7 @@ const PaperContainer = ( }) if (!tasks.length) { + syncRenderModeVisuals(targetGroup) resolve(true) return } @@ -2403,6 +2468,7 @@ const PaperContainer = ( return } + syncRenderModeVisuals(targetGroup) resolve(true) } @@ -2416,10 +2482,107 @@ const PaperContainer = ( isPendingFrameSwitchStale, objectOperations, renderPolygons, + syncRenderModeVisuals, withPaperGroup, ] ) + useEffect(() => { + if (!setup || loadingData) return + + const group = usePaperStore.getState().group + if (!group) return + + syncRenderModeVisuals(group) + + if (renderMode) { + usePaperSupportStore.getState().clearAll() + useOtherToolsStore.getState().setShowContextMenu({ + showContextMenu: false, + position: { top: 0, left: 0 }, + imageId: "", + operationId: "", + id: 0, + }) + } + }, [ + activeImage, + activeImageLabel, + currentSelectedPathKey, + loadingData, + renderMode, + setup, + syncRenderModeVisuals, + ]) + + const stopRenderModePan = useCallback(() => { + renderModePanRef.current.active = false + }, []) + + const handleRenderModeOverlayMouseDown = useCallback( + (event: React.MouseEvent) => { + event.preventDefault() + event.stopPropagation() + + useOtherToolsStore.getState().setShowContextMenu({ + showContextMenu: false, + position: { top: 0, left: 0 }, + imageId: "", + operationId: "", + id: 0, + }) + + if (event.button !== 1) return + + renderModePanRef.current = { + active: true, + lastX: event.clientX, + lastY: event.clientY, + } + }, + [] + ) + + useEffect(() => { + if (!renderMode) { + stopRenderModePan() + return + } + + const handleMouseMove = (event: MouseEvent) => { + const currentPan = renderModePanRef.current + if (!currentPan.active) return + + event.preventDefault() + const group = usePaperStore.getState().group + if (!group) return + + const deltaX = event.clientX - currentPan.lastX + const deltaY = event.clientY - currentPan.lastY + if (!deltaX && !deltaY) return + + group.position = group.position.add(new paper.Point(deltaX, deltaY)) + renderModePanRef.current.lastX = event.clientX + renderModePanRef.current.lastY = event.clientY + } + + const handleMouseUp = (event: MouseEvent) => { + if (event.button === 1) { + event.preventDefault() + } + stopRenderModePan() + } + + window.addEventListener("mousemove", handleMouseMove, { passive: false }) + window.addEventListener("mouseup", handleMouseUp, { passive: false }) + + return () => { + window.removeEventListener("mousemove", handleMouseMove) + window.removeEventListener("mouseup", handleMouseUp) + stopRenderModePan() + } + }, [renderMode, stopRenderModePan]) + const preparePendingFrameSwitch = useCallback( async (imageName: string, token: number) => { if ( @@ -4024,6 +4187,22 @@ const PaperContainer = ( }} /> )} + {renderMode && ( + { + event.preventDefault() + }} + onMouseDown={handleRenderModeOverlayMouseDown} + style={{ + position: "absolute", + inset: 0, + zIndex: 47, + background: "rgba(128, 128, 128, 0.24)", + pointerEvents: "auto", + }} + /> + )} {contextMenu} )} - + + { + setRenderMode(!renderMode) + }}> + + + ((set) => ({ break default: usePaperStore.getState().viewTool?.activate() + if (usePaperStore.getState().el) { + usePaperStore.getState().el!.style.cursor = "default" + } break } }, diff --git a/components/label/useTopToolsStore.ts b/components/label/useTopToolsStore.ts index cc06de4..7a189b4 100644 --- a/components/label/useTopToolsStore.ts +++ b/components/label/useTopToolsStore.ts @@ -10,6 +10,8 @@ interface TopToolsState { // 标注页面是否仅查看 isView: boolean setIsView: (val: boolean) => void + renderMode: boolean + setRenderMode: (val: boolean) => void // 绘制模式 editMode: boolean setEditMode: (val: boolean) => void @@ -81,6 +83,7 @@ interface TopToolsState { const initialTopToolsState = { isView: false, + renderMode: false, editMode: false, pressA: false, imageFilter: { @@ -114,6 +117,12 @@ export const useTopToolsStore = create()( ...state, isView: val, })), + renderMode: initialTopToolsState.renderMode, + setRenderMode: (val: boolean) => + set((state: TopToolsState) => ({ + ...state, + renderMode: val, + })), editMode: initialTopToolsState.editMode, setEditMode: (val: boolean) => set((state: TopToolsState) => ({