feat(tool): eclipse

This commit is contained in:
zhangheng
2026-04-09 18:21:12 +08:00
parent d1fac5d158
commit d6183a0036
5 changed files with 210 additions and 8 deletions

View File

@@ -267,6 +267,7 @@ const LabelPage = ({
setShowMultiFrame, setShowMultiFrame,
isView, isView,
setIsView, setIsView,
renderMode,
scale, scale,
setScale, setScale,
objectOperations, objectOperations,
@@ -1307,8 +1308,8 @@ const LabelPage = ({
) )
const setDrawType = useCallback(() => { const setDrawType = useCallback(() => {
if (isView) { if (isView || renderMode) {
setPaperMode("pan") setPaperMode(null)
} else { } else {
let operationSchema = getOperationSchema(activeOperation) let operationSchema = getOperationSchema(activeOperation)
if (operationSchema) { if (operationSchema) {
@@ -1371,6 +1372,7 @@ const LabelPage = ({
editMode, editMode,
getOperationSchema, getOperationSchema,
isView, isView,
renderMode,
setPaperMode, setPaperMode,
setToolOption, setToolOption,
]) ])
@@ -2117,7 +2119,7 @@ const LabelPage = ({
return return
} }
if (isView) return if (isView || renderMode) return
if (!e.ctrlKey && !e.metaKey && !e.altKey && lowerKey === "h") { if (!e.ctrlKey && !e.metaKey && !e.altKey && lowerKey === "h") {
e.preventDefault() e.preventDefault()
@@ -2426,6 +2428,7 @@ const LabelPage = ({
handleContinueSelectedPolygon, handleContinueSelectedPolygon,
handleShortcut, handleShortcut,
isView, isView,
renderMode,
projectDetail?.label_type, projectDetail?.label_type,
setEditMode, setEditMode,
setGroupScale, setGroupScale,

View File

@@ -51,6 +51,7 @@ import { useBottomToolsStore } from "../useBottomToolsStore"
import { useKeyboardStore } from "../useKeyBoardStore" import { useKeyboardStore } from "../useKeyBoardStore"
import { useOtherToolsStore } from "../useOtherToolsStore" import { useOtherToolsStore } from "../useOtherToolsStore"
import { clearSupportAnnotationItem, usePaperStore } from "../usePaperStore" import { clearSupportAnnotationItem, usePaperStore } from "../usePaperStore"
import { usePaperSupportStore } from "../usePaperSupportStore"
import { renderGroupPath } from "../useRenderGroupPath" import { renderGroupPath } from "../useRenderGroupPath"
import useRenderTag from "../useRenderTag" import useRenderTag from "../useRenderTag"
import { useRightToolsStore } from "../useRightToolsStore" import { useRightToolsStore } from "../useRightToolsStore"
@@ -443,6 +444,7 @@ const PaperContainer = (
crosshairStatus, crosshairStatus,
assistToolEnabled, assistToolEnabled,
assistToolSize, assistToolSize,
renderMode,
showTags, showTags,
showTagsConfigs, showTagsConfigs,
} = useTopToolsStore() } = useTopToolsStore()
@@ -452,6 +454,8 @@ const PaperContainer = (
selectedPath, selectedPath,
updateSelectedPath, updateSelectedPath,
} = useObjectStore() } = useObjectStore()
const currentSelectedPath = selectedPath[activeImage] || []
const currentSelectedPathKey = currentSelectedPath.join(",")
const { const {
showContextMenu, showContextMenu,
@@ -511,6 +515,11 @@ const PaperContainer = (
token: number token: number
imageName: string imageName: string
} | null>(null) } | null>(null)
const renderModePanRef = useRef({
active: false,
lastX: 0,
lastY: 0,
})
const paperScope = useRef<paper.PaperScope>(null) const paperScope = useRef<paper.PaperScope>(null)
useEffect(() => { 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 saveSupportAnnotationData = useCallback(() => {
const group = usePaperStore.getState().group const group = usePaperStore.getState().group
@@ -2163,6 +2218,7 @@ const PaperContainer = (
if (useBottomToolsStore.getState().activeImage !== expectedImage) return if (useBottomToolsStore.getState().activeImage !== expectedImage) return
renderGroupPath() renderGroupPath()
renderTag() renderTag()
syncRenderModeVisuals()
return return
} }
@@ -2194,6 +2250,7 @@ const PaperContainer = (
if (useBottomToolsStore.getState().activeImage !== expectedImage) return if (useBottomToolsStore.getState().activeImage !== expectedImage) return
renderGroupPath() renderGroupPath()
renderTag() renderTag()
syncRenderModeVisuals()
return return
} }
@@ -2238,6 +2295,7 @@ const PaperContainer = (
if (useBottomToolsStore.getState().activeImage !== expectedImage) return if (useBottomToolsStore.getState().activeImage !== expectedImage) return
renderGroupPath() renderGroupPath()
renderTag() renderTag()
syncRenderModeVisuals()
} }
if (typeof globalThis.requestAnimationFrame === "function") { if (typeof globalThis.requestAnimationFrame === "function") {
@@ -2246,7 +2304,13 @@ const PaperContainer = (
setTimeout(runBatch, 0) setTimeout(runBatch, 0)
} }
}, },
[clearRenderedAnnotationItems, objectOperations, renderPolygons, renderTag] [
clearRenderedAnnotationItems,
objectOperations,
renderPolygons,
renderTag,
syncRenderModeVisuals,
]
) )
const withPaperGroup = useCallback( const withPaperGroup = useCallback(
@@ -2365,6 +2429,7 @@ const PaperContainer = (
}) })
if (!tasks.length) { if (!tasks.length) {
syncRenderModeVisuals(targetGroup)
resolve(true) resolve(true)
return return
} }
@@ -2403,6 +2468,7 @@ const PaperContainer = (
return return
} }
syncRenderModeVisuals(targetGroup)
resolve(true) resolve(true)
} }
@@ -2416,10 +2482,107 @@ const PaperContainer = (
isPendingFrameSwitchStale, isPendingFrameSwitchStale,
objectOperations, objectOperations,
renderPolygons, renderPolygons,
syncRenderModeVisuals,
withPaperGroup, 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<HTMLDivElement>) => {
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( const preparePendingFrameSwitch = useCallback(
async (imageName: string, token: number) => { async (imageName: string, token: number) => {
if ( if (
@@ -4024,6 +4187,22 @@ const PaperContainer = (
}} }}
/> />
)} )}
{renderMode && (
<Box
aria-hidden
onContextMenu={(event) => {
event.preventDefault()
}}
onMouseDown={handleRenderModeOverlayMouseDown}
style={{
position: "absolute",
inset: 0,
zIndex: 47,
background: "rgba(128, 128, 128, 0.24)",
pointerEvents: "auto",
}}
/>
)}
{contextMenu} {contextMenu}
<Modal <Modal
opened={confirmModalOpened} opened={confirmModalOpened}

View File

@@ -180,6 +180,8 @@ const TopTools = (
const { const {
isView, isView,
setIsView, setIsView,
renderMode,
setRenderMode,
saveCurrentScale, saveCurrentScale,
setSaveCurrentScale, setSaveCurrentScale,
showMultiFrame, showMultiFrame,
@@ -1896,10 +1898,16 @@ const TopTools = (
<SquarePen style={{ width: 20, height: 20 }} /> <SquarePen style={{ width: 20, height: 20 }} />
</ActionIcon> </ActionIcon>
)} )}
<Eclipse <Tooltip label={renderMode ? "关闭渲染" : "开启渲染"}>
style={{ width: 20, height: 20, display: "none" }} <ActionIcon
color="gray" variant="transparent"
/> c={renderMode ? "var(--mantine-color-text)" : "gray"}
onClick={() => {
setRenderMode(!renderMode)
}}>
<Eclipse style={{ width: 20, height: 20 }} />
</ActionIcon>
</Tooltip>
<CopySlash style={{ width: 20, height: 20, display: "none" }} /> <CopySlash style={{ width: 20, height: 20, display: "none" }} />
</Flex> </Flex>
<Flex <Flex

View File

@@ -5600,6 +5600,9 @@ export const usePaperStore = create<PaperState>((set) => ({
break break
default: default:
usePaperStore.getState().viewTool?.activate() usePaperStore.getState().viewTool?.activate()
if (usePaperStore.getState().el) {
usePaperStore.getState().el!.style.cursor = "default"
}
break break
} }
}, },

View File

@@ -10,6 +10,8 @@ interface TopToolsState {
// 标注页面是否仅查看 // 标注页面是否仅查看
isView: boolean isView: boolean
setIsView: (val: boolean) => void setIsView: (val: boolean) => void
renderMode: boolean
setRenderMode: (val: boolean) => void
// 绘制模式 // 绘制模式
editMode: boolean editMode: boolean
setEditMode: (val: boolean) => void setEditMode: (val: boolean) => void
@@ -81,6 +83,7 @@ interface TopToolsState {
const initialTopToolsState = { const initialTopToolsState = {
isView: false, isView: false,
renderMode: false,
editMode: false, editMode: false,
pressA: false, pressA: false,
imageFilter: { imageFilter: {
@@ -114,6 +117,12 @@ export const useTopToolsStore = create<TopToolsState>()(
...state, ...state,
isView: val, isView: val,
})), })),
renderMode: initialTopToolsState.renderMode,
setRenderMode: (val: boolean) =>
set((state: TopToolsState) => ({
...state,
renderMode: val,
})),
editMode: initialTopToolsState.editMode, editMode: initialTopToolsState.editMode,
setEditMode: (val: boolean) => setEditMode: (val: boolean) =>
set((state: TopToolsState) => ({ set((state: TopToolsState) => ({