fix(bug): fix

This commit is contained in:
zhangheng
2026-04-03 13:37:37 +08:00
parent d7d2bbf43f
commit bbde4df329
5 changed files with 610 additions and 177 deletions

View File

@@ -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()

View File

@@ -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 || {}

View File

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