feat(circle): change size

This commit is contained in:
zhangheng
2026-04-15 11:04:42 +08:00
parent d3d9e8385b
commit 72b1dacf1c
4 changed files with 133 additions and 39 deletions

View File

@@ -1022,9 +1022,6 @@ const PaperContainer = (
parent: usePaperStore.getState().group, parent: usePaperStore.getState().group,
strokeScaling: false, strokeScaling: false,
}) })
if (Math.abs(path.area) < useTopToolsStore.getState().checkSize) {
path.remove()
} else {
if (hollows[index]) { if (hollows[index]) {
path.data.isHollowPolygon = true path.data.isHollowPolygon = true
innerPaths.push(path) innerPaths.push(path)
@@ -1033,7 +1030,6 @@ const PaperContainer = (
outerPaths.push(path) outerPaths.push(path)
} }
} }
}
}) })
const compoundPath = new paper.CompoundPath({ const compoundPath = new paper.CompoundPath({
children: [...outerPaths, ...innerPaths], children: [...outerPaths, ...innerPaths],

View File

@@ -92,7 +92,12 @@ import { useKeyboardStore } from "../useKeyBoardStore"
import { usePaperStore } from "../usePaperStore" import { usePaperStore } from "../usePaperStore"
import { useRightToolsStore } from "../useRightToolsStore" import { useRightToolsStore } from "../useRightToolsStore"
import { useIntervalStore, useLabelTimeStore } from "../useTimerStore" import { useIntervalStore, useLabelTimeStore } from "../useTimerStore"
import { useTopToolsStore } from "../useTopToolsStore" import {
DEFAULT_NODE_SIZE,
NODE_SIZE_MAX,
NODE_SIZE_MIN,
useTopToolsStore,
} from "../useTopToolsStore"
import { getSubAttrStatus } from "../util" import { getSubAttrStatus } from "../util"
import { safeClone } from "../utils/clone" import { safeClone } from "../utils/clone"
import { labelTypeMap, TaskStatusEnum } from "../utils/constants" import { labelTypeMap, TaskStatusEnum } from "../utils/constants"
@@ -222,13 +227,14 @@ const TopTools = (
setAssistToolEnabled, setAssistToolEnabled,
assistToolSize, assistToolSize,
setAssistToolSize, setAssistToolSize,
nodeSize,
setNodeSize,
needBackup, needBackup,
setNeedBackup, setNeedBackup,
checkSize,
setCheckSize,
magnetFlag, magnetFlag,
setMagnetFlag, setMagnetFlag,
} = useTopToolsStore() } = useTopToolsStore()
const [nodeSizeInput, setNodeSizeInput] = useState<number | string>(nodeSize)
const { pathGroupMap } = useRightToolsStore() const { pathGroupMap } = useRightToolsStore()
const isAutoSave = useIntervalStore((state) => state.isAutoSave) const isAutoSave = useIntervalStore((state) => state.isAutoSave)
@@ -247,6 +253,11 @@ const TopTools = (
].filter(Boolean).length ].filter(Boolean).length
}, [projectDetail, showDescList, showGroupList, showObjectList, showTaskList]) }, [projectDetail, showDescList, showGroupList, showObjectList, showTaskList])
useEffect(() => {
setNodeSizeInput(nodeSize)
usePaperStore.getState().refreshNodeVisualSize()
}, [nodeSize])
const toggleRightPanel = useCallback( const toggleRightPanel = useCallback(
(visible: boolean, setter: (val: boolean) => void) => { (visible: boolean, setter: (val: boolean) => void) => {
const nextVisible = !visible const nextVisible = !visible
@@ -2188,23 +2199,39 @@ const TopTools = (
</Flex> </Flex>
</Flex> </Flex>
<Flex justify="space-between" align="center" px="xs" gap="xs"> <Flex justify="space-between" align="center" px="xs" gap="xs">
<Tooltip label={`节点直径范围 ${NODE_SIZE_MIN}-${NODE_SIZE_MAX}`}>
<Flex align="center" gap={4}>
<Text span size="xs" style={{ minWidth: "fit-content" }}>
</Text>
<NumberInput <NumberInput
value={checkSize} value={nodeSizeInput}
w={50} w={50}
min={1} min={NODE_SIZE_MIN}
max={NODE_SIZE_MAX}
step={1}
allowDecimal={false}
size="xs" size="xs"
radius="sm" radius="sm"
hideControls hideControls
onChange={(v) => { onChange={(value) => {
if (v) setCheckSize(Number(v)) setNodeSizeInput(value)
}} }}
onFocus={() => { onFocus={() => {
useKeyEventStore.getState().setFocusInput(true) useKeyEventStore.getState().setFocusInput(true)
}} }}
onBlur={() => { onBlur={(event) => {
useKeyEventStore.getState().setFocusInput(false) useKeyEventStore.getState().setFocusInput(false)
const rawValue = event.currentTarget.value.trim()
const nextValue =
rawValue === "" ? DEFAULT_NODE_SIZE : Number(rawValue)
setNodeSize(
Number.isFinite(nextValue) ? nextValue : DEFAULT_NODE_SIZE
)
}} }}
/> />
</Flex>
</Tooltip>
{renderEditIcon} {renderEditIcon}
<Button <Button
w="fit-content" w="fit-content"

View File

@@ -18,7 +18,7 @@ import { useKeyboardStore } from "./useKeyBoardStore"
import { useOtherToolsStore } from "./useOtherToolsStore" import { useOtherToolsStore } from "./useOtherToolsStore"
import { usePaperSupportStore } from "./usePaperSupportStore" import { usePaperSupportStore } from "./usePaperSupportStore"
import { useRightToolsStore } from "./useRightToolsStore" import { useRightToolsStore } from "./useRightToolsStore"
import { useTopToolsStore } from "./useTopToolsStore" import { DEFAULT_NODE_SIZE, useTopToolsStore } from "./useTopToolsStore"
import { safeClone } from "./utils/clone" import { safeClone } from "./utils/clone"
interface ContinuePolygonTarget { interface ContinuePolygonTarget {
@@ -183,6 +183,7 @@ interface PaperState {
setContinuePolygonTarget: (target: ContinuePolygonTarget | null) => void setContinuePolygonTarget: (target: ContinuePolygonTarget | null) => void
beginContinuePolygonDraft: () => boolean beginContinuePolygonDraft: () => boolean
setLoadingData: (flag: boolean) => void setLoadingData: (flag: boolean) => void
refreshNodeVisualSize: () => void
resizeGhostRequestToken: number resizeGhostRequestToken: number
requestResizeGhost: () => void requestResizeGhost: () => void
} }
@@ -328,6 +329,7 @@ const PAPER_CONTEXT_HIT_TYPES = [
"pathBuff", "pathBuff",
"pathCircle", "pathCircle",
] as const ] as const
const LEGACY_PAPER_NODE_DIAMETER = 6
const isPaperObjectType = (type: unknown) => const isPaperObjectType = (type: unknown) =>
typeof type === "string" && typeof type === "string" &&
@@ -339,6 +341,39 @@ const isPaperContextHitType = (type: unknown) =>
type as (typeof PAPER_CONTEXT_HIT_TYPES)[number] type as (typeof PAPER_CONTEXT_HIT_TYPES)[number]
) )
const getPaperNodeDiameter = () => {
const configuredDiameter = useTopToolsStore.getState().nodeSize
return Number.isFinite(configuredDiameter)
? Math.round(configuredDiameter)
: DEFAULT_NODE_SIZE
}
const getPaperNodeRadius = () => getPaperNodeDiameter() / 2
const getPaperNodeScaleFactor = () => {
const currentScaling = usePaperStore.getState().group?.scaling.x || 1
const newScale = useTopToolsStore.getState().scale / currentScaling
return newScale / currentScaling
}
const syncPaperCircleDiameter = (circle: paper.Path) => {
const nextDiameter = getPaperNodeDiameter()
const currentDiameter =
Number(circle.data?.diameter) || LEGACY_PAPER_NODE_DIAMETER
if (
Number.isFinite(currentDiameter) &&
currentDiameter > 0 &&
Math.abs(currentDiameter - nextDiameter) > 0.001
) {
circle.scale(nextDiameter / currentDiameter)
}
circle.data = Object.assign({}, circle.data, {
diameter: nextDiameter,
})
}
const getPaperCursorByMode = ( const getPaperCursorByMode = (
mode: PaperState["mode"] = usePaperStore.getState().mode mode: PaperState["mode"] = usePaperStore.getState().mode
) => { ) => {
@@ -529,6 +564,7 @@ const createPointCircleForPath = (
imageId: path.data.imageId, imageId: path.data.imageId,
operationId: path.data.operationId, operationId: path.data.operationId,
text: index + 1, text: index + 1,
diameter: getPaperNodeDiameter(),
fillColor: path.data.blankColor, fillColor: path.data.blankColor,
strokeColor: path.data.strokeColor, strokeColor: path.data.strokeColor,
}) })
@@ -562,6 +598,7 @@ const syncPointCirclesWithPath = (path: paper.Path) => {
imageId: path.data.imageId, imageId: path.data.imageId,
operationId: path.data.operationId, operationId: path.data.operationId,
text: index + 1, text: index + 1,
diameter: getPaperNodeDiameter(),
fillColor: path.data.blankColor, fillColor: path.data.blankColor,
strokeColor: path.data.strokeColor, strokeColor: path.data.strokeColor,
}) })
@@ -1014,6 +1051,19 @@ export const usePaperStore = create<PaperState>((set) => ({
...state, ...state,
loadingData: flag, loadingData: flag,
})), })),
refreshNodeVisualSize: () => {
const group = usePaperStore.getState().group
if (!group) return
;(
group.getItems({
match: (item: paper.Item) =>
item instanceof paper.Path &&
["circle", "pathCircle"].includes(item.data?.type || ""),
}) as paper.Path[]
).forEach((circle) => {
syncPaperCircleDiameter(circle)
})
},
requestResizeGhost: () => requestResizeGhost: () =>
set((state: PaperState) => ({ set((state: PaperState) => ({
...state, ...state,
@@ -5427,6 +5477,7 @@ export const usePaperStore = create<PaperState>((set) => ({
const newScale = useTopToolsStore.getState().scale / currentScaling const newScale = useTopToolsStore.getState().scale / currentScaling
myCircle.scale(newScale / currentScaling) myCircle.scale(newScale / currentScaling)
myCircle.data = Object.assign({ type: "circle", ...option }, data) myCircle.data = Object.assign({ type: "circle", ...option }, data)
syncPaperCircleDiameter(myCircle)
myCircle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => { myCircle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event && e.event.button === 2) { if (e.event && e.event.button === 2) {
if ( if (
@@ -5844,6 +5895,7 @@ export const usePaperStore = create<PaperState>((set) => ({
id: number, id: number,
color: any color: any
) => { ) => {
const nodeDiameter = getPaperNodeDiameter()
let circle: paper.Path.Circle = new paper.Path.Circle( let circle: paper.Path.Circle = new paper.Path.Circle(
Object.assign( Object.assign(
{ parent: usePaperStore.getState().group!, center: point }, { parent: usePaperStore.getState().group!, center: point },
@@ -5851,12 +5903,13 @@ export const usePaperStore = create<PaperState>((set) => ({
fillColor: color, fillColor: color,
// center: point, // center: point,
strokeColor: usePaperStore.getState().toolOption?.strokeColor, strokeColor: usePaperStore.getState().toolOption?.strokeColor,
radius: 3, radius: getPaperNodeRadius(),
strokeScaling: false, strokeScaling: false,
}, },
{ {
data: { data: {
type: "circle", type: "circle",
diameter: nodeDiameter,
fillColor: color, fillColor: color,
strokeColor: usePaperStore.getState().toolOption?.strokeColor, strokeColor: usePaperStore.getState().toolOption?.strokeColor,
imageId: useBottomToolsStore.getState().activeImage, imageId: useBottomToolsStore.getState().activeImage,
@@ -5868,9 +5921,7 @@ export const usePaperStore = create<PaperState>((set) => ({
} }
) )
) )
const currentScaling = usePaperStore.getState().group!.scaling.x circle.scale(getPaperNodeScaleFactor())
const newScale = useTopToolsStore.getState().scale / currentScaling
circle.scale(newScale / currentScaling)
circle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => { circle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event && e.event.button === 2) { if (e.event && e.event.button === 2) {
if ( if (
@@ -6051,8 +6102,7 @@ export const usePaperStore = create<PaperState>((set) => ({
id: number, id: number,
color: any color: any
) => { ) => {
const currentScaling = usePaperStore.getState().group!.scaling.x const nodeDiameter = getPaperNodeDiameter()
const newScale = useTopToolsStore.getState().scale / currentScaling
let circle: paper.Path.Circle = new paper.Path.Circle( let circle: paper.Path.Circle = new paper.Path.Circle(
Object.assign( Object.assign(
{ parent: usePaperStore.getState().group!, center: point }, { parent: usePaperStore.getState().group!, center: point },
@@ -6060,15 +6110,16 @@ export const usePaperStore = create<PaperState>((set) => ({
fillColor: color, fillColor: color,
// center: point, // center: point,
strokeColor: "#FFF", strokeColor: "#FFF",
radius: 3, strokeWidth: 2,
radius: getPaperNodeRadius(),
strokeScaling: false, strokeScaling: false,
}, },
{ {
data: { index, type: "pathCircle", id }, data: { index, type: "pathCircle", id, diameter: nodeDiameter },
} }
) )
) )
circle.scale(newScale / currentScaling) circle.scale(getPaperNodeScaleFactor())
circle.onMouseEnter = (_e: any) => { circle.onMouseEnter = (_e: any) => {
hoveredPathCircleInfo = { hoveredPathCircleInfo = {
id, id,

View File

@@ -6,6 +6,17 @@ import { useObjectStore } from "./store"
import { useBottomToolsStore } from "./useBottomToolsStore" import { useBottomToolsStore } from "./useBottomToolsStore"
import { safeClone } from "./utils/clone" import { safeClone } from "./utils/clone"
export const NODE_SIZE_MIN = 6
export const NODE_SIZE_MAX = 24
export const DEFAULT_NODE_SIZE = 10
const clampNodeSize = (value: number) => {
const nextValue = Number.isFinite(value)
? Math.round(value)
: DEFAULT_NODE_SIZE
return Math.min(NODE_SIZE_MAX, Math.max(NODE_SIZE_MIN, nextValue))
}
interface TopToolsState { interface TopToolsState {
// 标注页面是否仅查看 // 标注页面是否仅查看
isView: boolean isView: boolean
@@ -34,6 +45,8 @@ interface TopToolsState {
setAssistToolEnabled: (val: boolean) => void setAssistToolEnabled: (val: boolean) => void
assistToolSize: number assistToolSize: number
setAssistToolSize: (val: number) => void setAssistToolSize: (val: number) => void
nodeSize: number
setNodeSize: (val: number) => void
showTags: boolean showTags: boolean
setShowTags: (val: boolean) => void setShowTags: (val: boolean) => void
showTagsConfigs: string[] showTagsConfigs: string[]
@@ -94,6 +107,7 @@ const initialTopToolsState = {
crosshairStatus: "hidden" as "hidden" | "line" | "carve", crosshairStatus: "hidden" as "hidden" | "line" | "carve",
assistToolEnabled: false, assistToolEnabled: false,
assistToolSize: 10, assistToolSize: 10,
nodeSize: DEFAULT_NODE_SIZE,
drawOption: "default" as "default" | "intersect" | "unite", drawOption: "default" as "default" | "intersect" | "unite",
saveCurrentScale: false, saveCurrentScale: false,
scale: 1, scale: 1,
@@ -183,6 +197,12 @@ export const useTopToolsStore = create<TopToolsState>()(
...state, ...state,
assistToolSize: Math.max(1, val), assistToolSize: Math.max(1, val),
})), })),
nodeSize: initialTopToolsState.nodeSize,
setNodeSize: (val) =>
set((state: TopToolsState) => ({
...state,
nodeSize: clampNodeSize(val),
})),
showTags: false, showTags: false,
setShowTags: (val) => setShowTags: (val) =>
set((state: TopToolsState) => ({ set((state: TopToolsState) => ({