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,
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,

View File

@@ -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<paper.PaperScope>(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<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(
async (imageName: string, token: number) => {
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}
<Modal
opened={confirmModalOpened}

View File

@@ -180,6 +180,8 @@ const TopTools = (
const {
isView,
setIsView,
renderMode,
setRenderMode,
saveCurrentScale,
setSaveCurrentScale,
showMultiFrame,
@@ -1896,10 +1898,16 @@ const TopTools = (
<SquarePen style={{ width: 20, height: 20 }} />
</ActionIcon>
)}
<Eclipse
style={{ width: 20, height: 20, display: "none" }}
color="gray"
/>
<Tooltip label={renderMode ? "关闭渲染" : "开启渲染"}>
<ActionIcon
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" }} />
</Flex>
<Flex

View File

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

View File

@@ -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<TopToolsState>()(
...state,
isView: val,
})),
renderMode: initialTopToolsState.renderMode,
setRenderMode: (val: boolean) =>
set((state: TopToolsState) => ({
...state,
renderMode: val,
})),
editMode: initialTopToolsState.editMode,
setEditMode: (val: boolean) =>
set((state: TopToolsState) => ({