diff --git a/components/label/LabelNossr.tsx b/components/label/LabelNossr.tsx index 478966a..e4241e9 100644 --- a/components/label/LabelNossr.tsx +++ b/components/label/LabelNossr.tsx @@ -1627,6 +1627,8 @@ const LabelPage = ({ objectId: selectedContext.objectId, }) usePaperStore.getState().setPaperMode("polygon") + const started = usePaperStore.getState().beginContinuePolygonDraft() + if (!started) return notifications.show({ color: "green", message: "已进入继续标注模式", diff --git a/components/label/components/RightGroupTools.tsx b/components/label/components/RightGroupTools.tsx index e602285..1b941df 100644 --- a/components/label/components/RightGroupTools.tsx +++ b/components/label/components/RightGroupTools.tsx @@ -25,8 +25,8 @@ import { getShowContextMenuPosition, usePaperStore } from "../usePaperStore" import { renderGroupPath } from "../useRenderGroupPath" import { useRightToolsStore } from "../useRightToolsStore" import { useTopToolsStore } from "../useTopToolsStore" -import { labelTypeMap } from "../utils/constants" import { safeClone } from "../utils/clone" +import { labelTypeMap } from "../utils/constants" import RightGroupEditModal from "./RightGroupEditModal" interface RightGroupToolsComponentProps { @@ -56,9 +56,25 @@ const RightGroupTools: React.FC = (props) => { const { activeImage } = useBottomToolsStore() const { groupStatus, updateGroupStatus, selectedPath, updateSelectedPath } = useObjectStore() + const continuePolygonTarget = usePaperStore( + (state) => state.continuePolygonTarget + ) const { getItemById, getItemsById } = usePaperStore() const { shift: pressShift } = useKeyEventStore() const [isGroupCollapsed, setIsGroupCollapsed] = useState(true) + const continueObjectId = + continuePolygonTarget?.imageId === activeImage + ? continuePolygonTarget.objectId + : null + + const getGroupChildren = useCallback( + (groupId: number) => { + const groupChildren = pathGroupMap.get(activeImage)?.get(groupId) || [] + if (continueObjectId === null) return groupChildren + return groupChildren.filter((childId) => childId !== continueObjectId) + }, + [activeImage, continueObjectId, pathGroupMap] + ) const handleSelect = useCallback( (id: number) => { @@ -322,8 +338,10 @@ const RightGroupTools: React.FC = (props) => { {pathGroupMap.get(activeImage) && - Array.from(pathGroupMap.get(activeImage)!.keys())?.map( - (groupId) => { + Array.from(pathGroupMap.get(activeImage)!.keys()) + .filter((groupId) => getGroupChildren(groupId).length) + .map((groupId) => { + const groupChildren = getGroupChildren(groupId) const isOpen = !groupStatus[activeImage + groupId]?.["COLLAPSE"] return ( @@ -376,10 +394,7 @@ const RightGroupTools: React.FC = (props) => { - - {pathGroupMap.get(activeImage)?.get(groupId) - ?.length || 0} - + {groupChildren.length} = (props) => { - {pathGroupMap - .get(activeImage) - ?.get(groupId) - ?.map((child) => { - const isSelected = - selectedPath[activeImage]?.includes(child) - return ( - { - handleSelect(child) - }} - style={{ cursor: "pointer" }}> - - - {child}- - {labelTypeMap.get( - getOperationSchema( - getOperationIdByPathId(child) - )?.label_type || 0 - )} - - - - ) - })} + {groupChildren.map((child) => { + const isSelected = + selectedPath[activeImage]?.includes(child) + return ( + { + handleSelect(child) + }} + style={{ cursor: "pointer" }}> + + + {child}- + {labelTypeMap.get( + getOperationSchema( + getOperationIdByPathId(child) + )?.label_type || 0 + )} + + + + ) + })} ) - } - )} + })} diff --git a/components/label/components/RightObjectTools.tsx b/components/label/components/RightObjectTools.tsx index 91ebed6..1a4e4ea 100644 --- a/components/label/components/RightObjectTools.tsx +++ b/components/label/components/RightObjectTools.tsx @@ -42,8 +42,8 @@ import { getShowContextMenuPosition, usePaperStore } from "../usePaperStore" import { usePaperSupportStore } from "../usePaperSupportStore" import { useTopToolsStore } from "../useTopToolsStore" import { getSubAttrStatus } from "../util" -import { toggleObjectHideByShortcut } from "../utils/objectVisibility" import { labelTypeMap } from "../utils/constants" +import { toggleObjectHideByShortcut } from "../utils/objectVisibility" interface RightObjectToolsComponentProps { labelState: LabelState @@ -143,7 +143,27 @@ const RightObjectTools: React.FC = (props) => { const { shift: pressShift } = useKeyEventStore() + const continuePolygonTarget = usePaperStore( + (state) => state.continuePolygonTarget + ) const { getItemById, getItemsById } = usePaperStore() + const continueObjectId = + continuePolygonTarget?.imageId === activeImage + ? continuePolygonTarget.objectId + : null + + const getOperationChildren = useCallback( + (imageId: string, operationId: string) => { + const operationChildren = storeLabel.get(imageId)?.get(operationId) || [] + if (continueObjectId === null || imageId !== activeImage) { + return operationChildren + } + return operationChildren.filter( + ([objectId]) => objectId !== continueObjectId + ) + }, + [activeImage, continueObjectId, storeLabel] + ) // 切换选中对象时,清除所有其他对象的当前选中状态 const clearSelectedItems = () => { @@ -381,14 +401,11 @@ const RightObjectTools: React.FC = (props) => { (operationId: string, hide: boolean) => { updateOperationStatus(activeImage + operationId, "HIDE", hide) - storeLabel - .get(activeImage) - ?.get(operationId) - ?.map((child) => { - toggleObjectHideByShortcut(activeImage, operationId, child[0], hide) - }) + getOperationChildren(activeImage, operationId).map((child) => { + toggleObjectHideByShortcut(activeImage, operationId, child[0], hide) + }) }, - [activeImage, storeLabel, updateOperationStatus] + [activeImage, getOperationChildren, updateOperationStatus] ) const objectHide = useCallback( @@ -398,14 +415,16 @@ const RightObjectTools: React.FC = (props) => { [activeImage] ) - const activeImageOperations = labelState.get(activeImage) || [] + const activeImageOperations = (labelState.get(activeImage) || []).filter( + (operationId) => getOperationChildren(activeImage, operationId).length + ) const isObjectHidden = (imageId: string, objectId: number) => { return pathStatus[imageId + objectId]?.["HIDE"] ?? false } const isOperationHidden = (imageId: string, operationId: string) => { - const operationChildren = storeLabel.get(imageId)?.get(operationId) || [] + const operationChildren = getOperationChildren(imageId, operationId) return ( operationChildren.length > 0 && operationChildren.every(([objectId]) => isObjectHidden(imageId, objectId)) @@ -414,9 +433,7 @@ const RightObjectTools: React.FC = (props) => { const totalObjectCount = activeImageOperations.reduce( (count, operationId) => { - return ( - count + (storeLabel.get(activeImage)?.get(operationId)?.length || 0) - ) + return count + getOperationChildren(activeImage, operationId).length }, 0 ) @@ -424,8 +441,7 @@ const RightObjectTools: React.FC = (props) => { const areAllObjectsHidden = totalObjectCount > 0 && activeImageOperations.every((operationId) => { - const operationChildren = - storeLabel.get(activeImage)?.get(operationId) || [] + const operationChildren = getOperationChildren(activeImage, operationId) return operationChildren.every(([objectId]) => isObjectHidden(activeImage, objectId) ) @@ -451,6 +467,9 @@ const RightObjectTools: React.FC = (props) => { .get(activeImage) ?.get(operationId) ?.some((child) => { + if (continueObjectId !== null && child[0] === continueObjectId) { + return false + } return getSubAttrStatus( getOperationSchema(operationId), child[2]?.sub_attributes @@ -458,7 +477,7 @@ const RightObjectTools: React.FC = (props) => { }) return bool }, - [activeImage, getOperationSchema, storeLabel] + [activeImage, continueObjectId, getOperationSchema, storeLabel] ) // 搜索标注对象id @@ -468,7 +487,8 @@ const RightObjectTools: React.FC = (props) => { const id = Number(searchId) const labelObjMap = storeLabel.get(activeImage) Array.from(labelObjMap!.entries()).forEach(([operationId, objList]) => { - objList.forEach((obj) => { + false && objList + getOperationChildren(activeImage, operationId).forEach((obj) => { if (id === obj[0]) { handleSelect(id, operationId) } @@ -483,7 +503,7 @@ const RightObjectTools: React.FC = (props) => { if (e.key === "Enter") { Array.from(storeLabel.entries()).forEach(([imgId, objectMap]) => { if (objectMap.get(currentOperationId)) { - const list = objectMap.get(currentOperationId) + const list = getOperationChildren(imgId, currentOperationId) list!.forEach((item) => { const { sub_attributes } = item[2] // 如果没有子属性搜索值,则把对象HIDE设为false @@ -616,8 +636,10 @@ const RightObjectTools: React.FC = (props) => { const isHide = isOperationHidden(activeImage, operationId) const hasSubAttrError = getOperationSubAttrStatus(operationId) const operationLabel = operationSchema?.label_class - const operationChildren = - storeLabel.get(activeImage)?.get(operationId) || [] + const operationChildren = getOperationChildren( + activeImage, + operationId + ) const objectCount = operationChildren.length const visibleChildren = operationChildren.filter((child) => { const { comment } = child[2] diff --git a/components/label/store.ts b/components/label/store.ts index a479563..794a335 100644 --- a/components/label/store.ts +++ b/components/label/store.ts @@ -5,8 +5,8 @@ import { persist, PersistStorage, StorageValue } from "zustand/middleware" import { Comment } from "./api/label/typing" import { safeClone } from "./utils/clone" -type LabelData = Map> -type LabelPathTuple = [number, any[], any, any[]] +export type LabelData = Map> +export type LabelPathTuple = [number, any[], any, any[]] type LabelTime = { label: number review1: number diff --git a/components/label/usePaperStore.ts b/components/label/usePaperStore.ts index dda3c7f..73fa3dd 100644 --- a/components/label/usePaperStore.ts +++ b/components/label/usePaperStore.ts @@ -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 setContinuePolygonTarget: (target: ContinuePolygonTarget | null) => void + beginContinuePolygonDraft: () => boolean setLoadingData: (flag: boolean) => void resizeGhostRequestToken: number requestResizeGhost: () => void @@ -844,6 +846,7 @@ export const usePaperStore = create((set) => ({ continuePolygonTarget: null, currentMousePosition: [0, 0], loadingData: false, + beginContinuePolygonDraft: () => false, init: ( initEl: HTMLCanvasElement, initScope: paper.PaperScope, @@ -3158,6 +3161,11 @@ export const usePaperStore = create((set) => ({ let continueSourceItems: Array = [] 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((set) => ({ return null } + const getContinueCandidatePaths = (objectId: number) => { + const flattenedPaths: paper.Path[] = [] + const visitedPaths = new Set() + + 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((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((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((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((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((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((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((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((set) => ({ hollowPoints.push(segments) } else { points.push(segments) + lastOuterContourPoints = getContinueContourPoints(segments) } }) @@ -3728,6 +3942,15 @@ export const usePaperStore = create((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((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((set) => ({ hollowPoints.push(segments) } else { points.push(segments) + lastOuterContourPoints = getContinueContourPoints(segments) } }) @@ -3852,6 +4077,15 @@ export const usePaperStore = create((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((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((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((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((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((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((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((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((set) => ({ set((state: PaperState) => ({ ...state, polygonTool: polygonTool, + beginContinuePolygonDraft: () => startContinuePolygonDraft(), })) }, addRectangle: (