fix(key): b
This commit is contained in:
@@ -7,6 +7,7 @@ import { Comment } from "./api/label/typing"
|
||||
import { Project } from "./api/project/typing"
|
||||
import {
|
||||
initialDetail,
|
||||
type LabelPathTuple,
|
||||
useKeyEventStore,
|
||||
useLabelStore,
|
||||
useObjectStore,
|
||||
@@ -180,6 +181,7 @@ interface PaperState {
|
||||
getItemById: (id: number) => paper.Path | null
|
||||
getItemsById: (id: number) => Array<paper.Path | paper.CompoundPath>
|
||||
setContinuePolygonTarget: (target: ContinuePolygonTarget | null) => void
|
||||
beginContinuePolygonDraft: () => boolean
|
||||
setLoadingData: (flag: boolean) => void
|
||||
resizeGhostRequestToken: number
|
||||
requestResizeGhost: () => void
|
||||
@@ -844,6 +846,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
continuePolygonTarget: null,
|
||||
currentMousePosition: [0, 0],
|
||||
loadingData: false,
|
||||
beginContinuePolygonDraft: () => false,
|
||||
init: (
|
||||
initEl: HTMLCanvasElement,
|
||||
initScope: paper.PaperScope,
|
||||
@@ -3158,6 +3161,11 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
let continueSourceItems: Array<paper.Path | paper.CompoundPath> = []
|
||||
let continueTargetContourIndex: number | null = null
|
||||
let continueTargetContext: ContinuePolygonTarget | null = null
|
||||
let continueTargetSnapshot: LabelPathTuple | null = null
|
||||
let continueDraftHints = new Map<
|
||||
string,
|
||||
{ contourIndex: number; points: paper.Point[] }
|
||||
>()
|
||||
|
||||
const handleCircles = (path: paper.Path | paper.CompoundPath | null) => {
|
||||
// 更新选中的点
|
||||
@@ -3243,6 +3251,146 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
return null
|
||||
}
|
||||
|
||||
const getContinueCandidatePaths = (objectId: number) => {
|
||||
const flattenedPaths: paper.Path[] = []
|
||||
const visitedPaths = new Set<paper.Path>()
|
||||
|
||||
usePaperStore
|
||||
.getState()
|
||||
.getItemsById(objectId)
|
||||
.filter((item) => item.data?.type === "polygon")
|
||||
.forEach((item) => {
|
||||
if (item instanceof paper.CompoundPath) {
|
||||
;(item.children as paper.Path[]).forEach((child) => {
|
||||
if (visitedPaths.has(child)) return
|
||||
visitedPaths.add(child)
|
||||
flattenedPaths.push(child)
|
||||
})
|
||||
return
|
||||
}
|
||||
if (item instanceof paper.Path && !visitedPaths.has(item)) {
|
||||
visitedPaths.add(item)
|
||||
flattenedPaths.push(item)
|
||||
}
|
||||
})
|
||||
|
||||
const candidatePaths = flattenedPaths.filter(
|
||||
(path) => path.segments?.length > 3
|
||||
)
|
||||
if (!candidatePaths.length) return [] as paper.Path[]
|
||||
|
||||
const preferredPaths = candidatePaths.filter(
|
||||
(path) => !path.data?.isHollowPolygon
|
||||
)
|
||||
return preferredPaths.length ? preferredPaths : candidatePaths
|
||||
}
|
||||
|
||||
const clonePaperPoints = (pathPoints: paper.Point[]) => {
|
||||
return pathPoints.map((point) => new paper.Point(point))
|
||||
}
|
||||
|
||||
const removeDuplicatedClosingPoint = (pathPoints: paper.Point[]) => {
|
||||
if (pathPoints.length <= 1) return pathPoints
|
||||
const firstPoint = pathPoints[0]
|
||||
const lastPoint = pathPoints[pathPoints.length - 1]
|
||||
if (
|
||||
Math.abs(firstPoint.x - lastPoint.x) < 0.000001 &&
|
||||
Math.abs(firstPoint.y - lastPoint.y) < 0.000001
|
||||
) {
|
||||
pathPoints.pop()
|
||||
}
|
||||
return pathPoints
|
||||
}
|
||||
|
||||
const getContinueContourPoints = (segments: any[] | undefined) => {
|
||||
return removeDuplicatedClosingPoint(
|
||||
(segments || [])
|
||||
.map((segment) => getPointFromSegment(segment))
|
||||
.filter((point): point is paper.Point => point !== null)
|
||||
)
|
||||
}
|
||||
|
||||
const getContinueDraftHintKey = (target: ContinuePolygonTarget) => {
|
||||
return `${target.imageId}::${target.operationId}::${target.objectId}`
|
||||
}
|
||||
|
||||
const normalizeComparableContour = (pathPoints: paper.Point[]) => {
|
||||
return removeDuplicatedClosingPoint(clonePaperPoints(pathPoints)).map(
|
||||
(point) => [point.x, point.y] as [number, number]
|
||||
)
|
||||
}
|
||||
|
||||
const isSameComparablePoint = (
|
||||
pointA: [number, number],
|
||||
pointB: [number, number]
|
||||
) => {
|
||||
return (
|
||||
Math.abs(pointA[0] - pointB[0]) < 0.000001 &&
|
||||
Math.abs(pointA[1] - pointB[1]) < 0.000001
|
||||
)
|
||||
}
|
||||
|
||||
const isSameContourOrder = (
|
||||
contourA: [number, number][],
|
||||
contourB: [number, number][]
|
||||
) => {
|
||||
if (contourA.length !== contourB.length) return false
|
||||
if (!contourA.length) return false
|
||||
for (let offset = 0; offset < contourB.length; offset += 1) {
|
||||
let matched = true
|
||||
for (let index = 0; index < contourA.length; index += 1) {
|
||||
if (
|
||||
!isSameComparablePoint(
|
||||
contourA[index],
|
||||
contourB[(index + offset) % contourB.length]
|
||||
)
|
||||
) {
|
||||
matched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (matched) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const isSameContour = (pathA: paper.Point[], pathB: paper.Point[]) => {
|
||||
const contourA = normalizeComparableContour(pathA)
|
||||
const contourB = normalizeComparableContour(pathB)
|
||||
if (contourA.length !== contourB.length || !contourA.length) {
|
||||
return false
|
||||
}
|
||||
return (
|
||||
isSameContourOrder(contourA, contourB) ||
|
||||
isSameContourOrder(contourA, [...contourB].reverse())
|
||||
)
|
||||
}
|
||||
|
||||
const findMatchingContinueContourIndex = (
|
||||
candidatePoints: paper.Point[],
|
||||
contours: any[]
|
||||
) => {
|
||||
for (let index = contours.length - 1; index >= 0; index -= 1) {
|
||||
const contourPoints = getContinueContourPoints(contours[index])
|
||||
if (isSameContour(candidatePoints, contourPoints)) {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
const setContinueDraftHint = (
|
||||
target: ContinuePolygonTarget,
|
||||
contourIndex: number,
|
||||
contourPoints: paper.Point[]
|
||||
) => {
|
||||
if (contourIndex < 0 || !contourPoints.length) return
|
||||
continueDraftHints.set(getContinueDraftHintKey(target), {
|
||||
contourIndex,
|
||||
points: removeDuplicatedClosingPoint(clonePaperPoints(contourPoints)),
|
||||
})
|
||||
}
|
||||
|
||||
const clearContinueSourceItems = (restoreVisible: boolean) => {
|
||||
continueSourceItems.forEach((item) => {
|
||||
if (restoreVisible) {
|
||||
@@ -3258,18 +3406,26 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
clearContinueSourceItems(restoreVisible)
|
||||
continueTargetContourIndex = null
|
||||
continueTargetContext = null
|
||||
continueTargetSnapshot = null
|
||||
usePaperStore.getState().setContinuePolygonTarget(null)
|
||||
}
|
||||
|
||||
const hasContinuePolygonDraft = () => {
|
||||
return !!(
|
||||
continueTargetContext ||
|
||||
continueTargetSnapshot ||
|
||||
usePaperStore.getState().continuePolygonTarget
|
||||
)
|
||||
}
|
||||
|
||||
const startContinuePolygonDraft = () => {
|
||||
const target = usePaperStore.getState().continuePolygonTarget
|
||||
if (!target) return false
|
||||
|
||||
const imageLabel = useLabelStore.getState().label.get(target.imageId)
|
||||
const operationPaths = imageLabel?.get(target.operationId) || []
|
||||
const targetTuple = operationPaths.find(
|
||||
(path) => path[0] === target.objectId
|
||||
)
|
||||
const targetTuple =
|
||||
operationPaths.find((path) => path[0] === target.objectId) || null
|
||||
if (!targetTuple) {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
@@ -3280,14 +3436,71 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
|
||||
const nextPoints = safeClone(targetTuple[1] || [])
|
||||
const continuePaths = getContinueCandidatePaths(target.objectId)
|
||||
const continuePath = continuePaths[continuePaths.length - 1]
|
||||
const continuePathPoints = continuePath?.segments?.length
|
||||
? removeDuplicatedClosingPoint(
|
||||
continuePath.segments.map(
|
||||
(segment) => new paper.Point(segment.point)
|
||||
)
|
||||
)
|
||||
: []
|
||||
const continueHint = continueDraftHints.get(
|
||||
getContinueDraftHintKey(target)
|
||||
)
|
||||
|
||||
let contourIndex = -1
|
||||
for (let i = nextPoints.length - 1; i >= 0; i -= 1) {
|
||||
if (nextPoints[i]?.length > 3) {
|
||||
if (
|
||||
continueHint &&
|
||||
continueHint.contourIndex >= 0 &&
|
||||
continueHint.contourIndex < nextPoints.length
|
||||
) {
|
||||
contourIndex = continueHint.contourIndex
|
||||
} else if (continuePathPoints.length) {
|
||||
contourIndex = findMatchingContinueContourIndex(
|
||||
continuePathPoints,
|
||||
nextPoints
|
||||
)
|
||||
}
|
||||
for (
|
||||
let i = nextPoints.length - 1;
|
||||
contourIndex === -1 && i >= 0;
|
||||
i -= 1
|
||||
) {
|
||||
if (nextPoints[i]?.length > 2) {
|
||||
contourIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if (contourIndex === -1) {
|
||||
if (contourIndex === -1 && nextPoints.length) {
|
||||
contourIndex = nextPoints.length - 1
|
||||
}
|
||||
|
||||
let sourceContourPoints = continueHint?.points?.length
|
||||
? clonePaperPoints(continueHint.points)
|
||||
: []
|
||||
|
||||
if (
|
||||
sourceContourPoints.length &&
|
||||
continuePathPoints.length &&
|
||||
!isSameContour(sourceContourPoints, continuePathPoints)
|
||||
) {
|
||||
sourceContourPoints = []
|
||||
}
|
||||
|
||||
if (!sourceContourPoints.length && continuePathPoints.length) {
|
||||
sourceContourPoints = clonePaperPoints(continuePathPoints)
|
||||
}
|
||||
|
||||
if (!sourceContourPoints.length && contourIndex >= 0) {
|
||||
sourceContourPoints = getContinueContourPoints(nextPoints[contourIndex])
|
||||
}
|
||||
|
||||
const contourPoints = sourceContourPoints
|
||||
.slice(0, -1)
|
||||
.map((point) => new paper.Point(point))
|
||||
|
||||
if (!sourceContourPoints.length) {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
message: "当前对象没有可继续标注的区域",
|
||||
@@ -3296,17 +3509,10 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
return false
|
||||
}
|
||||
|
||||
const contourSegments = safeClone(nextPoints[contourIndex] || [])
|
||||
const continueSegments = contourSegments.slice().reverse()
|
||||
continueSegments.pop()
|
||||
const contourPoints = continueSegments
|
||||
.map((segment: any) => getPointFromSegment(segment))
|
||||
.filter((point: paper.Point | null): point is paper.Point => !!point)
|
||||
|
||||
if (contourPoints.length < 3) {
|
||||
if (contourPoints.length < 2) {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
message: "当前区域至少保留3个节点,无法继续撤回",
|
||||
message: "当前区域至少保留2个节点,无法继续撤回",
|
||||
})
|
||||
clearContinuePolygonContext(true)
|
||||
return false
|
||||
@@ -3321,8 +3527,19 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
item.visible = false
|
||||
})
|
||||
|
||||
useOtherToolsStore.getState().setShowContextMenu({
|
||||
showContextMenu: false,
|
||||
position: { top: 0, left: 0 },
|
||||
imageId: target.imageId,
|
||||
operationId: target.operationId,
|
||||
id: target.objectId,
|
||||
})
|
||||
usePaperSupportStore.getState().clearAll()
|
||||
useObjectStore.getState().updateSelectedPath(target.imageId, "ADD")
|
||||
|
||||
continueTargetContourIndex = contourIndex
|
||||
continueTargetContext = target
|
||||
continueTargetSnapshot = safeClone(targetTuple) as LabelPathTuple
|
||||
points = contourPoints
|
||||
draftId = target.objectId
|
||||
polygon?.remove()
|
||||
@@ -3351,14 +3568,8 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
const saveContinuePolygonDraft = (draftPath: paper.Path) => {
|
||||
const target =
|
||||
continueTargetContext || usePaperStore.getState().continuePolygonTarget
|
||||
if (!target) return false
|
||||
|
||||
const imageLabel = useLabelStore.getState().label.get(target.imageId)
|
||||
const operationPaths = imageLabel?.get(target.operationId) || []
|
||||
const targetTuple = operationPaths.find(
|
||||
(path) => path[0] === target.objectId
|
||||
)
|
||||
if (!targetTuple) return false
|
||||
const targetTuple = continueTargetSnapshot
|
||||
if (!target || !targetTuple) return false
|
||||
|
||||
const pathData = JSON.parse(draftPath.exportJSON({ precision: 20 }))
|
||||
const nextContour = pathData?.[1]?.segments
|
||||
@@ -3382,6 +3593,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
safeClone(targetTuple[2] || initialDetail),
|
||||
safeClone(targetTuple[3] || []),
|
||||
])
|
||||
setContinueDraftHint(target, contourIndex, clonePaperPoints(points))
|
||||
useObjectStore
|
||||
.getState()
|
||||
.updateSelectedPath(target.imageId, "ADD", target.objectId)
|
||||
@@ -3673,6 +3885,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
|
||||
const points: any[] = []
|
||||
const hollowPoints: any[] = []
|
||||
let lastOuterContourPoints: paper.Point[] = []
|
||||
const nextPaths =
|
||||
nextPolygon instanceof paper.CompoundPath
|
||||
? (nextPolygon.children as paper.Path[])
|
||||
@@ -3700,6 +3913,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
hollowPoints.push(segments)
|
||||
} else {
|
||||
points.push(segments)
|
||||
lastOuterContourPoints = getContinueContourPoints(segments)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3728,6 +3942,15 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
prevDetail,
|
||||
hollowPoints,
|
||||
])
|
||||
setContinueDraftHint(
|
||||
{
|
||||
imageId: activeImage,
|
||||
operationId,
|
||||
objectId: selectedId,
|
||||
},
|
||||
points.length - 1,
|
||||
lastOuterContourPoints
|
||||
)
|
||||
useObjectStore
|
||||
.getState()
|
||||
.updateSelectedPath(activeImage, "ADD", selectedId)
|
||||
@@ -3797,6 +4020,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
|
||||
const points: any[] = []
|
||||
const hollowPoints: any[] = []
|
||||
let lastOuterContourPoints: paper.Point[] = []
|
||||
const nextPaths =
|
||||
nextPolygon instanceof paper.CompoundPath
|
||||
? (nextPolygon.children as paper.Path[])
|
||||
@@ -3824,6 +4048,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
hollowPoints.push(segments)
|
||||
} else {
|
||||
points.push(segments)
|
||||
lastOuterContourPoints = getContinueContourPoints(segments)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3852,6 +4077,15 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
prevDetail,
|
||||
hollowPoints,
|
||||
])
|
||||
setContinueDraftHint(
|
||||
{
|
||||
imageId: activeImage,
|
||||
operationId,
|
||||
objectId: selectedId,
|
||||
},
|
||||
points.length - 1,
|
||||
lastOuterContourPoints
|
||||
)
|
||||
useObjectStore
|
||||
.getState()
|
||||
.updateSelectedPath(activeImage, "ADD", selectedId)
|
||||
@@ -3874,6 +4108,9 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
if (polygon || points.length) {
|
||||
hidePaperContextMenu()
|
||||
undoPolygonPoint()
|
||||
if (!points.length && hasContinuePolygonDraft()) {
|
||||
clearContinuePolygonContext(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (!trySelectPaperObjectByPoint(e.point)) {
|
||||
@@ -3889,11 +4126,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
usePaperSupportStore.getState().clearAll()
|
||||
}
|
||||
|
||||
if (
|
||||
!polygon &&
|
||||
!points.length &&
|
||||
usePaperStore.getState().continuePolygonTarget
|
||||
) {
|
||||
if (!polygon && !points.length && hasContinuePolygonDraft()) {
|
||||
if (!startContinuePolygonDraft()) return
|
||||
}
|
||||
|
||||
@@ -3917,7 +4150,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
// 初始化 compoundPolygon
|
||||
compoundPolygon = new paper.Path()
|
||||
|
||||
if (usePaperStore.getState().continuePolygonTarget) {
|
||||
if (hasContinuePolygonDraft()) {
|
||||
;(polygon as paper.Path).closePath()
|
||||
const continueSaved = saveContinuePolygonDraft(
|
||||
polygon as paper.Path
|
||||
@@ -4089,11 +4322,15 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
if (polygon.children && polygon.children.length) {
|
||||
let points: any[] = []
|
||||
let hollowPoints: any[] = []
|
||||
let lastOuterContourPoints: paper.Point[] = []
|
||||
;(polygon.children as paper.Path[]).forEach((path) => {
|
||||
let pathData = JSON.parse(path.exportJSON({ precision: 20 }))
|
||||
// 在镂空或分割的区域中 路径方向是false
|
||||
if (path.clockwise) {
|
||||
points.push(pathData[1]?.segments)
|
||||
lastOuterContourPoints = getContinueContourPoints(
|
||||
pathData[1]?.segments
|
||||
)
|
||||
} else {
|
||||
hollowPoints.push(pathData[1]?.segments)
|
||||
}
|
||||
@@ -4129,6 +4366,17 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
hollowPoints,
|
||||
]
|
||||
)
|
||||
if (points.length) {
|
||||
setContinueDraftHint(
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
objectId: polygon.data.id,
|
||||
},
|
||||
points.length - 1,
|
||||
lastOuterContourPoints
|
||||
)
|
||||
}
|
||||
if (!useTopToolsStore.getState().pressA)
|
||||
useRightToolsStore.getState().pushPathId(polygon.data.id)
|
||||
}
|
||||
@@ -4163,6 +4411,15 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
[],
|
||||
]
|
||||
)
|
||||
setContinueDraftHint(
|
||||
{
|
||||
imageId: useBottomToolsStore.getState().activeImage,
|
||||
operationId: useTopToolsStore.getState().activeOperation,
|
||||
objectId: polygon.data.id,
|
||||
},
|
||||
0,
|
||||
clonePaperPoints(points)
|
||||
)
|
||||
useRightToolsStore.getState().pushPathId(polygon.data.id)
|
||||
}
|
||||
|
||||
@@ -4393,11 +4650,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
}
|
||||
polygonTool.onKeyDown = (e: paper.KeyEvent) => {
|
||||
if (e.key === "escape" || e.key === "alt") {
|
||||
if (
|
||||
polygon ||
|
||||
points.length ||
|
||||
usePaperStore.getState().continuePolygonTarget
|
||||
) {
|
||||
if (polygon || points.length || hasContinuePolygonDraft()) {
|
||||
resetPolygonDraft()
|
||||
clearContinuePolygonContext(true)
|
||||
usePaperSupportStore.getState().handleBufferPaths(null)
|
||||
@@ -4417,6 +4670,7 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
set((state: PaperState) => ({
|
||||
...state,
|
||||
polygonTool: polygonTool,
|
||||
beginContinuePolygonDraft: () => startContinuePolygonDraft(),
|
||||
}))
|
||||
},
|
||||
addRectangle: (
|
||||
|
||||
Reference in New Issue
Block a user