fix(bug): fix
This commit is contained in:
@@ -17,6 +17,10 @@ import {
|
||||
IconChevronsLeft,
|
||||
IconChevronsRight,
|
||||
IconFlag,
|
||||
IconPlayerTrackNext,
|
||||
IconPlayerTrackNextFilled,
|
||||
IconPlayerTrackPrev,
|
||||
IconPlayerTrackPrevFilled,
|
||||
} from "@tabler/icons-react"
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||
import { Project } from "../api/project/typing"
|
||||
@@ -46,6 +50,8 @@ interface BottomToolsComponentProps {
|
||||
) => void | null
|
||||
}
|
||||
|
||||
type PlaybackDirection = "backward" | "forward"
|
||||
|
||||
const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
const { labelState, projectDetail, taskDetail, renderPolygons } = props
|
||||
|
||||
@@ -72,35 +78,34 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [conflictOpen, setConflictOpen] = useState(false)
|
||||
const [playDirection, setPlayDirection] = useState<PlaybackDirection | null>(
|
||||
null
|
||||
)
|
||||
const allKeys = useMemo(() => Array.from(labelState.keys()), [labelState])
|
||||
const isShowKeyFrame =
|
||||
projectDetail &&
|
||||
[5, 6].includes(projectDetail.label_type) &&
|
||||
Array.from(labelState.keys()).some(
|
||||
allKeys.some(
|
||||
(k) => keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
) &&
|
||||
onlyKeyFrame
|
||||
const currentKeys = Array.from(labelState.keys()).filter((k) =>
|
||||
isShowKeyFrame
|
||||
? keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
: true
|
||||
const currentKeys = useMemo(
|
||||
() =>
|
||||
allKeys.filter((k) =>
|
||||
isShowKeyFrame
|
||||
? keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
: true
|
||||
),
|
||||
[allKeys, isShowKeyFrame, keyFrameData]
|
||||
)
|
||||
// 上一帧图片id
|
||||
const previousFrameKey = useMemo(() => {
|
||||
let key = ""
|
||||
const keys = Array.from(labelState.keys()).filter((k) =>
|
||||
isShowKeyFrame
|
||||
? keyFrameData.has(k) && keyFrameData.get(k)?.key_frame
|
||||
: true
|
||||
)
|
||||
keys.forEach((k, index) => {
|
||||
if (k === activeImage && index > 0) key = keys[index - 1]
|
||||
})
|
||||
return key
|
||||
}, [activeImage, isShowKeyFrame, keyFrameData, labelState])
|
||||
const currentIndex = currentKeys.findIndex((k) => k === activeImage)
|
||||
return currentIndex > 0 ? currentKeys[currentIndex - 1] : ""
|
||||
}, [activeImage, currentKeys])
|
||||
|
||||
useEffect(() => {
|
||||
const keys = Array.from(labelState.keys())
|
||||
if (!keys.length) {
|
||||
if (!currentKeys.length) {
|
||||
setActiveImage("")
|
||||
setInputNumber("")
|
||||
setActiveImageId(0)
|
||||
@@ -108,36 +113,42 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
}
|
||||
|
||||
const currentIndex = activeImage
|
||||
? keys.findIndex((k) => k === activeImage)
|
||||
? currentKeys.findIndex((k) => k === activeImage)
|
||||
: -1
|
||||
if (currentIndex >= 0) {
|
||||
setInputNumber(String(currentIndex + 1))
|
||||
setActiveImageId(currentIndex + 1)
|
||||
const absoluteIndex = allKeys.findIndex((k) => k === activeImage)
|
||||
setActiveImageId(absoluteIndex >= 0 ? absoluteIndex + 1 : 0)
|
||||
return
|
||||
}
|
||||
|
||||
const firstKey = keys[0]
|
||||
const firstKey = currentKeys[0]
|
||||
const absoluteIndex = allKeys.findIndex((k) => k === firstKey)
|
||||
setActiveImage(firstKey)
|
||||
setInputNumber("1")
|
||||
setActiveImageId(1)
|
||||
setActiveImageId(absoluteIndex >= 0 ? absoluteIndex + 1 : 1)
|
||||
}, [
|
||||
activeImage,
|
||||
labelState,
|
||||
allKeys,
|
||||
currentKeys,
|
||||
setActiveImage,
|
||||
setActiveImageId,
|
||||
setInputNumber,
|
||||
])
|
||||
|
||||
const scrollToIndex = (index: number) => {
|
||||
const key = currentKeys[index]
|
||||
if (key && itemRefs.current[key]) {
|
||||
itemRefs.current[key]?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
})
|
||||
}
|
||||
}
|
||||
const scrollToIndex = useCallback(
|
||||
(index: number) => {
|
||||
const key = currentKeys[index]
|
||||
if (key && itemRefs.current[key]) {
|
||||
itemRefs.current[key]?.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "nearest",
|
||||
inline: "center",
|
||||
})
|
||||
}
|
||||
},
|
||||
[currentKeys]
|
||||
)
|
||||
|
||||
const closeRightContext = useCallback(() => {
|
||||
useOtherToolsStore.getState().setShowContextMenu({
|
||||
@@ -152,6 +163,72 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
})
|
||||
}, [])
|
||||
|
||||
const goToVisibleIndex = useCallback(
|
||||
(index: number) => {
|
||||
if (index < 0 || index >= currentKeys.length) return false
|
||||
|
||||
const nextImage = currentKeys[index]
|
||||
const absoluteIndex = allKeys.findIndex((k) => k === nextImage)
|
||||
|
||||
scrollToIndex(index)
|
||||
setInputNumber(String(index + 1))
|
||||
setActiveImageId(absoluteIndex >= 0 ? absoluteIndex + 1 : 0)
|
||||
setActiveImage(nextImage)
|
||||
closeRightContext()
|
||||
|
||||
return true
|
||||
},
|
||||
[
|
||||
allKeys,
|
||||
closeRightContext,
|
||||
currentKeys,
|
||||
scrollToIndex,
|
||||
setActiveImage,
|
||||
setActiveImageId,
|
||||
setInputNumber,
|
||||
]
|
||||
)
|
||||
|
||||
const stepVisibleImage = useCallback(
|
||||
(direction: PlaybackDirection) => {
|
||||
const currentIndex = currentKeys.findIndex((k) => k === activeImage)
|
||||
if (currentIndex < 0) return false
|
||||
|
||||
const nextIndex =
|
||||
direction === "backward" ? currentIndex - 1 : currentIndex + 1
|
||||
|
||||
return goToVisibleIndex(nextIndex)
|
||||
},
|
||||
[activeImage, currentKeys, goToVisibleIndex]
|
||||
)
|
||||
|
||||
const togglePlayback = useCallback(
|
||||
(direction: PlaybackDirection) => {
|
||||
if (currentKeys.length < 2) return
|
||||
setPlayDirection((current) => (current === direction ? null : direction))
|
||||
},
|
||||
[currentKeys.length]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!playDirection) return
|
||||
if (currentKeys.length < 2) {
|
||||
setPlayDirection(null)
|
||||
return
|
||||
}
|
||||
|
||||
const timer = window.setInterval(() => {
|
||||
const moved = stepVisibleImage(playDirection)
|
||||
if (!moved) {
|
||||
setPlayDirection(null)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
return () => {
|
||||
window.clearInterval(timer)
|
||||
}
|
||||
}, [currentKeys.length, playDirection, stepVisibleImage])
|
||||
|
||||
const getOperationSchema = useCallback(
|
||||
(operationId: string) => {
|
||||
return (
|
||||
@@ -368,28 +445,32 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
scrollToIndex(0)
|
||||
currentKeys.length && setActiveImage(currentKeys[0])
|
||||
setInputNumber("1")
|
||||
setActiveImageId(1)
|
||||
closeRightContext()
|
||||
goToVisibleIndex(0)
|
||||
}}>
|
||||
<IconChevronsLeft size={16} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={playDirection === "backward" ? "filled" : "subtle"}
|
||||
color={playDirection === "backward" ? "blue" : "gray"}
|
||||
size="sm"
|
||||
disabled={currentKeys.length < 2}
|
||||
aria-label="向前播放"
|
||||
title="向前播放"
|
||||
onClick={() => {
|
||||
togglePlayback("backward")
|
||||
}}>
|
||||
{playDirection === "backward" ? (
|
||||
<IconPlayerTrackPrevFilled size={16} />
|
||||
) : (
|
||||
<IconPlayerTrackPrev size={16} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (inputNumber !== "1") {
|
||||
let newActiveIndex = +inputNumber - 1
|
||||
setInputNumber(newActiveIndex.toString())
|
||||
setActiveImageId(newActiveIndex)
|
||||
scrollToIndex(newActiveIndex - 1)
|
||||
|
||||
setActiveImage(currentKeys?.[newActiveIndex - 1])
|
||||
closeRightContext()
|
||||
}
|
||||
stepVisibleImage("backward")
|
||||
}}>
|
||||
<IconChevronLeft size={16} />
|
||||
</ActionIcon>
|
||||
@@ -410,10 +491,21 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
scrollToIndex(+inputNumber - 1)
|
||||
setActiveImage(currentKeys?.[+inputNumber - 1])
|
||||
setActiveImageId(+inputNumber)
|
||||
closeRightContext()
|
||||
const targetIndex = Number(inputNumber)
|
||||
if (
|
||||
Number.isInteger(targetIndex) &&
|
||||
targetIndex >= 1 &&
|
||||
targetIndex <= currentKeys.length
|
||||
) {
|
||||
goToVisibleIndex(targetIndex - 1)
|
||||
} else {
|
||||
const currentIndex = currentKeys.findIndex(
|
||||
(key) => key === activeImage
|
||||
)
|
||||
setInputNumber(
|
||||
currentIndex >= 0 ? String(currentIndex + 1) : ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -423,27 +515,32 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (+inputNumber < currentKeys.length) {
|
||||
let newActiveIndex = +inputNumber + 1
|
||||
setInputNumber(newActiveIndex.toString())
|
||||
setActiveImageId(newActiveIndex)
|
||||
scrollToIndex(newActiveIndex - 1)
|
||||
setActiveImage(currentKeys?.[newActiveIndex - 1])
|
||||
closeRightContext()
|
||||
}
|
||||
stepVisibleImage("forward")
|
||||
}}>
|
||||
<IconChevronRight size={16} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant={playDirection === "forward" ? "filled" : "subtle"}
|
||||
color={playDirection === "forward" ? "blue" : "gray"}
|
||||
size="sm"
|
||||
disabled={currentKeys.length < 2}
|
||||
aria-label="向后播放"
|
||||
title="向后播放"
|
||||
onClick={() => {
|
||||
togglePlayback("forward")
|
||||
}}>
|
||||
{playDirection === "forward" ? (
|
||||
<IconPlayerTrackNextFilled size={16} />
|
||||
) : (
|
||||
<IconPlayerTrackNext size={16} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
scrollToIndex(currentKeys.length - 1)
|
||||
setInputNumber(currentKeys.length.toString())
|
||||
setActiveImageId(currentKeys.length)
|
||||
setActiveImage(currentKeys?.[currentKeys.length - 1])
|
||||
closeRightContext()
|
||||
goToVisibleIndex(currentKeys.length - 1)
|
||||
}}>
|
||||
<IconChevronsRight size={16} />
|
||||
</ActionIcon>
|
||||
@@ -657,9 +754,7 @@ const BottomTools: React.FC<BottomToolsComponentProps> = (props) => {
|
||||
if (ctrlKey || shiftKey) {
|
||||
setSelectedItems(key, ctrlKey, shiftKey)
|
||||
} else {
|
||||
setInputNumber(index + 1 + "")
|
||||
setActiveImage(key)
|
||||
setActiveImageId(index + 1)
|
||||
goToVisibleIndex(index)
|
||||
setSelectedItems("", false, false)
|
||||
}
|
||||
closeRightContext()
|
||||
|
||||
@@ -95,6 +95,7 @@ const renderRaster = (paperGroup: paper.Group, option: any) => {
|
||||
}
|
||||
|
||||
type NormalizedPoint = [number, number]
|
||||
type SupportBox = [number, number, number, number]
|
||||
type ContextMenuLayout = {
|
||||
top: number
|
||||
left: number
|
||||
@@ -447,7 +448,8 @@ const PaperContainer = (
|
||||
async (
|
||||
points: Array<[number, number]>,
|
||||
tags: number[],
|
||||
clear_previous_state = false
|
||||
clear_previous_state = false,
|
||||
boxes: SupportBox[] = []
|
||||
) => {
|
||||
void clear_previous_state
|
||||
try {
|
||||
@@ -475,6 +477,9 @@ const PaperContainer = (
|
||||
usePaperStore.getState().rasterScale[activeImage] ?? 1
|
||||
|
||||
const draw = () => {
|
||||
const paperGroup = usePaperStore.getState().group!
|
||||
const raster = paperGroup.getItems({ data: { name: "pic" } })[0]
|
||||
|
||||
points.forEach((point, index) => {
|
||||
const drawPoint = new paper.Path.Circle({
|
||||
center: point,
|
||||
@@ -486,16 +491,30 @@ const PaperContainer = (
|
||||
type: "point",
|
||||
},
|
||||
})
|
||||
drawPoint.parent = usePaperStore.getState().group!
|
||||
const raster = usePaperStore
|
||||
.getState()
|
||||
.group!.getItems({ data: { name: "pic" } })[0]
|
||||
drawPoint.parent = paperGroup
|
||||
if (!raster.contains(drawPoint.position)) {
|
||||
throw new Error("点位超出图片范围")
|
||||
}
|
||||
})
|
||||
|
||||
boxes.forEach(([left, top, right, bottom]) => {
|
||||
new paper.Path.Rectangle({
|
||||
parent: paperGroup,
|
||||
from: [left, top],
|
||||
to: [right, bottom],
|
||||
strokeColor: "#FFF",
|
||||
strokeWidth: 2,
|
||||
strokeScaling: false,
|
||||
dashArray: [6, 4],
|
||||
fillColor: new paper.Color(1, 1, 1, 0.05),
|
||||
data: {
|
||||
id: "support",
|
||||
type: "box",
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
console.log(points, tags)
|
||||
console.log(points, tags, boxes)
|
||||
clearSupportAnnotationItem()
|
||||
draw()
|
||||
|
||||
@@ -512,8 +531,12 @@ const PaperContainer = (
|
||||
const promptPoints = foregroundPoints.length
|
||||
? foregroundPoints
|
||||
: normalizedPoints
|
||||
// const promptBox =
|
||||
// promptPoints.length > 1 ? getContourBox(promptPoints) : null
|
||||
const normalizedBoxes = boxes.map(([left, top, right, bottom]) => [
|
||||
Math.round(Math.min(left, right) / currentScale),
|
||||
Math.round(Math.min(top, bottom) / currentScale),
|
||||
Math.round(Math.max(left, right) / currentScale),
|
||||
Math.round(Math.max(top, bottom) / currentScale),
|
||||
]) as SupportBox[]
|
||||
const projectId =
|
||||
projectDetail?.name ||
|
||||
projectDetail?.rpath ||
|
||||
@@ -522,8 +545,8 @@ const PaperContainer = (
|
||||
if (!projectId) {
|
||||
throw new Error("缺少 project_id,无法调用模型接口")
|
||||
}
|
||||
if (!promptPoints.length) {
|
||||
throw new Error("缺少有效点位,无法调用模型接口")
|
||||
if (!promptPoints.length && !normalizedBoxes.length) {
|
||||
throw new Error("缺少有效提示,无法调用模型接口")
|
||||
}
|
||||
// const picSize = usePaperStore.getState().rasterSize[activeImage]
|
||||
|
||||
@@ -533,9 +556,13 @@ const PaperContainer = (
|
||||
image: activeImage,
|
||||
// project_id: "image_test_bk",
|
||||
prompt: {
|
||||
foreground_points: promptPoints,
|
||||
background_points: background_points,
|
||||
// ...(promptBox ? { boxes: [promptBox] } : {}),
|
||||
...(promptPoints.length
|
||||
? { foreground_points: promptPoints }
|
||||
: {}),
|
||||
...(background_points.length
|
||||
? { background_points: background_points }
|
||||
: {}),
|
||||
...(normalizedBoxes.length ? { boxes: normalizedBoxes } : {}),
|
||||
},
|
||||
})
|
||||
const { contours = [], hollows = [] } = data || {}
|
||||
|
||||
@@ -139,7 +139,8 @@ interface PaperState {
|
||||
renderPath: (
|
||||
points: Array<[number, number]>,
|
||||
tags: number[],
|
||||
flag?: boolean
|
||||
flag?: boolean,
|
||||
boxes?: Array<[number, number, number, number]>
|
||||
) => void
|
||||
) => void
|
||||
getClickedCircle: (
|
||||
@@ -2574,6 +2575,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
polygon.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
if (polygon.data.id) {
|
||||
let category = useTopToolsStore
|
||||
.getState()
|
||||
@@ -2620,6 +2627,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
point: paper.Point
|
||||
}) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
if (compoundPath.data.id) {
|
||||
let category = useTopToolsStore
|
||||
.getState()
|
||||
@@ -2659,11 +2672,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let polygon: paper.Path | paper.CompoundPath | null
|
||||
let compoundPolygon: paper.Path | paper.CompoundPath | null
|
||||
let points: paper.Point[] = []
|
||||
let lastPoint: paper.Point
|
||||
let lastTime: number
|
||||
let lastPoint: paper.Point | null = null
|
||||
let lastTime = 0
|
||||
let isMouseDown: boolean = false
|
||||
let polygonTool = paperPolygonTool
|
||||
let currentMagnetPoint: paper.Point | null = null
|
||||
let draftId: number | null = null
|
||||
|
||||
const handleCircles = (path: paper.Path | paper.CompoundPath | null) => {
|
||||
// 更新选中的点
|
||||
@@ -2688,6 +2702,71 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
})
|
||||
}
|
||||
|
||||
const getDraftPolygonData = () => ({
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
draftId ??
|
||||
(draftId =
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1),
|
||||
})
|
||||
|
||||
const clearPolygonMagnet = () => {
|
||||
const magnetPaths = usePaperStore
|
||||
.getState()
|
||||
.group!.getItems({ data: { type: "magnet" } })
|
||||
magnetPaths.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
currentMagnetPoint = null
|
||||
}
|
||||
|
||||
const rebuildPolygonDraft = () => {
|
||||
polygon?.remove()
|
||||
polygon = null
|
||||
|
||||
if (points.length) {
|
||||
polygon = usePaperStore.getState().addPolygon(
|
||||
renderPath,
|
||||
points,
|
||||
{
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
getDraftPolygonData()
|
||||
)
|
||||
} else {
|
||||
draftId = null
|
||||
}
|
||||
|
||||
handleCircles(polygon)
|
||||
lastPoint = points.length
|
||||
? usePaperStore
|
||||
.getState()
|
||||
.group!.localToGlobal(points[points.length - 1])
|
||||
: null
|
||||
clearPolygonMagnet()
|
||||
}
|
||||
|
||||
const resetPolygonDraft = () => {
|
||||
polygon?.remove()
|
||||
polygon = null
|
||||
points = []
|
||||
handleCircles(null)
|
||||
lastPoint = null
|
||||
lastTime = 0
|
||||
draftId = null
|
||||
clearPolygonMagnet()
|
||||
}
|
||||
|
||||
const undoPolygonPoint = () => {
|
||||
if (!points.length) return false
|
||||
points.pop()
|
||||
rebuildPolygonDraft()
|
||||
return true
|
||||
}
|
||||
|
||||
const handlePolygonIntersectRaster = (savePath: paper.Path) => {
|
||||
let oldPolygon: any = savePath
|
||||
polygon = oldPolygon.intersect(
|
||||
@@ -2781,6 +2860,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
point: paper.Point
|
||||
}) => {
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
if (e.event.button === 2) {
|
||||
e.event.preventDefault()
|
||||
hidePaperContextMenu()
|
||||
undoPolygonPoint()
|
||||
return
|
||||
}
|
||||
if (e.event.button !== 0) return
|
||||
isMouseDown = true
|
||||
|
||||
@@ -2803,6 +2888,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
selectedCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
selectedCircles = []
|
||||
}
|
||||
|
||||
// 调整polygon点位初始顺序
|
||||
@@ -3000,15 +3086,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
selectedCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
const magnetPaths = usePaperStore
|
||||
.getState()
|
||||
.group!.getItems({ data: { type: "magnet" } })
|
||||
magnetPaths.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
currentMagnetPoint = null
|
||||
|
||||
selectedCircles = []
|
||||
clearPolygonMagnet()
|
||||
useTopToolsStore.getState().setPressA(false)
|
||||
draftId = null
|
||||
lastPoint = null
|
||||
lastTime = 0
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -3036,14 +3119,15 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let point = currentMagnetPoint
|
||||
? currentMagnetPoint
|
||||
: temp.globalToLocal(e.point)
|
||||
const draftData = getDraftPolygonData()
|
||||
|
||||
let circle = usePaperStore
|
||||
.getState()
|
||||
.getClickedCircle(
|
||||
point,
|
||||
selectedCircles.length,
|
||||
polygon?.data.id,
|
||||
polygon?.data.blankColor
|
||||
draftData.id,
|
||||
usePaperStore.getState().toolOption.blankColor
|
||||
)
|
||||
// add circle
|
||||
selectedCircles.push(circle)
|
||||
@@ -3060,14 +3144,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// option
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
|
||||
}
|
||||
draftData
|
||||
)
|
||||
|
||||
lastPoint = checkPoint
|
||||
@@ -3139,7 +3216,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
}
|
||||
})
|
||||
if (isMouseDown) {
|
||||
if (isMouseDown && lastPoint) {
|
||||
const delta = currentMagnetPoint
|
||||
? (currentMagnetPoint as paper.Point).subtract(lastPoint)
|
||||
: e.point.subtract(lastPoint)
|
||||
@@ -3187,27 +3264,17 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// option
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
{}
|
||||
getDraftPolygonData()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
polygonTool.onKeyDown = (e: paper.KeyEvent) => {
|
||||
if (e.key === "escape" || e.key === "alt") {
|
||||
if (polygon) {
|
||||
polygon.remove()
|
||||
polygon = null
|
||||
points = []
|
||||
usePaperSupportStore.getState().handleBufferPaths(polygon)
|
||||
handleCircles(polygon)
|
||||
if (polygon || points.length) {
|
||||
resetPolygonDraft()
|
||||
usePaperSupportStore.getState().handleBufferPaths(null)
|
||||
}
|
||||
const magnetPaths = usePaperStore
|
||||
.getState()
|
||||
.group!.getItems({ data: { type: "magnet" } })
|
||||
magnetPaths.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
currentMagnetPoint = null
|
||||
} else if (e.key === "delete") {
|
||||
handleDelete()
|
||||
forceUpdate()
|
||||
@@ -3255,6 +3322,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
rect.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
if (rect.data.id) {
|
||||
let category = useTopToolsStore
|
||||
.getState()
|
||||
@@ -3469,6 +3542,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// });
|
||||
brush.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
// console.log(e.event.clientX, e.event.clientY);
|
||||
if (brush.data.id) {
|
||||
let category = useTopToolsStore
|
||||
@@ -3506,10 +3585,66 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let brushCircles: paper.Path.Circle[] = []
|
||||
let myPath: paper.Path | null
|
||||
let compoundPolygon: paper.Path | paper.CompoundPath | null
|
||||
let lastPoint: paper.Point
|
||||
let lastPoint: paper.Point | null = null
|
||||
let points: paper.Point[] = []
|
||||
|
||||
let brushTool = paperBrushTool
|
||||
let draftId: number | null = null
|
||||
|
||||
const getDraftBrushData = () => ({
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
draftId ??
|
||||
(draftId =
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1),
|
||||
})
|
||||
|
||||
const rebuildBrushDraft = () => {
|
||||
myPath?.remove()
|
||||
myPath = null
|
||||
|
||||
if (points.length) {
|
||||
myPath = usePaperStore.getState().addBrush(
|
||||
renderPath,
|
||||
points,
|
||||
{
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
getDraftBrushData()
|
||||
)
|
||||
} else {
|
||||
draftId = null
|
||||
}
|
||||
|
||||
lastPoint = points.length
|
||||
? usePaperStore
|
||||
.getState()
|
||||
.group!.localToGlobal(points[points.length - 1])
|
||||
: null
|
||||
}
|
||||
|
||||
const resetBrushDraft = () => {
|
||||
brushCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
brushCircles = []
|
||||
myPath?.remove()
|
||||
myPath = null
|
||||
points = []
|
||||
lastPoint = null
|
||||
draftId = null
|
||||
}
|
||||
|
||||
const undoBrushPoint = () => {
|
||||
if (!points.length) return false
|
||||
points.pop()
|
||||
brushCircles.pop()?.remove()
|
||||
rebuildBrushDraft()
|
||||
return true
|
||||
}
|
||||
|
||||
const handleBrushIntersectRaster = (savePath: paper.Path) => {
|
||||
let intersections = savePath.getIntersections(
|
||||
@@ -3576,6 +3711,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
|
||||
brushTool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
if (e.event.button === 2) {
|
||||
e.event.preventDefault()
|
||||
hidePaperContextMenu()
|
||||
undoBrushPoint()
|
||||
return
|
||||
}
|
||||
if (e.event.button !== 0) return
|
||||
|
||||
// 绘制时去除选中辅助
|
||||
@@ -3771,8 +3912,11 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
brushCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
brushCircles = []
|
||||
|
||||
useTopToolsStore.getState().setPressA(false)
|
||||
draftId = null
|
||||
lastPoint = null
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -3790,6 +3934,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
|
||||
let temp = renderPath(usePaperStore.getState().group!, {})
|
||||
let point = temp.globalToLocal(e.point)
|
||||
const draftData = getDraftBrushData()
|
||||
|
||||
// add point
|
||||
points.push(point)
|
||||
@@ -3803,22 +3948,15 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// option
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
|
||||
}
|
||||
draftData
|
||||
)
|
||||
let circle = usePaperStore
|
||||
.getState()
|
||||
.getClickedCircle(
|
||||
point,
|
||||
brushCircles.length,
|
||||
myPath?.data.id,
|
||||
myPath?.data.blankColor
|
||||
draftData.id,
|
||||
usePaperStore.getState().toolOption.blankColor
|
||||
)
|
||||
brushCircles.push(circle)
|
||||
|
||||
@@ -3846,7 +3984,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
fillColor: undefined,
|
||||
closed: false,
|
||||
},
|
||||
{}
|
||||
getDraftBrushData()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3857,13 +3995,8 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
|
||||
brushTool.onKeyDown = (e: paper.KeyEvent) => {
|
||||
if (e.key === "escape" || e.key === "alt") {
|
||||
if (myPath) {
|
||||
brushCircles.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myPath.remove()
|
||||
myPath = null
|
||||
points = []
|
||||
if (myPath || points.length) {
|
||||
resetBrushDraft()
|
||||
}
|
||||
} else if (e.key === "delete") {
|
||||
handleDelete()
|
||||
@@ -3900,6 +4033,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
myCircle.data = Object.assign({ type: "circle", ...option }, data)
|
||||
myCircle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
// console.log(e.event.clientX, e.event.clientY);
|
||||
if (myCircle.data.id) {
|
||||
let category = useTopToolsStore
|
||||
@@ -3973,13 +4112,82 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let myCircleText: paper.PointText[] = []
|
||||
let textNumbers: number[] = []
|
||||
let myPath: paper.Path | null
|
||||
let lastPoint: paper.Point
|
||||
let lastPoint: paper.Point | null = null
|
||||
let points: paper.Point[] = []
|
||||
|
||||
let pointTool = paperPointTool
|
||||
let draftId: number | null = null
|
||||
|
||||
const getDraftPointData = () => ({
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
draftId ??
|
||||
(draftId =
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1),
|
||||
})
|
||||
|
||||
const rebuildPointDraft = () => {
|
||||
myPath?.remove()
|
||||
myPath = null
|
||||
|
||||
if (points.length) {
|
||||
myPath = usePaperStore.getState().addPointLine(
|
||||
renderPath,
|
||||
points,
|
||||
{
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
getDraftPointData()
|
||||
)
|
||||
} else {
|
||||
draftId = null
|
||||
}
|
||||
|
||||
lastPoint = points.length
|
||||
? usePaperStore
|
||||
.getState()
|
||||
.group!.localToGlobal(points[points.length - 1])
|
||||
: null
|
||||
}
|
||||
|
||||
const resetPointDraft = () => {
|
||||
myPath?.remove()
|
||||
myPath = null
|
||||
points = []
|
||||
textNumbers = []
|
||||
myCircleText.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myCircleText = []
|
||||
myCircle.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myCircle = []
|
||||
lastPoint = null
|
||||
draftId = null
|
||||
}
|
||||
|
||||
const undoPoint = () => {
|
||||
if (!points.length) return false
|
||||
points.pop()
|
||||
textNumbers.pop()
|
||||
myCircle.pop()?.remove()
|
||||
myCircleText.pop()?.remove()
|
||||
rebuildPointDraft()
|
||||
return true
|
||||
}
|
||||
|
||||
pointTool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
if (e.event.button === 2) {
|
||||
e.event.preventDefault()
|
||||
hidePaperContextMenu()
|
||||
undoPoint()
|
||||
return
|
||||
}
|
||||
if (e.event.button !== 0) return
|
||||
|
||||
// 绘制时去除选中辅助
|
||||
@@ -4106,6 +4314,8 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
myCircleText = []
|
||||
// 避免后续按键删除已绘制的circle
|
||||
myCircle = []
|
||||
draftId = null
|
||||
lastPoint = null
|
||||
|
||||
return
|
||||
}
|
||||
@@ -4125,6 +4335,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let temp = renderCircle(usePaperStore.getState().group!, {})
|
||||
let point = temp.globalToLocal(e.point)
|
||||
let text = textNumbers.reduce((a, b) => Math.max(a, b), 0) + 1
|
||||
const draftData = getDraftPointData()
|
||||
|
||||
let circle = usePaperStore.getState().addPoint(
|
||||
renderCircle,
|
||||
@@ -4137,13 +4348,8 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
strokeScaling: false,
|
||||
},
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
...draftData,
|
||||
text: text,
|
||||
id:
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
|
||||
}
|
||||
)
|
||||
// add circle
|
||||
@@ -4174,14 +4380,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// option
|
||||
...usePaperStore.getState().toolOption,
|
||||
},
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
id:
|
||||
useRightToolsStore
|
||||
.getState()
|
||||
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
|
||||
}
|
||||
draftData
|
||||
)
|
||||
|
||||
lastPoint = e.point
|
||||
@@ -4208,7 +4407,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
fillColor: undefined,
|
||||
closed: false,
|
||||
},
|
||||
{}
|
||||
getDraftPointData()
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -4217,19 +4416,8 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
pointTool.onKeyDown = (e: paper.KeyEvent) => {
|
||||
if (e.key === "escape" || e.key === "alt") {
|
||||
if (myPath) {
|
||||
myPath.remove()
|
||||
myPath = null
|
||||
points = []
|
||||
textNumbers = []
|
||||
myCircleText.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myCircleText = []
|
||||
myCircle.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
myCircle = []
|
||||
if (myPath || points.length) {
|
||||
resetPointDraft()
|
||||
}
|
||||
} else if (e.key === "delete") {
|
||||
handleDelete()
|
||||
@@ -4283,6 +4471,12 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
circle.scale(newScale / currentScaling)
|
||||
circle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (e.event && e.event.button === 2) {
|
||||
if (
|
||||
["polygon", "brush", "point"].includes(
|
||||
usePaperStore.getState().mode || ""
|
||||
)
|
||||
)
|
||||
return
|
||||
// console.log(e.event.clientX, e.event.clientY);
|
||||
if (circle.data.id) {
|
||||
let category = useTopToolsStore
|
||||
@@ -4325,34 +4519,125 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
initSupportTool: (tool, renderPath) => {
|
||||
let points: Array<[number, number]> = []
|
||||
let tags: number[] = []
|
||||
let boxes: Array<[number, number, number, number]> = []
|
||||
let dragStartPoint: paper.Point | null = null
|
||||
let dragButton: number | null = null
|
||||
let supportBox: paper.Path.Rectangle | null = null
|
||||
let isBoxDragging = false
|
||||
|
||||
const BOX_DRAG_THRESHOLD = 6
|
||||
|
||||
const clearSupportBoxPreview = () => {
|
||||
supportBox?.remove()
|
||||
supportBox = null
|
||||
isBoxDragging = false
|
||||
}
|
||||
|
||||
const getSupportBox = (
|
||||
from: paper.Point,
|
||||
to: paper.Point
|
||||
): [number, number, number, number] => [
|
||||
Math.min(from.x, to.x),
|
||||
Math.min(from.y, to.y),
|
||||
Math.max(from.x, to.x),
|
||||
Math.max(from.y, to.y),
|
||||
]
|
||||
|
||||
const renderSupportBoxPreview = (from: paper.Point, to: paper.Point) => {
|
||||
const [left, top, right, bottom] = getSupportBox(from, to)
|
||||
supportBox?.remove()
|
||||
supportBox = new paper.Path.Rectangle({
|
||||
parent: usePaperStore.getState().group!,
|
||||
from: [left, top],
|
||||
to: [right, bottom],
|
||||
strokeColor: "#FFF",
|
||||
strokeWidth: 2,
|
||||
strokeScaling: false,
|
||||
dashArray: [6, 4],
|
||||
fillColor: new paper.Color(1, 1, 1, 0.05),
|
||||
data: {
|
||||
id: "support",
|
||||
type: "box",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const renderSupportPrompt = () => {
|
||||
void renderPath(points, tags, points.length + boxes.length === 1, boxes)
|
||||
}
|
||||
|
||||
tool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
|
||||
if (startMiddleMousePan(e.event)) return
|
||||
let point = usePaperStore.getState().group!.globalToLocal(e.point)
|
||||
points.push([point.x, point.y])
|
||||
tags.push(e.event.button === 0 ? 1 : 0)
|
||||
renderPath(points, tags, points.length === 1)
|
||||
dragStartPoint = usePaperStore.getState().group!.globalToLocal(e.point)
|
||||
dragButton = e.event.button
|
||||
clearSupportBoxPreview()
|
||||
}
|
||||
tool.onMouseDrag = (e: { event: MouseEvent; delta: paper.Point }) => {
|
||||
|
||||
tool.onMouseDrag = (e: {
|
||||
event: MouseEvent
|
||||
point: paper.Point
|
||||
delta: paper.Point
|
||||
}) => {
|
||||
if (dragMiddleMousePan(e)) return
|
||||
|
||||
if (!dragStartPoint || dragButton !== 0) return
|
||||
|
||||
const currentPoint = usePaperStore
|
||||
.getState()
|
||||
.group!.globalToLocal(e.point)
|
||||
if (currentPoint.subtract(dragStartPoint).length <= BOX_DRAG_THRESHOLD)
|
||||
return
|
||||
|
||||
isBoxDragging = true
|
||||
renderSupportBoxPreview(dragStartPoint, currentPoint)
|
||||
}
|
||||
tool.onMouseUp = (_e: paper.ToolEvent) => {
|
||||
stopMiddleMousePan()
|
||||
|
||||
tool.onMouseUp = (e: paper.ToolEvent) => {
|
||||
if (stopMiddleMousePan()) return
|
||||
if (!dragStartPoint || dragButton === null) return
|
||||
|
||||
const currentPoint = usePaperStore
|
||||
.getState()
|
||||
.group!.globalToLocal(e.point)
|
||||
|
||||
if (dragButton === 0 && isBoxDragging) {
|
||||
boxes.push(getSupportBox(dragStartPoint, currentPoint))
|
||||
} else {
|
||||
points.push([dragStartPoint.x, dragStartPoint.y])
|
||||
tags.push(dragButton === 0 ? 1 : 0)
|
||||
}
|
||||
|
||||
renderSupportPrompt()
|
||||
clearSupportBoxPreview()
|
||||
dragStartPoint = null
|
||||
dragButton = null
|
||||
}
|
||||
|
||||
tool.onKeyDown = (e: paper.KeyEvent) => {
|
||||
if (e.key === "escape") {
|
||||
points = []
|
||||
tags = []
|
||||
boxes = []
|
||||
dragStartPoint = null
|
||||
dragButton = null
|
||||
clearSupportBoxPreview()
|
||||
clearSupportAnnotationItem()
|
||||
usePaperStore.getState().setPaperMode("pan")
|
||||
}
|
||||
if (e.key === "f1") {
|
||||
points = []
|
||||
tags = []
|
||||
boxes = []
|
||||
dragStartPoint = null
|
||||
dragButton = null
|
||||
clearSupportBoxPreview()
|
||||
}
|
||||
}
|
||||
|
||||
tool.onMouseMove = (e: paper.MouseEvent) => {
|
||||
handleMouseMove(e)
|
||||
}
|
||||
|
||||
set((state: PaperState) => ({
|
||||
...state,
|
||||
supportTool: tool,
|
||||
|
||||
Reference in New Issue
Block a user