Files
labelmain/components/label/usePaperStore.ts
2026-02-28 17:59:19 +08:00

4538 lines
149 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Comment } from "./api/label/typing"
import { Project } from "./api/project/typing"
import dayjs from "dayjs"
import paper from "paper"
import { DispatchWithoutAction } from "react"
import { create } from "zustand"
import { usePermissionStore } from "./store/auth"
import {
initialDetail,
useKeyEventStore,
useLabelStore,
useObjectStore,
} from "./store"
import { useBottomToolsStore } from "./useBottomToolsStore"
import { useOtherToolsStore } from "./useOtherToolsStore"
import { usePaperSupportStore } from "./usePaperSupportStore"
import { useRightToolsStore } from "./useRightToolsStore"
import { useTopToolsStore } from "./useTopToolsStore"
import { safeClone } from "./utils/clone"
interface PaperState {
paperScope: paper.PaperScope | null
group: paper.Group | null
rasterPath: paper.Path | null
el: HTMLCanvasElement | null
viewTool: paper.Tool | null
panTool: paper.Tool | null
toolOption: any
polygonTool: paper.Tool | null
rectangleTool: paper.Tool | null
brushTool: paper.Tool | null
pointTool: paper.Tool | null
supportTool: paper.Tool | null
mode: "pan" | "polygon" | "rectangle" | "brush" | "point" | "support" | null
rasterScale: { [x: string]: number }
rasterSize: { [x: string]: [number, number] }
reciprocalRasterScale: { [x: string]: number }
loadingData: boolean
init: (
initEl: HTMLCanvasElement,
initScope: paper.PaperScope,
initGroup: paper.Group
) => void
setRasterPath: (paperRaster: paper.Path) => void
setRasterScale: (id: string, scale: number) => void
setRasterSize: (id: string, size: [number, number]) => void
resetRasterScale: () => void
initViewTool: (paperPanTool: paper.Tool) => void
initPanTool: (
paperPanTool: paper.Tool,
renderRectangle: (
paperGroup: paper.Group,
option: any
) => paper.Path.Rectangle,
forceUpdate: DispatchWithoutAction
) => void
setToolOption: (toolOption: any) => void
addPolygon: (
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
points: paper.Point[] | undefined,
option: any,
data: any
) => paper.Path
addCompoundPath: (
renderCompoundPath: (
paperGroup: paper.Group,
option: any
) => paper.CompoundPath,
children: paper.Path[] | undefined,
option: any,
data: any
) => paper.CompoundPath
initPolygonTool: (
paperPolygonTool: paper.Tool,
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
renderCompoundPath: (
paperGroup: paper.Group,
option: any
) => paper.CompoundPath,
forceUpdate: DispatchWithoutAction
) => void
addRectangle: (
renderRectangle: (
paperGroup: paper.Group,
option: any
) => paper.Path.Rectangle,
option: any,
data: any
) => paper.Path.Rectangle
initRectangleTool: (
paperRectangleTool: paper.Tool,
renderRectangle: (
paperGroup: paper.Group,
option: any
) => paper.Path.Rectangle,
forceUpdate: DispatchWithoutAction
) => void
addBrush: (
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
points: paper.Point[] | undefined,
option: any,
data: any
) => paper.Path
initBrushTool: (
paperBrushTool: paper.Tool,
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
renderCompoundPath: (
paperGroup: paper.Group,
option: any
) => paper.CompoundPath,
forceUpdate: DispatchWithoutAction
) => void
addPoint: (
renderCircle: (paperGroup: paper.Group, option: any) => paper.Path.Circle,
points: paper.Point | undefined,
option: any,
data: any
) => paper.Path.Circle
addPointLine: (
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
points: paper.Point[] | undefined,
option: any,
data: any
) => paper.Path
initPointTool: (
paperPointTool: paper.Tool,
renderCircle: (paperGroup: paper.Group, option: any) => paper.Path.Circle,
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
forceUpdate: DispatchWithoutAction
) => void
getPointCircle: (
point: paper.Point,
index: number,
id: number,
color: any
) => paper.Path.Circle
initSupportTool: (
tool: paper.Tool,
renderPath: (
points: Array<[number, number]>,
tags: number[],
flag?: boolean
) => void
) => void
getClickedCircle: (
point: paper.Point,
index: number,
id: number,
color: any
) => paper.Path.Circle
getBuffPath: (
item: paper.Segment,
index: number,
id: number,
color: any
) => paper.Path | null
setPaperMode: (
modeParam:
| "pan"
| "polygon"
| "rectangle"
| "brush"
| "point"
| "support"
| null
) => void
setGroupScale: (scale: number) => void
getAll: () => paper.Path[]
getItemById: (id: number) => paper.Path | null
getItemsById: (id: number) => Array<paper.Path | paper.CompoundPath>
setLoadingData: (flag: boolean) => void
}
interface PositionStore {
currentMousePosition: [number, number]
setCurrentMousePosition: (p: [number, number]) => void
}
const initialPaperState = {
paperScope: null,
group: null,
rasterPath: null,
el: null,
mode: null,
viewTool: null,
panTool: null,
toolOption: {
strokeColor: "red",
strokeWidth: 2,
fillColor: "#ff000033",
blankColor: "#ff000033",
},
polygonTool: null,
rectangleTool: null,
brushTool: null,
pointTool: null,
rasterScale: {},
rasterSize: {},
reciprocalRasterScale: {},
}
const handleDelete = () => {
const group = usePaperStore.getState().group!
const imageId = useBottomToolsStore.getState().activeImage
let ids = useObjectStore.getState().selectedPath[imageId]
let operationIds: any[] = []
let parentGroupIds: any[] = []
ids.forEach((id, index) => {
const items = group.getItems({ data: { id } })
items.forEach((item: any) => {
item.remove()
if (item.data.operationId && !operationIds[index])
operationIds[index] = item.data.operationId
if (item.data.parentGroupId && !parentGroupIds[index])
parentGroupIds[index] = item.data.parentGroupId
})
useObjectStore
.getState()
.updateSelectedPath(
useBottomToolsStore.getState().activeImage,
"DELETE",
id
)
})
// handle group path update
if (parentGroupIds.length) {
console.log(parentGroupIds)
const groupMap = useRightToolsStore.getState().pathGroupMap.get(imageId)
parentGroupIds.forEach((parentGroupId, index) => {
if (parentGroupId) {
const currentGroupIds = groupMap
?.get(parentGroupId)
?.filter((v) => v !== ids[index])
if (!currentGroupIds || currentGroupIds?.length <= 1) {
groupMap?.delete(parentGroupId)
} else {
groupMap?.set(parentGroupId, currentGroupIds ?? [])
}
}
})
}
const currentLabelData = safeClone(useLabelStore.getState().label)
operationIds.forEach((operationId, index) => {
let result =
currentLabelData
.get(imageId)
?.get(operationId)
?.filter((item) => item[0] !== ids[index]) ?? []
currentLabelData.get(imageId)?.set(operationId, result)
})
useLabelStore.getState().setLabel(currentLabelData)
useLabelStore.getState().pushStateStack(currentLabelData)
// ids.forEach((id) => {
// usePaperStore
// .getState()
// .group?.getItems({
// data: { id: id },
// })
// .forEach((item: any) => {
// item.remove();
// if (item.data.id) {
// if (item.data.imageId && item.data.operationId) {
// useLabelStore
// .getState()
// .deleteOperation(
// item.data.imageId,
// item.data.operationId,
// item.data.id
// );
// }
// // tocheck
// useObjectStore
// .getState()
// .updateSelectedPath(
// useBottomToolsStore.getState().activeImage,
// item.data.id
// );
// }
// });
// });
}
const handleShift = (flag: boolean) => {
useKeyEventStore.getState().setShift(flag)
}
// 通用 鼠标在paperScope上移动时更新当前鼠标位置
const handleMouseMove = (e: paper.MouseEvent) => {
usePositionStore.getState().setCurrentMousePosition([e.point.x, e.point.y])
}
const { user_id } = usePermissionStore.getState()
type OrderPoint = [number, number]
const getOrder = (b: OrderPoint[]): number[] => {
// 左下角x最小y最大
// 左上角x最小y最小
// 右上角x最大y最小
// 右下角x最大y最大
const bottomLeft = b.reduce(
(acc, point) =>
point[0] < acc[0] || (point[0] === acc[0] && point[1] > acc[1])
? point
: acc,
b[0]
)
const topLeft = b.reduce(
(acc, point) =>
point[0] < acc[0] || (point[0] === acc[0] && point[1] < acc[1])
? point
: acc,
b[0]
)
const topRight = b.reduce(
(acc, point) =>
point[0] > acc[0] || (point[0] === acc[0] && point[1] < acc[1])
? point
: acc,
b[0]
)
const bottomRight = b.reduce(
(acc, point) =>
point[0] > acc[0] || (point[0] === acc[0] && point[1] > acc[1])
? point
: acc,
b[0]
)
return [
b.indexOf(bottomLeft),
b.indexOf(topLeft),
b.indexOf(topRight),
b.indexOf(bottomRight),
]
}
const handleRectangleIntersectRaster = (rect: paper.Path) => {
let intersections = rect.getIntersections(
usePaperStore.getState().rasterPath!
)
let validIntersections = intersections.filter((intersection) => {
// 检查交点是否在两个路径的可见部分
return (
rect?.contains(intersection.point) &&
usePaperStore.getState().rasterPath!.contains(intersection.point)
)
})
// 没有 validIntersections 不进行操作
if (validIntersections.length) {
let intersectionPath: paper.Path | null = rect.intersect(
usePaperStore.getState().rasterPath!
) as paper.Path
let rectOrder = getOrder(
rect.segments.map((segment) => [
+segment.point.x.toFixed(2),
+segment.point.y.toFixed(2),
])
)
let intersectionOrder = getOrder(
intersectionPath.segments.map((segment) => [
+segment.point.x.toFixed(2),
+segment.point.y.toFixed(2),
])
)
let order = rectOrder.map((rorder) =>
intersectionOrder.findIndex((iorder) => iorder === rorder)
)
if (intersectionPath) {
rect.segments = order.map((child) => intersectionPath!.segments[child])
}
// rect.segments = [
// intersectionPath.segments[1],
// intersectionPath.segments[2],
// intersectionPath.segments[3],
// intersectionPath.segments[0],
// ];
// rect.segments = intersectionPath.segments;
intersectionPath.remove()
intersectionPath = null
} else {
let intersectionPath: paper.Path | null = rect.intersect(
usePaperStore.getState().rasterPath!
) as paper.Path
rect.segments = intersectionPath.segments
intersectionPath.remove()
intersectionPath = null
}
}
export const updateObjLatestComment = (
imageId: any,
operationId: any,
id: any
) => {
// 标注中的任务不对批注状态进行更新
if (useTopToolsStore.getState().currentTimeKey === "label") return
const currentDetail = useLabelStore
.getState()
.getLabelDetailDataByKeys(imageId, operationId, id)
const comment = currentDetail?.comment || []
const updatedComment = comment.map((item: Comment, index: number) =>
index < comment.length - 1
? item
: { ...item, comment_type: item.comment_type || 1 }
)
useLabelStore
.getState()
.updateLabelDetailDataByKeys(imageId, operationId, id, {
...currentDetail,
comment: updatedComment,
})
}
// 清空辅助标注形成的path
export const clearSupportAnnotationItem = () => {
const needClearItems = usePaperStore
.getState()
.group!.getItems({ data: { id: "support" } })
needClearItems.forEach((item: any) => item.remove())
}
// 根据点击位置判断弹窗弹出的top、left
const containerWidth = 244
const containerHeight = 240
export const getShowContextMenuPosition = (
clickPoint: paper.Point,
category: Project.LabelSchemaList,
offsetWidth = 0,
offsetHeight = 0
) => {
let subAttrHeight = 0
if (category) {
const { sub_attributes_describe } = category
console.log(sub_attributes_describe)
if (sub_attributes_describe.length) subAttrHeight += 30
sub_attributes_describe.forEach((item) => {
if (item.chinese_name) subAttrHeight += 32
if (item.optional_item && item.optional_item.length)
item.optional_item.forEach(() => {
subAttrHeight += 22
})
if (item.select_mode === 0) subAttrHeight += 32
})
}
const finalHeight = containerHeight + subAttrHeight
const element = document.getElementById("paper-container")
const rasterPos = [element!.clientWidth, element!.clientHeight]
console.log(rasterPos, finalHeight)
let left =
clickPoint.x + containerWidth > rasterPos[0]
? clickPoint.x + offsetWidth - containerWidth
: clickPoint.x + offsetWidth
let top =
clickPoint.y + finalHeight > rasterPos[1]
? clickPoint.y + offsetHeight - finalHeight < 0
? 0
: clickPoint.y + offsetHeight - finalHeight
: clickPoint.y + offsetHeight
console.log("转换之后的x、y", left, top)
return {
top,
left,
}
}
export const usePositionStore = create<PositionStore>((set) => ({
currentMousePosition: [0, 0],
setCurrentMousePosition: (point) =>
set((state) => ({ ...state, currentMousePosition: point })),
}))
// store paper
export const usePaperStore = create<PaperState>((set) => ({
paperScope: initialPaperState.paperScope,
group: initialPaperState.group,
rasterPath: initialPaperState.rasterPath,
el: initialPaperState.el,
mode: initialPaperState.mode,
viewTool: initialPaperState.viewTool,
panTool: initialPaperState.panTool,
toolOption: initialPaperState.toolOption,
polygonTool: initialPaperState.polygonTool,
rectangleTool: initialPaperState.rectangleTool,
brushTool: initialPaperState.brushTool,
pointTool: initialPaperState.pointTool,
supportTool: null,
rasterScale: initialPaperState.rasterScale,
rasterSize: initialPaperState.rasterSize,
reciprocalRasterScale: initialPaperState.reciprocalRasterScale,
currentMousePosition: [0, 0],
loadingData: false,
init: (
initEl: HTMLCanvasElement,
initScope: paper.PaperScope,
initGroup: paper.Group
) => {
set((state: PaperState) => ({
...state,
paperScope: initScope,
group: initGroup,
el: initEl,
}))
},
setRasterPath: (rasterPath) => {
set((state: PaperState) => ({
...state,
rasterPath: rasterPath,
}))
},
setRasterScale: (id: string, scale: number) => {
set((state: PaperState) => ({
...state,
rasterScale: { ...state.rasterScale, [id]: scale },
reciprocalRasterScale: {
...state.reciprocalRasterScale,
[id]: 1 / scale,
},
}))
},
setRasterSize: (id: string, size: [number, number]) => {
set((state: PaperState) => ({
...state,
rasterSize: { ...state.rasterSize, [id]: size },
}))
},
resetRasterScale: () =>
set((state: PaperState) => ({
...state,
rasterScale: {},
reciprocalRasterScale: {},
})),
setLoadingData: (flag) =>
set((state: PaperState) => ({
...state,
loadingData: flag,
})),
setCurrentMousePosition: (point: [number, number]) =>
set((state: PaperState) => ({
...state,
currentMousePosition: point,
})),
setToolOption: (toolOption: any) => {
set((state: PaperState) => ({
...state,
toolOption: toolOption,
}))
},
initViewTool: (paperViewTool: paper.Tool) => {
let viewTool = paperViewTool
viewTool.onMouseMove = (e: paper.MouseEvent) => {
handleMouseMove(e)
}
set((state: PaperState) => ({
...state,
viewTool: viewTool,
}))
},
initPanTool: (
paperPanTool: paper.Tool,
renderRectangle: (
paperGroup: paper.Group,
option: any
) => paper.Path.Rectangle,
forceUpdate: DispatchWithoutAction
) => {
let panMode = ""
let activePath: paper.Path
// let compoundPolygon: paper.Path | paper.CompoundPath | null;
let hitIndex: number
let pressShift: boolean = false
let pressCtrl: boolean = false
let rect: paper.Path.Rectangle | null
let panTool = paperPanTool
let lastPoint: paper.Point
let isChanged: boolean = false
let lastTime: number
// const handleSelected = (path: paper.Path) => {
// // path.blendMode = "subtract";
// path.segments.forEach((segment) => {
// // console.log(segment.index);
// // let point = usePaperStore
// // .getState()
// // .group!.globalToLocal(segment.point);
// let circle = usePaperStore
// .getState()
// .getClickedCircle(
// segment.point,
// segment.index,
// path.data.id,
// path.data.blankColor
// );
// // circle.insertBelow(path);
// // path.addChild(circle);
// selectedCircles.push(circle);
// // usePaperStore.getState().group?.addChild(circle);
// // circle.bringToFront();
// // circle.addTo(path);
// });
// };
const handleCtrl = (rect: paper.Path.Rectangle) => {
let items = usePaperStore
.getState()
.group!.getItems({})
.filter(
(item) =>
item.data?.id &&
["rectangle", "polygon", "brush", "point"].includes(item.data.type)
)
// 清空选中
usePaperStore
.getState()
.group!.getItems({
data: { selected: true },
})
.forEach((item) => {
if (item.data.selected) {
item.data.selected = false
usePaperSupportStore.getState().clearMasks()
usePaperSupportStore.getState().clearTexts()
}
// item.data.selected = false;
})
handleCancelSelect()
for (let i = 0; i < items.length; i++) {
let child = items[i]
if (child instanceof paper.Path) {
if (
rect.intersects(child.bounds as any) ||
rect.contains(child.bounds)
) {
if (rect.data.id !== child.data.id) {
if (child.data.type !== "brush" && child.data.type !== "point") {
child.fillColor = child.data.fillColor
}
if (child.data.type === "rectangle") {
if (child instanceof paper.Path) child.data.selected = true
} else if (child.data.type === "brush") {
child.data.selected = true
usePaperSupportStore.getState().handleMask(child)
} else if (child.data.type === "point") {
child.data.selected = true
usePaperSupportStore.getState().handleMask(child)
usePaperSupportStore.getState().handleText(child as paper.Path)
}
useObjectStore
.getState()
.updateSelectedPath(
useBottomToolsStore.getState().activeImage,
"ADDSOME",
child.data?.id
)
}
}
} else if (child instanceof paper.CompoundPath) {
let bool = child.children.some((item) => {
return (
rect.intersects(item.bounds as any) || rect.contains(item.bounds)
)
})
if (bool) {
if (rect.data.id !== child.data.id) {
if (child.data.type !== "brush" && child.data.type !== "point") {
child.fillColor = child.data.fillColor
}
if (child.data.type === "rectangle") {
if (child instanceof paper.Path) child.data.selected = true
} else if (child.data.type === "brush") {
child.data.selected = true
usePaperSupportStore.getState().handleMask(child)
} else if (child.data.type === "point") {
child.data.selected = true
usePaperSupportStore.getState().handleMask(child)
usePaperSupportStore.getState().handleText(child as paper.Path)
}
useObjectStore
.getState()
.updateSelectedPath(
useBottomToolsStore.getState().activeImage,
"ADDSOME",
child.data?.id
)
}
}
}
}
}
const handleCancelSelect = () => {
usePaperStore
.getState()
.group!.getItems({})
.forEach((item) => {
// clear color
if (
!(
item.data.type === "mask" ||
item.data.type === "text" ||
item.data.type === "tag"
)
)
item.fillColor = item?.data?.blankColor
})
// 如果没有按下shift 当前图片SelectedPath被清空 下面从空数组开始push
useObjectStore
.getState()
.updateSelectedPath(useBottomToolsStore.getState().activeImage, "ADD")
}
panTool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (pressCtrl) return
const currentTime = Date.now()
const timeDelta = currentTime - lastTime
if (e.event && e.event.button === 0) {
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: false,
position: {
top: 0,
left: 0,
},
imageId: useOtherToolsStore.getState().imageId,
operationId: useOtherToolsStore.getState().operationId,
id: useOtherToolsStore.getState().id,
})
} else if (e.event && e.event.button === 1) {
// 鼠标中键操作图片移动
panMode = "item"
return
}
if (useTopToolsStore.getState().isView) {
panMode = ""
return
}
let point = usePaperStore.getState().group!.globalToLocal(e.point)
let hitResult = usePaperStore.getState().group?.hitTest(e.point, {
segments: true,
stroke: true,
curves: true,
fill: true,
guide: false,
tolerance: usePaperStore.getState().paperScope
? 8 / useTopToolsStore.getState().scale
: 0,
})
console.log("hitResult", hitResult)
let lastTagDataId = -1
if (activePath?.data.id) {
lastTagDataId = activePath.data.id
}
// 处理相同id标注对象的函数 生成对应activePath 更新rect和polygon的fillColor
const handleSameIdItems = (id: number) => {
const sameIdItems = usePaperStore
.getState()
.getItemsById(id)
.filter((item) => {
return item.data.type !== "tag"
})
const types = [
...new Set(sameIdItems.map((item) => item.data.type || "")),
]
if (types.includes("rectangle")) {
activePath = sameIdItems.filter((item) => {
if (item.data.type === "rectangle") {
item.fillColor = item.data.fillColor
return true
} else return false
})[0] as paper.Path
} else if (types.includes("polygon")) {
const items = sameIdItems.filter((item) => {
if (item.data.type === "polygon") {
if (!item.data.isHollowPolygon)
item.fillColor = item.data.fillColor
return true
} else return false
})
console.log(items)
} else if (types.includes("brush")) {
const items = sameIdItems.filter((item) => item.data.type === "brush")
if (items.length === 1) {
// 此时 items[0] 类型只可能为path
activePath = items[0] as paper.Path
} else if (items.length > 1) {
let parent: any = items.filter(
(item) => item instanceof paper.CompoundPath
)[0]
const children = parent.children
if (children.length === 1) {
activePath = children[0]
} else if (children.length > 1) {
let activeItem: any
let min = 1000000
children.forEach((child: any) => {
let nearestPoint = child.getNearestPoint(point)
let distance = point.getDistance(nearestPoint)
if (distance < min) {
min = distance
activeItem = child
}
})
activePath = activeItem
}
// 根据点击位置更新实际点击类型
let currentHitRes = activePath.hitTest(point, {
segments: true,
stroke: true,
curves: true,
fill: true,
guide: false,
tolerance: usePaperStore.getState().paperScope
? 8 / useTopToolsStore.getState().scale
: 0,
})
if (currentHitRes) {
if (currentHitRes.type === "segment") {
hitIndex = currentHitRes.segment.index
panMode = "segment"
} else if (currentHitRes.type === "stroke") {
hitIndex = currentHitRes.location.index
panMode = "stroke"
}
}
}
} else if (types.includes("point")) {
const items = sameIdItems.filter((item) => item.data.type === "point")
if (items.length === 1) {
// 点类型没有单对象多区域的情况
activePath = items[0] as paper.Path
}
}
console.log("处理相同id对象后", activePath)
if (activePath instanceof paper.CompoundPath) {
let selected = (activePath.children as paper.Path[]).find(
(child) => child.data.selected
)
selected && usePaperSupportStore.getState().handleCircles(selected)
}
}
// 当前点击结果为遮罩 处理线段和点的相关情况
if (hitResult && hitResult.item.data.type === "mask") {
panMode = "fill"
if (hitResult.item.data.id) {
handleSameIdItems(hitResult.item.data.id)
}
return
}
if (pressShift) {
} else {
// not press shift
let selectedItems = usePaperStore.getState().group!.getItems({
data: { selected: true },
})
if (selectedItems && selectedItems.length) {
selectedItems.forEach((item) => {
if (item.data.selected) {
let itemHitResult = item.hitTest(point, {
segments: true,
stroke: true,
curves: true,
fill: true,
guide: false,
tolerance: usePaperStore.getState().paperScope
? 8 / usePaperStore.getState().paperScope!.view.zoom
: 0,
})
if (!itemHitResult) {
item.data.selected = false
usePaperSupportStore.getState().clearAll()
handleCancelSelect()
}
}
// item.data.selected = false;
})
} else {
handleCancelSelect()
}
}
if (hitResult && hitResult.item.className !== "Raster") {
if (hitResult.item.data?.id) {
// 处理多边形和矩形填充色
usePaperStore
.getState()
.group!.getItems({
data: { id: hitResult.item.data?.id },
})
.forEach((item) => {
// 点击图形本身 fill color
if (
hitResult.item.data.type !== "brush" &&
hitResult.item.data.type !== "point" &&
hitResult.item.data.type !== "pathCircle" &&
hitResult.item.data.type !== "pathBuff" &&
hitResult.item.data.type !== "text" &&
item.data.type !== "tag"
) {
item.fillColor = hitResult.item.data.fillColor
}
})
if (hitResult.item.data.type === "polygon") {
if (hitResult.item instanceof paper.CompoundPath) {
;(hitResult.item.children as paper.Path[]).forEach((item) => {
const childHitResult = item.hitTest(point, {
segments: true,
stroke: true,
curves: true,
fill: true,
guide: false,
tolerance: 0,
})
if (childHitResult) {
if (childHitResult.item.data.isHollowPolygon) return
// item.fillColor = new paper.Color("transparent");
// item.strokeColor = item.strokeColor || new paper.Color("red");
// item.strokeWidth = item.strokeWidth || 1;
if (
lastPoint &&
lastPoint.x === point.x &&
lastPoint.y === point.y &&
timeDelta < 200
) {
console.log("点击位置 多边形子路径", childHitResult)
if (childHitResult.item instanceof paper.Path) {
// if (!childHitResult.item.data.selected) {
// childHitResult.item.data.selected = true;
// }
let selectedItems = usePaperStore
.getState()
.group!.getItems({
data: { selected: true },
})
selectedItems.forEach((item) => {
item.data.selected = false
})
childHitResult.item.data.selected = true
usePaperSupportStore
.getState()
.handleBufferPaths(childHitResult.item)
usePaperSupportStore
.getState()
.handleCircles(childHitResult.item)
}
}
}
})
} else if (hitResult.item instanceof paper.Path) {
// doubleclick;
if (
lastPoint &&
lastPoint.x === point.x &&
lastPoint.y === point.y &&
timeDelta < 200
) {
if (hitResult.item.data.isHollowPolygon) return
let selectedItems = usePaperStore.getState().group!.getItems({
data: { selected: true },
})
selectedItems.forEach((item) => {
item.data.selected = false
})
hitResult.item.data.selected = true
usePaperSupportStore
.getState()
.handleBufferPaths(hitResult.item)
usePaperSupportStore.getState().handleCircles(hitResult.item)
// if (!hitResult.item.data.selected) {
// hitResult.item.data.selected = true;
// usePaperSupportStore
// .getState()
// .handleBufferPaths(hitResult.item);
// usePaperSupportStore.getState().handleCircles(hitResult.item);
// }
}
}
activePath = hitResult.item as paper.Path
} else if (hitResult.item.data.type === "rectangle") {
if (hitResult.item instanceof paper.Path) {
if (!hitResult.item.data.selected) {
hitResult.item.data.selected = true
usePaperSupportStore
.getState()
.handleBufferPaths(hitResult.item)
usePaperSupportStore.getState().handleCircles(hitResult.item)
}
activePath = hitResult.item as paper.Path
}
} else if (hitResult.item.data.type === "brush") {
// hitResult.item.data.selected = true;
usePaperSupportStore.getState().handleMask(hitResult.item)
// 绘制选中的circle
if (hitResult.item instanceof paper.CompoundPath) {
;(hitResult.item.children as paper.Path[]).forEach((item) => {
const childHitResult = item.hitTest(point, {
segments: true,
stroke: true,
curves: true,
fill: true,
guide: false,
tolerance: usePaperStore.getState().paperScope
? 8 / usePaperStore.getState().paperScope!.view.zoom
: 0,
})
if (childHitResult) {
if (childHitResult.item instanceof paper.Path) {
if (!childHitResult.item.data.selected) {
childHitResult.item.data.selected = true
usePaperSupportStore
.getState()
.handleBufferPaths(childHitResult.item)
usePaperSupportStore
.getState()
.handleCircles(childHitResult.item)
}
activePath = childHitResult.item as paper.Path
}
}
})
} else if (hitResult.item instanceof paper.Path) {
if (!hitResult.item.data.selected) {
hitResult.item.data.selected = true
usePaperSupportStore
.getState()
.handleBufferPaths(hitResult.item)
usePaperSupportStore.getState().handleCircles(hitResult.item)
}
activePath = hitResult.item as paper.Path
}
} else if (hitResult.item.data.type === "point") {
// 只有点击圆才视为选中
if (hitResult.type === "segment") {
// Path 类型
if (hitResult.item instanceof paper.Path) {
if (!hitResult.item.data.selected) {
hitResult.item.data.selected = true
// 绘制遮罩
usePaperSupportStore.getState().handleMask(hitResult.item)
// 绘制点对应的数字
usePaperSupportStore
.getState()
.handleText(hitResult.item as paper.Path)
// 绘制选中边
usePaperSupportStore
.getState()
.handleBufferPaths(hitResult.item)
// 绘制选中圆
usePaperSupportStore.getState().handleCircles(hitResult.item)
}
activePath = hitResult.item as paper.Path
}
}
} else if (
hitResult.item.data.type === "pathBuff" ||
hitResult.item.data.type === "pathCircle"
) {
handleSameIdItems(hitResult.item.data.id)
}
// // 更新被选中的标注对象detail
// updateObjLatestComment(
// hitResult.item.data?.imageId,
// hitResult.item.data?.operationId,
// hitResult.item.data?.id
// );
useObjectStore
.getState()
.updateSelectedPath(
useBottomToolsStore.getState().activeImage,
"ADD",
hitResult.item.data?.id
)
// usePaperStore.getState().group?.addChild(activePath); // 放到顶层
// activePath.bringToFront();
}
} else {
// if Raster
if (e.event && e.event.button === 2) {
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: false,
position: {
top: 0,
left: 0,
},
imageId: useOtherToolsStore.getState().imageId,
operationId: useOtherToolsStore.getState().operationId,
id: useOtherToolsStore.getState().id,
})
}
}
// 禁止右键拖拽
if (e.event && e.event.button === 2) {
panMode = ""
if (
hitResult?.item?.data.id &&
["rectangle", "polygon", "brush", "point"].includes(
hitResult?.item?.data.type
)
) {
const data = hitResult?.item?.data
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === data.operationId
)
const { top, left } = getShowContextMenuPosition(
e.point,
category!,
5,
5
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: data.imageId,
operationId: data.operationId,
id: data.id,
})
}
} else if (hitResult && hitResult.type === "fill") {
if (
hitResult &&
hitResult.item?.data?.type === "pathCircle" &&
activePath?.data?.type === "rectangle"
) {
// 放大后 点击只考虑了rect 1204todo
hitIndex = hitResult.item.data.index
panMode = "segment"
} else {
panMode = "fill"
}
} else if (hitResult && hitResult.type === "stroke") {
// 镂空区域不可添加点
if (activePath && activePath.data.isHollowPolygon) return
panMode = "stroke"
// hitIndex = hitResult.location.index;
if (hitResult.item?.data?.type === "pathBuff") {
hitIndex = hitResult.item.data.index
} else {
hitIndex = hitResult.location.index
return
}
//点击多边形边,添加一个 端点
if (
activePath?.data?.type === "polygon" ||
activePath?.data?.type === "brush" ||
activePath?.data?.type === "point"
) {
// doubleclick
if (
lastPoint &&
lastPoint.x === point.x &&
lastPoint.y === point.y &&
timeDelta < 200
) {
if (activePath instanceof paper.CompoundPath) {
;(activePath.children as paper.Path[]).forEach((item) => {
const childHitResult = item.hitTest(point, {
segments: true,
stroke: true,
curves: true,
fill: true,
guide: false,
tolerance: usePaperStore.getState().paperScope
? 8 / usePaperStore.getState().paperScope!.view.zoom
: 0,
})
if (childHitResult) {
if (childHitResult.location.point) {
item.insert(hitIndex + 1, childHitResult.location.point)
usePaperSupportStore.getState().handleBufferPaths(item)
usePaperSupportStore.getState().handleCircles(item)
isChanged = true
panMode = "stroke"
hitIndex = hitIndex + 1
}
}
})
} else {
activePath.insert(hitIndex + 1, hitResult.location.point)
activePath?.data?.type === "point" &&
usePaperStore
.getState()
.getPointCircle(
hitResult.location.point,
hitIndex + 1,
activePath.data.id,
activePath.data.blankColor
)
activePath.bringToFront()
usePaperSupportStore.getState().handleBufferPaths(activePath)
usePaperSupportStore.getState().handleCircles(activePath)
isChanged = true
panMode = "stroke"
hitIndex = hitIndex + 1
}
if (activePath?.data?.type === "brush") {
usePaperSupportStore.getState().handleMask(hitResult.item)
}
if (activePath?.data?.type === "point") {
// 绘制点对应的数字
usePaperSupportStore.getState().handleText(activePath)
let circles = usePaperStore.getState().group!.getItems({
data: {
id: activePath.data.id,
type: "circle",
},
}) as paper.Path[]
activePath.segments.forEach((segment, index) => {
let findIndex = circles.findIndex((circle) => {
return circle.contains(segment.point)
})
circles[index].data = Object.assign({}, circles[index]?.data, {
text: findIndex + 1,
})
})
usePaperSupportStore.getState().handleBufferPaths(activePath)
usePaperSupportStore.getState().handleCircles(activePath)
}
}
}
} else if (hitResult && hitResult.type === "segment") {
// 镂空区域不可删除点
if (activePath && activePath.data.isHollowPolygon) return
panMode = "segment"
if (hitResult.item?.data?.type === "pathCircle") {
hitIndex = hitResult.item.data.index
} else {
hitIndex = hitResult.segment.index
}
// shift点击 删除端点
// if (e.event.shiftKey) {
// activePath.removeSegment(hitIndex);
// panMode = "";
// }
if (
activePath?.data?.type === "polygon" ||
activePath?.data?.type === "brush" ||
activePath?.data?.type === "point"
) {
if (
lastPoint &&
lastPoint.x === point.x &&
lastPoint.y === point.y &&
timeDelta < 200
) {
if (activePath instanceof paper.CompoundPath) {
;(activePath.children as paper.Path[]).forEach((item) => {
const childHitResult = item.hitTest(point, {
segments: true,
stroke: true,
curves: true,
fill: true,
guide: false,
tolerance: usePaperStore.getState().paperScope
? 8 / usePaperStore.getState().paperScope!.view.zoom
: 0,
})
if (childHitResult) {
let checkLength = 3
if (item?.data?.type === "brush") checkLength = 2
if (item?.data?.type === "point") checkLength = 1
if (item.segments && item.segments.length <= checkLength)
return
;(childHitResult.item as paper.Path).removeSegment(hitIndex)
usePaperSupportStore
.getState()
.handleBufferPaths(childHitResult.item as paper.Path)
usePaperSupportStore
.getState()
.handleCircles(childHitResult.item as paper.Path)
isChanged = true
panMode = "segment"
}
})
} else {
let checkLength = 3
if (activePath?.data?.type === "brush") checkLength = 2
if (activePath?.data?.type === "point") checkLength = 1
if (
activePath.segments &&
activePath.segments.length <= checkLength
)
return
activePath.removeSegment(hitIndex)
usePaperSupportStore.getState().handleBufferPaths(activePath)
usePaperSupportStore.getState().handleCircles(activePath)
isChanged = true
panMode = "segment"
}
if (activePath?.data?.type === "brush") {
usePaperSupportStore.getState().handleMask(hitResult.item)
}
if (activePath?.data?.type === "point") {
let paths = usePaperStore.getState().group!.getItems({
data: {
id: activePath.data.id,
// text: activePath.segments.length - hitIndex + 1,
type: "circle",
},
}) as paper.Path[]
// paths = paths.filter((item) => {
// if (item.data.text === hitIndex + 1) item.remove();
// return item.data.text !== hitIndex + 1;
// });
paths = paths.filter((circle) => {
let findIndex = activePath.segments.findIndex((segment) => {
return circle.contains(segment.point)
})
if (findIndex === -1) {
circle.remove()
return false
} else {
return true
}
})
// 1. 对数组按照 text 属性进行排序
let sortedArr = paths
.slice()
.sort((a, b) => a.data.text - b.data.text)
// 2. 重新赋值 text 属性为从 1 开始的连续自然数
sortedArr.forEach((item, index) => {
item.data.text = index + 1
})
// paths.forEach((child) => {
// if (
// child.data.text ===
// activePath.segments.length - hitIndex + 1
// ) {
// // 删除 circle
// child.remove();
// } else if (
// child.data.text >
// activePath.segments.length - hitIndex + 1
// ) {
// child.data.text = child.data.text - 1;
// }
// });
// usePaperSupportStore.getState().handleMask(hitResult.item);
// usePaperSupportStore
// .getState()
// .handleText(hitResult.item as paper.Path);
}
}
}
} else {
panMode = ""
}
// 当前选中路径 tags
const tags = usePaperStore.getState().group!.getItems({
data: {
id: activePath?.data?.id,
type: "tag",
},
})
// 上一选中路径 tags
const lastTags = usePaperStore.getState().group!.getItems({
data: {
id: lastTagDataId,
type: "tag",
},
})
// 标识位为true时选中路径隐藏tags; 没选中显示tags
const configs = useTopToolsStore.getState().showTagsConfigs
if (useTopToolsStore.getState().showTags)
if (panMode) {
lastTags.forEach((tag) => {
if (tag.data.tag_category === "mask")
tag.visible = configs.includes("外框")
else if (tag.data.tag_category === "point_text")
tag.visible = configs.includes("点ID")
else tag.visible = true
})
tags.forEach((tag) => {
tag.visible = false
})
} else {
tags.forEach((tag) => {
if (tag.data.tag_category === "mask")
tag.visible = configs.includes("外框")
else if (tag.data.tag_category === "point_text")
tag.visible = configs.includes("点ID")
else tag.visible = true
})
}
console.log("鼠标按下 选中路径", activePath, "当前模式", panMode)
lastPoint = point
lastTime = Date.now()
}
panTool.onMouseDrag = (e: {
event: MouseEvent
point: paper.Point
downPoint: paper.Point
delta: paper.Point
}) => {
// 优先判断移动图片
if (panMode === "item") {
usePaperStore.getState().group!.position = usePaperStore
.getState()
.group?.position.add(e.delta)!
}
if (useTopToolsStore.getState().isView) return
if (pressCtrl) {
const delta = e.point.subtract(e.downPoint)
if (delta.length > 10 && e.event.button === 0) {
let temp = renderRectangle(usePaperStore.getState().group!, {})
let point = temp.globalToLocal(e.point)
let downPoint = temp.globalToLocal(e.downPoint)
temp.remove()
rect = usePaperStore.getState().addRectangle(
renderRectangle,
{
from: downPoint,
to: point,
// option
strokeColor: "#FFF",
strokeWidth: 2,
fillColor: undefined,
},
{
imageId: useBottomToolsStore.getState().activeImage,
operationId: useTopToolsStore.getState().activeOperation,
id:
useRightToolsStore
.getState()
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
}
)
rect.removeOnDrag()
}
} else {
let point: paper.Point | undefined = undefined
if (activePath) {
point = usePaperStore.getState().group!.globalToLocal(e.point)
}
let movePath = (path: paper.Path) => {
const currentGroupScaling = usePaperStore.getState().group!.scaling
const delta = {
x: e.delta.x / currentGroupScaling.x,
y: e.delta.y / currentGroupScaling.y,
}
if (panMode === "fill") {
if (path.data.type === "brush") {
path.position = path.position.add(delta)
usePaperSupportStore
.getState()
.selectedCircles.forEach((item) => {
item.position = item.position.add(delta)
})
usePaperSupportStore.getState().handleMask(path)
} else if (path.data.type === "point") {
path.position = path.position.add(delta)
usePaperSupportStore.getState().handleMask(path)
usePaperSupportStore.getState().handleText(path)
let circles = usePaperStore.getState().group!.getItems({
data: {
id: path.data.id,
type: "circle",
},
}) as paper.Path[]
if (circles && circles.length) {
circles.forEach((circle) => {
circle.position = circle.position.add(delta)
})
}
usePaperSupportStore
.getState()
.selectedCircles.forEach((circle) => {
circle.position = circle.position.add(delta)
})
} else {
path.position = path.position.add(delta)
// 镂空区域移动不影响选中点移动
if (!path.data.isHollowPolygon) {
usePaperSupportStore
.getState()
.selectedCircles.forEach((item) => {
item.position = item.position.add(delta)
})
}
usePaperSupportStore.getState().handleBufferPaths(null)
}
} else if (panMode === "stroke") {
if (path.data.type === "rectangle") {
usePaperSupportStore.getState().handleBufferPaths(null)
let axis: "x" | "y" = hitIndex % 2 ? "y" : "x"
path.segments[hitIndex].point[axis] = point![axis]
path.segments[hitIndex].next.point[axis] = point![axis]
usePaperSupportStore.getState().selectedCircles[
hitIndex
]!.position[axis] = point![axis]
let next =
hitIndex + 1 ===
usePaperSupportStore.getState().selectedCircles.length
? 0
: hitIndex + 1
usePaperSupportStore.getState().selectedCircles[next].position[
axis
] = point![axis]
} else if (path.data.type === "polygon") {
} else if (path.data.type === "brush") {
// path.position = path.position.add(e.delta);
// handleMask(path);
}
} else if (panMode === "segment") {
if (path.data.type === "rectangle") {
usePaperSupportStore.getState().handleBufferPaths(null)
path.segments[hitIndex].point.x = point!.x
path.segments[hitIndex].point.y = point!.y
path.segments[hitIndex].next.point[hitIndex % 2 ? "y" : "x"] =
point![hitIndex % 2 ? "y" : "x"]
path.segments[hitIndex].previous.point[hitIndex % 2 ? "x" : "y"] =
point![hitIndex % 2 ? "x" : "y"]
usePaperSupportStore.getState().selectedCircles[
hitIndex
]!.position.x = point!.x
usePaperSupportStore.getState().selectedCircles[
hitIndex
]!.position.y = point!.y
let previous =
hitIndex + 1 ===
usePaperSupportStore.getState().selectedCircles.length
? 0
: hitIndex + 1
let next =
hitIndex - 1 < 0
? usePaperSupportStore.getState().selectedCircles.length - 1
: hitIndex - 1
usePaperSupportStore.getState().selectedCircles[
previous
].position[hitIndex % 2 ? "y" : "x"] =
point![hitIndex % 2 ? "y" : "x"]
usePaperSupportStore.getState().selectedCircles[next].position[
hitIndex % 2 ? "x" : "y"
] = point![hitIndex % 2 ? "x" : "y"]
} else if (path.data.type === "polygon") {
if (path.data.isHollowPolygon) return
usePaperSupportStore.getState().clearBufferPaths()
path.segments[hitIndex].point.x = point!.x
path.segments[hitIndex].point.y = point!.y
usePaperSupportStore.getState().selectedCircles[
hitIndex
]!.position.x = point!.x
usePaperSupportStore.getState().selectedCircles[
hitIndex
]!.position.y = point!.y
} else if (path.data.type === "brush") {
path.segments[hitIndex].point.x = point!.x
path.segments[hitIndex].point.y = point!.y
usePaperSupportStore.getState().selectedCircles[
hitIndex
]!.position.x = point!.x
usePaperSupportStore.getState().selectedCircles[
hitIndex
]!.position.y = point!.y
usePaperSupportStore.getState().handleMask(path)
} else if (path.data.type === "point") {
let circles = usePaperStore.getState().group!.getItems({
data: {
id: path.data.id,
type: "circle",
},
}) as paper.Path[]
// let index = path.segments.length - hitIndex - 1
// let index = hitIndex;
// 移动点
if (circles && circles.length) {
// let circle = circles.find(
// (item) => item.data.text === index + 1
// );
let circle = circles.find((circle) => {
return circle.contains(path.segments[hitIndex].point)
})
circle!.position.x = point!.x
circle!.position.y = point!.y
}
// 移动路径
path.segments[hitIndex].point.x = point!.x
path.segments[hitIndex].point.y = point!.y
// 移动选中点
usePaperSupportStore.getState().selectedCircles[
hitIndex
]!.position.x = point!.x
usePaperSupportStore.getState().selectedCircles[
hitIndex
]!.position.y = point!.y
// 移动蒙版
usePaperSupportStore.getState().handleMask(path)
usePaperSupportStore.getState().handleText(path as paper.Path)
}
}
}
// 如果 activePath 被选中了 即可以被移动修改
if (activePath?.data.selected) {
movePath(activePath)
isChanged = true
} else {
// 没被选中可能是children被选中
if (activePath instanceof paper.CompoundPath) {
// let selectedItems = activePath.children.filter(
// (item) => item.data.selected
// );
// console.log("selectedItems", selectedItems);
const selectedItem: paper.Path = (
activePath.children as paper.Path[]
).filter((item) => item.data.selected)[0]
;(activePath.children as paper.Path[]).forEach((item) => {
if (item && item.data.selected) {
movePath(item as paper.Path)
isChanged = true
} else if (
item.data.isHollowPolygon &&
selectedItem &&
selectedItem.bounds.contains(item.bounds)
) {
// 当前被选中路径的镂空区域需要一起移动
movePath(item as paper.Path)
isChanged = true
}
})
}
}
}
}
// 改变对象不能用 activeImage activeOperation (因为图形只在对应图片渲染所以activeImage是一样的)
panTool.onMouseUp = (_e: paper.ToolEvent) => {
if (useTopToolsStore.getState().isView) return
let newDrawPath: paper.Path | paper.CompoundPath | null = null
if (pressCtrl) {
if (rect) handleCtrl(rect)
rect?.remove()
return
}
const handleBrushIntersectRaster = (currentPath: paper.Path) => {
const currentSegments = currentPath.segments
if (!currentSegments || (currentSegments && !currentSegments.length))
return
// 设置首尾连线 判断是否跟自身当前有交点
let flag: boolean
if (currentSegments.length === 2) flag = false
else if (currentSegments.length > 2) {
const line = new paper.Path([
currentSegments[0],
currentSegments[currentSegments.length - 1],
])
const lineIntersections = line.getIntersections(currentPath)
flag = lineIntersections.length > 1 ? false : true
} else {
flag = false
}
let intersections = currentPath.getIntersections(
usePaperStore.getState().rasterPath!
)
let validIntersections = intersections.filter((intersection) => {
// 检查交点是否在两个路径的可见部分
return (
currentPath.contains(intersection.point) &&
usePaperStore.getState().rasterPath!.contains(intersection.point)
)
})
// intersectionPath as paper.CompoundPath | paper.Path | null
let intersectionPath: any = currentPath.intersect(
usePaperStore.getState().rasterPath!,
{
insert: false,
trace: flag,
}
)
// 没有 validIntersections 不进行操作
if (validIntersections.length) {
// 处理相交部分
if (intersectionPath instanceof paper.CompoundPath) {
// intersectionPath是CompoundPath
let new_segments: any[] = []
if (
intersectionPath.children &&
!intersectionPath.children.length
) {
// unconfirm activePath被修改了
;(activePath.children as paper.Path[]).forEach(
(activeChild, index) => {
if (index !== 0) {
new_segments.push(...activeChild.segments)
}
}
)
activePath.removeChildren(1)
currentPath.segments = new_segments
} else {
;(intersectionPath.children as paper.Path[]).forEach((child) => {
if (child.segments && child.segments.length) {
new_segments.push(...child.segments)
}
})
currentPath.segments = new_segments
}
} else if (
intersectionPath.segments &&
intersectionPath.segments.length
) {
currentPath.segments = intersectionPath.segments
}
// let oldCompoundPolygon = compoundPolygon;
// compoundPolygon = oldCompoundPolygon?.intersect(
// usePaperStore.getState().rasterPath!
// ) as paper.Path;
// compoundPolygon.closed = false;
// oldCompoundPolygon?.remove();
// usePaperSupportStore.getState().handleMask(currentPath);
} else {
// path整体移动到图片外 清空path的点
if (
!usePaperStore
.getState()
.rasterPath!.bounds.contains(currentPath.bounds)
) {
currentPath.segments = []
}
}
intersectionPath.remove()
intersectionPath = null
usePaperSupportStore.getState().handleMask(currentPath)
usePaperSupportStore.getState().handleBufferPaths(currentPath)
usePaperSupportStore.getState().handleCircles(currentPath)
}
const handlePolygonIntersectRaster = (path: paper.Path) => {
let intersections = path.getIntersections(
usePaperStore.getState().rasterPath!
)
let validIntersections = intersections.filter((intersection) => {
// 检查交点是否在两个路径的可见部分
return (
path.contains(intersection.point) &&
usePaperStore.getState().rasterPath!.contains(intersection.point)
)
})
if (validIntersections.length) {
let oldPolygon: any = path
console.log("处理前", path)
newDrawPath = oldPolygon.intersect(
usePaperStore.getState().rasterPath!
) as any
console.log("after", newDrawPath)
// if (!newDrawPath?.clockwise) newDrawPath?.reverse();
if (newDrawPath instanceof paper.CompoundPath)
(newDrawPath.children as paper.Path[]).forEach((child) => {
if (
!child.data ||
(child.data && !Object.keys(child.data).length)
)
child.data = safeClone(newDrawPath?.data || {})
})
oldPolygon.remove()
oldPolygon = null
} else {
// path整体移动到图片外 清空path的点
if (
!usePaperStore
.getState()
.rasterPath!.bounds.contains(path.bounds) &&
newDrawPath instanceof paper.Path
) {
newDrawPath.segments = []
}
}
usePaperSupportStore.getState().handleBufferPaths(newDrawPath)
usePaperSupportStore.getState().handleCircles(newDrawPath)
}
const handlePointIntersectRaster = (path: paper.Path) => {
let circles = usePaperStore.getState().group!.getItems({
data: {
id: path.data.id,
type: "circle",
},
}) as paper.Path[]
// 去除超出图片边界的部分
let newCircles = circles.filter((circle) => {
if (
!usePaperStore
.getState()
.rasterPath!.bounds.contains(circle.bounds.center)
) {
circle.remove()
return false
} else {
return true
}
})
// 1. 对数组按照 text 属性进行排序
let sortedArr = newCircles
.slice()
.sort((a, b) => a.data.text - b.data.text)
// 2. 重新赋值 text 属性为从 1 开始的连续自然数
sortedArr.forEach((item, index) => {
item.data.text = index + 1
})
// newCircles.forEach((circle, circleIndex) => {
// // 重新设置显示数字
// circle.data.text = circleIndex + 1;
// });
path.segments = path.segments.filter((segment) => {
return usePaperStore
.getState()
.rasterPath!.bounds.contains(segment.point)
})
// 去除超出图片边界的部分 end
usePaperSupportStore.getState().handleMask(path)
usePaperSupportStore.getState().handleBufferPaths(path)
usePaperSupportStore.getState().handleCircles(path)
usePaperSupportStore.getState().handleText(path)
}
const handlePolygonIntersect = () => {
if (panMode !== "stroke" && newDrawPath) {
const handleCurrentPathSub = (eachPath: paper.Path) => {
if (newDrawPath) {
let oldPolygon: any = newDrawPath
let oldParent: any = newDrawPath.parent
newDrawPath = oldPolygon.subtract(eachPath)
// if (!newDrawPath?.clockwise) newDrawPath?.reverse();
if (newDrawPath instanceof paper.CompoundPath) {
;(newDrawPath.children as paper.Path[]).forEach((child) => {
child.data = safeClone(
Object.assign({}, newDrawPath?.data || {}, {
isHollowPolygon: !child.clockwise,
})
)
})
if (oldParent instanceof paper.CompoundPath) {
;(oldParent.children as paper.Path[]).forEach((child) => {
child.data = safeClone(
Object.assign({}, newDrawPath?.data || {}, {
isHollowPolygon: !child.clockwise,
})
)
})
}
}
oldPolygon.remove()
oldPolygon = null
}
}
usePaperStore.getState().group?.children!.forEach((item: any) => {
if (
!item.data.id ||
item.data.type !== "polygon" ||
!item.visible ||
item.data.id === newDrawPath?.data.id
)
return
if (item instanceof paper.Path) {
if (item.segments.length) {
handleCurrentPathSub(item)
}
} else if (item instanceof paper.CompoundPath) {
;(item.children as paper.Path[]).forEach((child) => {
if (
child?.segments?.length &&
child.data.id !== newDrawPath?.data.id
) {
handleCurrentPathSub(child)
}
})
}
})
}
}
const handlePolygonUnite = () => {
const handleCurrentPathUnite = (eachPath: paper.Path) => {
if (!newDrawPath) return
let intersectionPath = newDrawPath.intersect(eachPath, {
insert: false,
}) as paper.Path
if (intersectionPath.segments?.length) {
let oldPolygon: any = newDrawPath
newDrawPath = oldPolygon.unite(eachPath) as any
// if (!newDrawPath?.clockwise) newDrawPath?.reverse();
if (newDrawPath instanceof paper.CompoundPath)
(newDrawPath.children as paper.Path[]).forEach((child) => {
child.data = safeClone(newDrawPath?.data || {})
})
oldPolygon.remove()
oldPolygon = null
}
}
usePaperStore.getState().group?.children!.forEach((item: any) => {
if (!item.data.id || item.data.type !== "polygon" || !item.visible)
return
if (item instanceof paper.CompoundPath) {
;(item.children as paper.Path[]).forEach((child) => {
handleCurrentPathUnite(child)
})
} else if (item instanceof paper.Path) {
handleCurrentPathUnite(item)
}
})
}
const getNewDetail = (detailPathData: {
imageId: string
operationId: any
id: number
}) => {
const currentDetail = useLabelStore
.getState()
.getLabelDetailDataByKeys(
detailPathData.imageId,
detailPathData.operationId,
detailPathData.id
)
const newDetail = currentDetail
? {
...currentDetail,
first_modified_timestamp:
currentDetail.first_modified_timestamp || dayjs().unix(),
first_modified_uid: currentDetail.first_modified_uid || user_id,
last_modified_timestamp: dayjs().unix(),
last_modified_uid: user_id,
}
: {
...initialDetail,
create_timestamp: dayjs().unix(),
category_id: Number(detailPathData.operationId),
}
return newDetail
}
// 保存矩形类型数据结果
const handleSaveRect = (rectPath: any) => {
// 去除超出图片边界的部分
handleRectangleIntersectRaster(rectPath)
usePaperSupportStore.getState().handleBufferPaths(rectPath)
usePaperSupportStore.getState().handleCircles(rectPath)
const newDetail = getNewDetail(rectPath.data)
let pathData = JSON.parse(rectPath.exportJSON({ precision: 20 }))
if (rectPath.data.id && rectPath.segments?.length) {
useLabelStore
.getState()
.setOperation(rectPath.data.imageId, rectPath.data.operationId, [
rectPath.data.id,
[pathData[1].segments],
newDetail,
[],
])
} else {
useLabelStore
.getState()
.deleteOperation(
rectPath.data.imageId,
rectPath.data.operationId,
rectPath.data.id
)
}
forceUpdate()
}
// 保存多边形类型数据结果
const handleSavePolygon = (polygonPath: any) => {
newDrawPath = new paper.Path()
let parent: any = usePaperStore.getState().group
let hollowPaths: Array<{ path: paper.Path; index: number }> = []
let area = 0
if (polygonPath instanceof paper.CompoundPath) {
let oldSelected = (polygonPath.children as paper.Path[]).find(
(item) => item.data.selected
)!
parent = polygonPath
newDrawPath.set({
segments: oldSelected.segments,
data: oldSelected.data,
strokeColor: oldSelected.strokeColor,
strokeWidth: oldSelected.strokeWidth,
fillColor: oldSelected.fillColor,
closed: oldSelected.closed,
parent: usePaperStore.getState().group,
})
oldSelected.remove()
// newDrawPath = (polygonPath.children as paper.Path[]).find(
// (item) => item.data.selected
// )!;
area = (newDrawPath as paper.Path).area
// 设置镂空数组
polygonPath.children.forEach((child: any, index: number) => {
if (
child.data.isHollowPolygon &&
newDrawPath?.bounds.contains(child.bounds)
) {
hollowPaths.push({ path: child, index })
}
})
} else {
newDrawPath = polygonPath
area = (newDrawPath as paper.Path).area
}
console.log("保存当前被选中路径的结果", newDrawPath)
// 边界裁剪
handlePolygonIntersectRaster(newDrawPath as any)
usePaperSupportStore.getState().handleCircles(newDrawPath)
console.log("边界裁剪之后", newDrawPath)
if (useTopToolsStore.getState().drawOption === "intersect") {
// 裁剪
handlePolygonIntersect()
usePaperSupportStore.getState().handleCircles(newDrawPath)
} else if (useTopToolsStore.getState().drawOption === "unite") {
// 融合
handlePolygonUnite()
usePaperSupportStore.getState().handleCircles(newDrawPath)
}
// 路径已发生改变则清空之前存的镂空区域,后续无需处理
// if (
// newDrawPath instanceof paper.CompoundPath ||
// (newDrawPath instanceof paper.Path && newDrawPath.area !== area)
// ) {
// hollowPaths.forEach((item) => {
// item.path?.remove();
// });
// hollowPaths = [];
// }
console.log("裁剪之后", newDrawPath)
if (
newDrawPath instanceof paper.CompoundPath ||
(newDrawPath instanceof paper.Path && newDrawPath.area !== area)
) {
hollowPaths.forEach((item) => {
item.path?.remove()
})
hollowPaths = []
} else {
hollowPaths = hollowPaths.filter((item) => {
let subtractPath = newDrawPath!.subtract(item.path!, {
insert: false,
})
if (subtractPath instanceof paper.Path) {
item.path?.remove()
return false
} else {
return true
}
})
}
// 边界裁剪
// handlePolygonIntersectRaster(newDrawPath as any);
// if (
// newDrawPath instanceof paper.CompoundPath ||
// (newDrawPath instanceof paper.Path && newDrawPath.area !== area)
// ) {
// hollowPaths.forEach((item) => {
// item.path?.remove();
// });
// hollowPaths = [];
// }
// usePaperSupportStore.getState().handleCircles(newDrawPath);
// console.log("边界裁剪之后", newDrawPath);
if (newDrawPath instanceof paper.CompoundPath) {
let selectedItems = usePaperStore.getState().group!.getItems({
data: { selected: true },
})
selectedItems.forEach((item) => {
item.data.selected = false
})
handleCancelSelect()
}
// 存在镂空区域时,对镂空区域进行边界裁剪
if (hollowPaths.length) {
hollowPaths.forEach((hollowPath) => {
if (
!usePaperStore
.getState()
.rasterPath!.bounds.contains(hollowPath.path.bounds)
) {
polygonPath.removeChildren(hollowPath.index, hollowPath.index + 1)
return
}
// hollowPath.path.clockwise = false;
hollowPath.path.data = safeClone(
Object.assign({}, hollowPath.path?.data || {}, {
isHollowPolygon: true,
})
)
// let oldPolygon: any = hollowPath.path;
// let finnalPath = oldPolygon.intersect(
// usePaperStore.getState().rasterPath!
// ) as any;
// console.log("finnalPath", finnalPath);
// // if (hollowPath.clockwise) hollowPath.reverse();
// if (finnalPath instanceof paper.CompoundPath) {
// (finnalPath.children as paper.Path[]).forEach((child) => {
// // child.clockwise = false;
// child.data = safeClone(
// Object.assign({}, oldPolygon?.data || {}, {
// isHollowPolygon: true,
// })
// );
// });
// } else if (finnalPath instanceof paper.Path) {
// // finnalPath.clockwise = false;
// finnalPath.data = safeClone(
// Object.assign({}, oldPolygon?.data || {}, {
// isHollowPolygon: true,
// })
// );
// }
// oldPolygon.remove();
// oldPolygon = null;
// polygonPath.removeChildren(hollowPath.index, hollowPath.index + 1);
// polygonPath.addChild(finnalPath);
})
}
if (!(parent instanceof paper.Group)) {
if (newDrawPath instanceof paper.CompoundPath) {
let len = newDrawPath.children.length
for (let i = len - 1; i >= 0; i--) {
let child = newDrawPath.children[i]
child.set({
parent: parent,
})
}
} else {
newDrawPath?.set({
parent: parent,
})
}
}
// 创建对象 重新渲染对象 都需要添加 imageId operationId
if (
polygonPath.data.imageId &&
polygonPath.data.operationId &&
polygonPath.data.id
) {
const newDetail = getNewDetail(polygonPath.data)
let savedItems = usePaperStore.getState().group!.getItems({
data: { id: polygonPath.data.id },
}) as paper.Path[]
console.log(savedItems)
let saveCompoundItems = savedItems.filter(
(item) =>
item instanceof paper.Path &&
item.data.type === "polygon" &&
item.segments.length
)
let handleSavePaths = (paths: paper.Path[]) => {
let polygonPoints: any[] = []
let polygonHollowPoints: any[] = []
let bool = paths.some((child) => {
return child.segments.length > 0
})
if (bool) {
;(paths as paper.Path[]).forEach((path) => {
if (path.data.type !== "polygon") return
let pathData = JSON.parse(path.exportJSON({ precision: 20 }))
// 在镂空区域中 镂空区域路径方向为false
if (!path.data.isHollowPolygon) {
polygonPoints.push(pathData[1]?.segments)
} else {
if (path.data.id && path.segments?.length) {
polygonHollowPoints.push(pathData[1]?.segments)
}
}
})
console.log(
"编辑后最终保存结果",
polygonPoints,
polygonHollowPoints
)
useLabelStore
.getState()
.setOperation(
polygonPath.data.imageId,
polygonPath.data.operationId,
[
polygonPath.data.id,
polygonPoints,
newDetail,
polygonHollowPoints,
]
)
} else {
useLabelStore
.getState()
.deleteOperation(
polygonPath.data.imageId,
polygonPath.data.operationId,
polygonPath.data.id
)
}
}
handleSavePaths(saveCompoundItems)
activePath =
newDrawPath?.parent instanceof paper.CompoundPath
? newDrawPath.parent
: (newDrawPath as any)
forceUpdate()
}
}
// 保存线段类型数据结果
const handleSaveBrush = (brushPath: any) => {
if (brushPath instanceof paper.CompoundPath) {
;(brushPath.children as paper.Path[]).forEach((path) => {
handleBrushIntersectRaster(path)
})
} else {
handleBrushIntersectRaster(brushPath)
}
if (
brushPath.data.imageId &&
brushPath.data.operationId &&
brushPath.data.id
) {
const currentDetail = useLabelStore
.getState()
.getLabelDetailDataByKeys(
brushPath.data.imageId,
brushPath.data.operationId,
brushPath.data.id
)
const newDetail = currentDetail
? {
...currentDetail,
first_modified_timestamp:
currentDetail.first_modified_timestamp || dayjs().unix(),
first_modified_uid: currentDetail.first_modified_uid || user_id,
last_modified_timestamp: dayjs().unix(),
last_modified_uid: user_id,
}
: {
...initialDetail,
create_timestamp: dayjs().unix(),
category_id: Number(brushPath.data.operationId),
}
if (brushPath instanceof paper.CompoundPath) {
const brushChildren = brushPath.children
if (brushChildren && brushChildren.length) {
const points: any[] = []
;(brushChildren as paper.Path[]).forEach((childPath) => {
if (childPath.segments && childPath.segments.length) {
let pathData = JSON.parse(
childPath.exportJSON({ precision: 20 })
)
points.push(pathData[1]?.segments)
}
})
if (points.length)
useLabelStore
.getState()
.setOperation(
brushPath.data.imageId,
brushPath.data.operationId,
[brushPath.data.id, points, newDetail, []]
)
else {
usePaperSupportStore
.getState()
.removeMaskById(brushPath.data.id)
useLabelStore
.getState()
.deleteOperation(
brushPath.data.imageId,
brushPath.data.operationId,
brushPath.data.id
)
}
}
} else if (brushPath instanceof paper.Path) {
if (brushPath.segments && brushPath.segments.length) {
const pathData = JSON.parse(
brushPath.exportJSON({ precision: 20 })
)
const brushSegments = pathData?.[1]?.segments
if (brushSegments?.length) {
useLabelStore
.getState()
.setOperation(
brushPath.data.imageId,
brushPath.data.operationId,
[brushPath.data.id, [brushSegments], newDetail, []]
)
} else {
usePaperSupportStore
.getState()
.removeMaskById(brushPath.data.id)
useLabelStore
.getState()
.deleteOperation(
brushPath.data.imageId,
brushPath.data.operationId,
brushPath.data.id
)
}
} else {
usePaperSupportStore.getState().removeMaskById(brushPath.data.id)
useLabelStore
.getState()
.deleteOperation(
brushPath.data.imageId,
brushPath.data.operationId,
brushPath.data.id
)
}
}
forceUpdate()
}
}
const handleSavePoint = (pointPath: any) => {
handlePointIntersectRaster(pointPath)
if (
pointPath.data.imageId &&
pointPath.data.operationId &&
pointPath.data.id
) {
const currentDetail = useLabelStore
.getState()
.getLabelDetailDataByKeys(
pointPath.data.imageId,
pointPath.data.operationId,
pointPath.data.id
)
const newDetail = currentDetail
? {
...currentDetail,
first_modified_timestamp:
currentDetail.first_modified_timestamp || dayjs().unix(),
first_modified_uid: currentDetail.first_modified_uid || user_id,
last_modified_timestamp: dayjs().unix(),
last_modified_uid: user_id,
}
: {
...initialDetail,
create_timestamp: dayjs().unix(),
category_id: Number(pointPath.data.operationId),
}
if (pointPath instanceof paper.Path) {
if (pointPath.segments && pointPath.segments.length) {
const pathData = JSON.parse(
pointPath.exportJSON({ precision: 20 })
)
const pointSegments = pathData?.[1]?.segments
if (pointSegments?.length) {
useLabelStore
.getState()
.setOperation(
pointPath.data.imageId,
pointPath.data.operationId,
[pointPath.data.id, [pointSegments], newDetail, []]
)
} else {
usePaperSupportStore
.getState()
.removeMaskById(pointPath.data.id)
useLabelStore
.getState()
.deleteOperation(
pointPath.data.imageId,
pointPath.data.operationId,
pointPath.data.id
)
}
} else {
usePaperSupportStore.getState().removeMaskById(pointPath.data.id)
useLabelStore
.getState()
.deleteOperation(
pointPath.data.imageId,
pointPath.data.operationId,
pointPath.data.id
)
}
}
forceUpdate()
}
}
if (isChanged) {
if (
(activePath && activePath.data.selected) ||
(activePath?.children?.length &&
activePath?.children?.findIndex((item) => item.data.selected) !==
-1)
) {
if (
panMode === "segment" ||
panMode === "fill" ||
panMode === "stroke"
) {
if (activePath.data.type === "rectangle") {
handleSaveRect(activePath)
} else if (activePath.data.type === "brush") {
handleSaveBrush(activePath)
} else if (activePath.data.type === "polygon") {
handleSavePolygon(activePath)
} else if (activePath.data.type === "point") {
handleSavePoint(activePath)
}
}
} else {
// 没被选中
}
isChanged = false
}
}
panTool.onKeyDown = (e: paper.KeyEvent) => {
if (e.key === "delete") {
handleDelete()
usePaperSupportStore.getState().clearTexts()
let ids =
useObjectStore.getState().selectedPath[
useBottomToolsStore.getState().activeImage
]
usePaperSupportStore.getState().removeMaskByIds(ids)
forceUpdate()
} else if (e.key === "shift") {
pressShift = true
useKeyEventStore.getState().setShift(true)
} else if (e.key === "control") {
pressCtrl = true
}
}
panTool.onKeyUp = (e: paper.KeyEvent) => {
if (e.key === "shift") {
pressShift = false
useKeyEventStore.getState().setShift(false)
} else if (e.key === "control") {
pressCtrl = false
}
}
panTool.onMouseMove = (e: paper.MouseEvent) => {
handleMouseMove(e)
}
set((state: PaperState) => ({
...state,
panTool: panTool,
}))
},
addPolygon: (
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
points: paper.Point[] | undefined,
option: any,
data: any
) => {
let polygon = renderPath(
usePaperStore.getState().group!,
Object.assign(option, {
strokeScaling: false,
})
)
polygon.data = Object.assign({ type: "polygon", ...option }, data)
points?.forEach((point) => {
polygon.add(point)
})
polygon.onMouseEnter = (_e: paper.MouseEvent) => {
// if (usePaperStore.getState().mode === "pan") {
// e.target.strokeWidth += 2;
// if (usePaperStore.getState().el)
// usePaperStore.getState().el!.style.cursor = "move";
// }
}
polygon.onMouseLeave = (_e: paper.MouseEvent) => {
// if (usePaperStore.getState().mode === "pan") {
// // render 结束没有触发 onMouseEnter
// if (e.target.strokeWidth - 2 > 0) e.target.strokeWidth -= 2;
// if (usePaperStore.getState().el)
// usePaperStore.getState().el!.style.cursor = "default";
// }
}
polygon.onDoubleClick = (e: paper.MouseEvent) => {
console.log("polygon.onDoubleClick", e)
}
polygon.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event && e.event.button === 2) {
if (polygon.data.id) {
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === polygon.data.operationId
)
const { top, left } = getShowContextMenuPosition(
e.point,
category!,
5,
5
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: polygon.data.imageId,
operationId: polygon.data.operationId,
id: polygon.data.id,
})
}
}
}
return polygon
},
addCompoundPath: (
renderCompoundPath: (
paperGroup: paper.Group,
option: any
) => paper.CompoundPath,
children: paper.Path[] | undefined,
option: any,
data: any
) => {
let compoundPath = renderCompoundPath(
usePaperStore.getState().group!,
Object.assign(option, {
children,
strokeScaling: false,
})
)
compoundPath.data = Object.assign({ type: "polygon", ...option }, data)
compoundPath.onMouseDown = (e: {
event: MouseEvent
point: paper.Point
}) => {
if (e.event && e.event.button === 2) {
if (compoundPath.data.id) {
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) =>
item.category_id.toString() === compoundPath.data.operationId
)
const { top, left } = getShowContextMenuPosition(
e.point,
category!,
5,
5
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: compoundPath.data.imageId,
operationId: compoundPath.data.operationId,
id: compoundPath.data.id,
})
}
}
}
return compoundPath
},
initPolygonTool: (
paperPolygonTool: paper.Tool,
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
renderCompoundPath: (
paperGroup: paper.Group,
option: any
) => paper.CompoundPath,
forceUpdate: DispatchWithoutAction
) => {
let selectedCircles: paper.Path.Circle[] = []
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 isMouseDown: boolean = false
let polygonTool = paperPolygonTool
let currentMagnetPoint: paper.Point | null = null
const handleCircles = (path: paper.Path | paper.CompoundPath | null) => {
// 更新选中的点
if (selectedCircles && selectedCircles.length) {
selectedCircles.forEach((item) => {
item.remove()
})
selectedCircles = []
}
path &&
path instanceof paper.Path &&
path.segments.forEach((segment) => {
let circle = usePaperStore
.getState()
.getClickedCircle(
segment.point,
segment.index,
path.data.id,
path.data.blankColor
)
selectedCircles.push(circle)
})
}
const handlePolygonIntersectRaster = (savePath: paper.Path) => {
let oldPolygon: any = savePath
polygon = oldPolygon.intersect(
usePaperStore.getState().rasterPath!
) as any
if (polygon instanceof paper.CompoundPath)
(polygon.children as paper.Path[]).forEach((child) => {
if (!child.data || (child.data && !Object.keys(child.data).length))
child.data = safeClone(
Object.assign(
{ isHollowPolygon: !child.clockwise },
polygon?.data || {}
)
)
})
oldPolygon.remove()
oldPolygon = null
}
const handlePolygonIntersect = (path: paper.Path) => {
const handleCurrentPathSub = (eachPath: paper.Path) => {
if (!polygon) return
let oldPolygon: any = polygon
polygon = oldPolygon?.subtract(eachPath) as any
if (polygon instanceof paper.CompoundPath)
(polygon.children as paper.Path[]).forEach((child) => {
child.data = safeClone(
Object.assign(
{ isHollowPolygon: !child.clockwise },
polygon?.data || {}
)
)
})
oldPolygon?.remove()
oldPolygon = null
}
usePaperStore.getState().group?.children!.forEach((item: any) => {
if (!item.data.id || item.data.type !== "polygon" || !item.visible)
return
if (item instanceof paper.Path) {
if (item?.segments?.length && item.data.id !== path?.data.id) {
handleCurrentPathSub(item)
}
} else if (
item instanceof paper.CompoundPath &&
item.data.id !== path.data.id
) {
;(item.children as paper.Path[]).forEach((child) => {
if (child?.segments?.length && child.data.id !== path?.data.id) {
handleCurrentPathSub(child)
}
})
}
})
}
const handlePolygonUnite = () => {
const handleCurrentPathUnite = (eachPath: paper.Path) => {
if (!polygon) return
let intersectionPath = polygon.intersect(eachPath, {
insert: false,
}) as paper.Path
if (intersectionPath.segments?.length) {
let oldPolygon: any = polygon
polygon = oldPolygon.unite(eachPath) as any
if (polygon instanceof paper.CompoundPath)
(polygon.children as paper.Path[]).forEach((child) => {
child.data = safeClone(polygon?.data || {})
})
oldPolygon?.remove()
oldPolygon = null
}
}
usePaperStore.getState().group?.children!.forEach((item: any) => {
if (!item.data.id || item.data.type !== "polygon" || !item.visible)
return
if (item instanceof paper.CompoundPath) {
;(item.children as paper.Path[]).forEach((child) => {
handleCurrentPathUnite(child)
})
} else if (item instanceof paper.Path) {
handleCurrentPathUnite(item)
}
})
}
polygonTool.onMouseDown = (e: {
event: MouseEvent
point: paper.Point
}) => {
if (e.event.button !== 0) return
isMouseDown = true
// 绘制时去除选中辅助
if (usePaperSupportStore.getState().selectedCircles.length) {
usePaperSupportStore.getState().clearAll()
}
// 先判断是否存在吸附点
let checkPoint = currentMagnetPoint ? currentMagnetPoint : e.point
// doubleclick
if (
lastPoint &&
lastPoint.x === checkPoint.x &&
lastPoint.y === checkPoint.y
) {
if (polygon) {
if ((polygon as paper.Path).segments.length < 3) {
polygon.remove()
selectedCircles.forEach((item) => {
item.remove()
})
}
// 调整polygon点位初始顺序
if (!polygon.clockwise) polygon.reverse()
// 初始化 compoundPolygon
compoundPolygon = new paper.Path()
// pressA 模式下添加多边形
if (useTopToolsStore.getState().pressA) {
let ids =
useObjectStore.getState().selectedPath[
useBottomToolsStore.getState().activeImage
]
let paths = usePaperStore.getState().getItemsById(ids[0])
let oldPolygon = polygon
// 闭合绘制路径
oldPolygon.closePath()
if (paths.length === 1) {
const prevPath = paths[0]
polygon = usePaperStore.getState().addCompoundPath(
renderCompoundPath,
[prevPath, oldPolygon] as paper.Path[],
{
// option
...usePaperStore.getState().toolOption,
},
{ ...oldPolygon.data, id: prevPath?.data.id }
)
} else if (paths.length > 1) {
const prevPath = paths.filter(
(path) => path instanceof paper.CompoundPath
)[0]
// 直接将当前路径添加到原有父路径
prevPath.addChild(oldPolygon)
polygon = prevPath
}
}
if (useTopToolsStore.getState().drawOption === "intersect") {
// 裁剪
handlePolygonIntersect(polygon as paper.Path)
handleCircles(polygon)
} else if (useTopToolsStore.getState().drawOption === "unite") {
// 融合
handlePolygonUnite()
handleCircles(polygon)
}
console.log("裁剪之后", polygon)
// if (useTopToolsStore.getState().drawOption === "default") {
// if (useTopToolsStore.getState().pressA)
// compoundPolygon = polygon.clone();
// }
// 去除超出图片边界的部分
handlePolygonIntersectRaster(polygon as any)
// 去除超出图片边界的部分
console.log("边界裁剪之后", polygon)
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) =>
item.category_id.toString() ===
useTopToolsStore.getState().activeOperation
)
let label_class = category?.label_class || ""
let subAttr = Object.assign(
{},
useTopToolsStore.getState().subAttributes[label_class]
)
// 镂空或分割的多边形存值
if (polygon instanceof paper.CompoundPath) {
if (polygon.children && polygon.children.length) {
let points: any[] = []
let hollowPoints: any[] = []
;(polygon.children as paper.Path[]).forEach((path) => {
let pathData = JSON.parse(path.exportJSON({ precision: 20 }))
// 在镂空或分割的区域中 路径方向是false
if (path.clockwise) {
points.push(pathData[1]?.segments)
} else {
hollowPoints.push(pathData[1]?.segments)
}
})
console.log(
"处理完成的点位数据",
points,
hollowPoints,
polygon.data
)
useLabelStore
.getState()
.setOperation(
useBottomToolsStore.getState().activeImage,
useTopToolsStore.getState().activeOperation,
[
polygon.data.id,
points,
{
...initialDetail,
uid: usePermissionStore.getState().user_id,
comment: [
...useLabelStore.getState().labelDefaultComments,
],
create_timestamp: dayjs().unix(),
sub_attributes: subAttr,
image_id: useBottomToolsStore.getState().activeImageId,
category_id: Number(
useTopToolsStore.getState().activeOperation
),
},
hollowPoints,
]
)
if (!useTopToolsStore.getState().pressA)
useRightToolsStore.getState().pushPathId(polygon.data.id)
}
} else if (
// 未镂空或分割 存值
polygon instanceof paper.Path &&
polygon.data.id &&
polygon.segments?.length
) {
let pathData = JSON.parse(polygon.exportJSON({ precision: 20 }))
console.log(pathData[1]?.segments)
useLabelStore
.getState()
.setOperation(
useBottomToolsStore.getState().activeImage,
useTopToolsStore.getState().activeOperation,
[
polygon.data.id,
// todo intersect+a | +a会变成CompoundPath 理论上已解决这个todo
[pathData[1]?.segments, ...[]],
{
...initialDetail,
uid: usePermissionStore.getState().user_id,
comment: [...useLabelStore.getState().labelDefaultComments],
create_timestamp: dayjs().unix(),
sub_attributes: subAttr,
image_id: useBottomToolsStore.getState().activeImageId,
category_id: Number(
useTopToolsStore.getState().activeOperation
),
},
[],
]
)
useRightToolsStore.getState().pushPathId(polygon.data.id)
}
// 如果该类别存在子属性,则弹出子属性对话框
if (category && category.sub_attributes.length) {
const { top, left } = getShowContextMenuPosition(
e.point,
category!,
-30,
-30
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: polygon.data.imageId,
operationId: polygon.data.operationId,
id: polygon.data.id,
})
}
// 设置其为selectedPath
useObjectStore
.getState()
.updateSelectedPath(
useBottomToolsStore.getState().activeImage,
"ADD",
polygon.data.id
)
forceUpdate()
if (compoundPolygon instanceof paper.CompoundPath) {
polygon.remove()
polygon = null
points = []
} else {
compoundPolygon?.remove()
compoundPolygon = null
polygon?.closePath()
polygon = null
points = []
}
selectedCircles.forEach((item) => {
item.remove()
})
const magnetPaths = usePaperStore
.getState()
.group!.getItems({ data: { type: "magnet" } })
magnetPaths.forEach((item) => {
item.remove()
})
currentMagnetPoint = null
useTopToolsStore.getState().setPressA(false)
return
}
} else {
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: false,
position: {
top: 0,
left: 0,
},
imageId: "",
operationId: "",
id: 0,
})
// 清空多边形 fillColor
usePaperStore
.getState()
.group!.getItems({})
.forEach((item) => {
// clear color
item.fillColor = item?.data?.blankColor
})
}
let temp = renderPath(usePaperStore.getState().group!, {})
let point = currentMagnetPoint
? currentMagnetPoint
: temp.globalToLocal(e.point)
let circle = usePaperStore
.getState()
.getClickedCircle(
point,
selectedCircles.length,
polygon?.data.id,
polygon?.data.blankColor
)
// add circle
selectedCircles.push(circle)
// add point
points.push(point)
if (polygon) {
polygon.remove()
}
polygon = usePaperStore.getState().addPolygon(
renderPath,
points,
{
// 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,
}
)
lastPoint = checkPoint
lastTime = Date.now()
}
polygonTool.onMouseUp = (_e: paper.ToolEvent) => {
isMouseDown = false
}
polygonTool.onMouseMove = (e: paper.MouseEvent) => {
handleMouseMove(e)
if (polygon) {
let temp = renderPath(usePaperStore.getState().group!, {})
let point = temp.globalToLocal(e.point)
// let point = e.point;
// when mousedown then drag draw
const paperGroup = usePaperStore.getState().group!
const currentPloygons = paperGroup.getItems({
data: { type: "polygon" },
})
// clear prev magnet
const prevHitResult = paperGroup.getItems({
data: { type: "magnet" },
})
prevHitResult.forEach((item) => {
item.remove()
})
currentMagnetPoint = null
if (useTopToolsStore.getState().magnetFlag)
currentPloygons.forEach((imgPolygon) => {
if (
imgPolygon instanceof paper.Path &&
imgPolygon.data.id !== polygon!.data.id
) {
const hitResult = imgPolygon.hitTest(point, {
stroke: true,
tolerance: usePaperStore.getState().paperScope
? 8 / usePaperStore.getState().paperScope!.view.zoom
: 0,
})
if (hitResult) {
// 绘制高亮边及其端点
const { point, location } = hitResult
const magnetPoint = new paper.Path.Circle(point, 4).set({
fillColor: "white",
strokeScaling: false,
data: { type: "magnet" },
})
magnetPoint.scale(1 / usePaperStore.getState().group!.scaling.x)
const magnetLine = new paper.Path.Line(
location.curve.segment1.point,
location.curve.segment2.point
).set({
strokeColor: "white",
strokeWidth: 2,
strokeScaling: false,
data: { type: "magnet" },
})
paperGroup.addChild(magnetPoint)
paperGroup.addChild(magnetLine)
currentMagnetPoint = point
}
}
})
if (isMouseDown) {
const delta = currentMagnetPoint
? (currentMagnetPoint as paper.Point).subtract(lastPoint)
: e.point.subtract(lastPoint)
const currentTime = Date.now()
const timeDelta = currentTime - lastTime
if (delta.length > 10 && timeDelta > 50) {
// Add a point to the current path if the distance exceeds the threshold
if (!currentMagnetPoint) {
points.push(point)
let circle = usePaperStore
.getState()
.getClickedCircle(
point,
selectedCircles.length,
polygon.data.id,
polygon.data.blankColor
)
// add circle
selectedCircles.push(circle)
;(polygon as paper.Path).add(point)
lastPoint = e.point
} else {
points.push(currentMagnetPoint)
let circle = usePaperStore
.getState()
.getClickedCircle(
currentMagnetPoint,
selectedCircles.length,
polygon.data.id,
polygon.data.blankColor
)
// add circle
selectedCircles.push(circle)
;(polygon as paper.Path).add(currentMagnetPoint)
lastPoint = currentMagnetPoint
}
lastTime = currentTime
}
} else {
polygon.remove()
polygon = usePaperStore.getState().addPolygon(
renderPath,
points.concat(point),
{
// option
...usePaperStore.getState().toolOption,
},
{}
)
}
}
}
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)
}
const magnetPaths = usePaperStore
.getState()
.group!.getItems({ data: { type: "magnet" } })
magnetPaths.forEach((item) => {
item.remove()
})
currentMagnetPoint = null
} else if (e.key === "delete") {
handleDelete()
forceUpdate()
} else if (e.key === "shift") {
handleShift(true)
}
}
polygonTool.onKeyUp = (e: paper.KeyEvent) => {
if (e.key === "shift") {
handleShift(false)
}
}
set((state: PaperState) => ({
...state,
polygonTool: polygonTool,
}))
},
addRectangle: (
renderRectangle: (
paperGroup: paper.Group,
option: any
) => paper.Path.Rectangle,
option: any,
data: any
) => {
let rect = renderRectangle(
usePaperStore.getState().group!,
Object.assign(option, { strokeScaling: false })
)
rect.data = Object.assign({ type: "rectangle", ...option }, data)
rect.onMouseEnter = (_e: paper.MouseEvent) => {
// if (usePaperStore.getState().mode === "pan") {
// e.target.strokeWidth += 2;
// if (usePaperStore.getState().el)
// usePaperStore.getState().el!.style.cursor = "move";
// }
}
rect.onMouseLeave = (_e: paper.MouseEvent) => {
// if (usePaperStore.getState().mode === "pan") {
// // render 结束没有触发 onMouseEnter
// if (e.target.strokeWidth - 2 > 0) e.target.strokeWidth -= 2;
// if (usePaperStore.getState().el)
// usePaperStore.getState().el!.style.cursor = "default";
// }
}
rect.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event && e.event.button === 2) {
if (rect.data.id) {
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === rect.data.operationId
)
const { top, left } = getShowContextMenuPosition(
e.point,
category!,
5,
5
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: rect.data.imageId,
operationId: rect.data.operationId,
id: rect.data.id,
})
}
}
}
return rect
},
initRectangleTool: (
paperRectangleTool: paper.Tool,
renderRectangle: (
paperGroup: paper.Group,
option: any
) => paper.Path.Rectangle,
forceUpdate: DispatchWithoutAction
) => {
let rect: paper.Path.Rectangle | null
let rectangleTool = paperRectangleTool
rectangleTool.onMouseDown = (e: { event: MouseEvent }) => {
if (e.event && e.event.button !== 2)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: false,
position: {
top: 0,
left: 0,
},
imageId: "",
operationId: "",
id: 0,
})
// 绘制时去除选中辅助
if (usePaperSupportStore.getState().selectedCircles.length) {
usePaperSupportStore.getState().clearAll()
}
}
rectangleTool.onMouseDrag = (e: {
event: MouseEvent
point: paper.Point
downPoint: paper.Point
}) => {
const delta = e.point.subtract(e.downPoint)
// 阻止右键移动矩形(e.event.button === 0)
if (delta.length > 10 && e.event.button === 0) {
let temp = renderRectangle(usePaperStore.getState().group!, {})
let point = temp.globalToLocal(e.point)
let downPoint = temp.globalToLocal(e.downPoint)
temp.remove()
rect = usePaperStore.getState().addRectangle(
renderRectangle,
{
from: downPoint,
to: point,
// 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,
}
)
rect.removeOnDrag()
// !!! 矩形不进行裁剪
// intersect drawOption 舍弃交集
// 舍弃交集
}
}
rectangleTool.onMouseUp = (e: {
event: MouseEvent
point: paper.Point
}) => {
// add segment judgement
if (rect && rect.segments?.length && rect?.data?.id) {
// 去除超出图片边界的部分
handleRectangleIntersectRaster(rect)
// 去除后无点位 则不作后续新增处理
if (!rect.segments.length) return
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) =>
item.category_id.toString() ===
useTopToolsStore.getState().activeOperation
)
let label_class = category?.label_class || ""
let subAttr = Object.assign(
{},
useTopToolsStore.getState().subAttributes[label_class]
)
useLabelStore
.getState()
.setOperation(
useBottomToolsStore.getState().activeImage,
useTopToolsStore.getState().activeOperation,
[
rect.data.id,
[JSON.parse(rect.exportJSON({ precision: 20 }))[1]?.segments],
{
...initialDetail,
uid: usePermissionStore.getState().user_id,
comment: [...useLabelStore.getState().labelDefaultComments],
create_timestamp: dayjs().unix(),
sub_attributes: subAttr,
image_id: useBottomToolsStore.getState().activeImageId,
category_id: Number(
useTopToolsStore.getState().activeOperation
),
},
[],
]
)
useRightToolsStore.getState().pushPathId(rect.data.id)
// 设置其为selectedPath
useObjectStore
.getState()
.updateSelectedPath(
useBottomToolsStore.getState().activeImage,
"ADD",
rect.data?.id
)
forceUpdate()
// 如果该类别存在子属性,则弹出子属性对话框
if (category && category.sub_attributes.length) {
const { top, left } = getShowContextMenuPosition(
e.point,
category,
-30,
-30
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: rect.data.imageId,
operationId: rect.data.operationId,
id: rect.data.id,
})
}
}
rect = null
}
rectangleTool.onKeyDown = (e: paper.KeyEvent) => {
if (e.key === "escape" || e.key === "alt") {
if (rect) {
rect.remove()
rect = null
}
} else if (e.key === "delete") {
handleDelete()
forceUpdate()
} else if (e.key === "shift") {
handleShift(true)
}
}
rectangleTool.onKeyUp = (e: paper.KeyEvent) => {
if (e.key === "shift") {
handleShift(false)
}
}
rectangleTool.onMouseMove = (e: paper.MouseEvent) => {
handleMouseMove(e)
}
set((state: PaperState) => ({
...state,
rectangleTool: rectangleTool,
}))
},
addBrush: (
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
points: paper.Point[] | undefined,
option: any,
data: any
) => {
let brush = renderPath(
usePaperStore.getState().group!,
Object.assign(
{ ...option, fillColor: undefined, closed: false },
{ segments: points, strokeScaling: false }
)
)
brush.data = Object.assign({ type: "brush", ...option }, data)
// points?.forEach((point) => {
// brush.add(point);
// });
brush.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event && e.event.button === 2) {
// console.log(e.event.clientX, e.event.clientY);
if (brush.data.id) {
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === brush.data.operationId
)
const { top, left } = getShowContextMenuPosition(
e.point,
category!,
5,
5
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: brush.data.imageId,
operationId: brush.data.operationId,
id: brush.data.id,
})
}
}
}
return brush
},
initBrushTool: (
paperBrushTool: paper.Tool,
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
renderCompoundPath: (
paperGroup: paper.Group,
option: any
) => paper.CompoundPath,
forceUpdate: DispatchWithoutAction
) => {
let brushCircles: paper.Path.Circle[] = []
let myPath: paper.Path | null
let compoundPolygon: paper.Path | paper.CompoundPath | null
let lastPoint: paper.Point
let points: paper.Point[] = []
let brushTool = paperBrushTool
const handleBrushIntersectRaster = (savePath: paper.Path) => {
let intersections = savePath.getIntersections(
usePaperStore.getState().rasterPath!
)
let validIntersections = intersections.filter((intersection) => {
// 检查交点是否在两个路径的可见部分
return (
savePath.contains(intersection.point) &&
usePaperStore.getState().rasterPath!.contains(intersection.point)
)
})
// 没有 validIntersections 不进行操作
if (validIntersections.length) {
// intersectionPath as paper.CompoundPath | paper.Path | null
let intersectionPath: any = savePath.intersect(
usePaperStore.getState().rasterPath!,
{
insert: false,
trace: false,
}
)
// 检测交点并处理
if (intersectionPath instanceof paper.CompoundPath) {
let new_segments: any[] = []
;(intersectionPath.children as paper.Path[]).forEach((child) => {
if (child.segments && child.segments.length) {
new_segments.push(...child.segments)
}
})
savePath.segments = new_segments
} else if (
intersectionPath.segments &&
intersectionPath.segments.length
) {
// intersectionPath是CompoundPath
savePath.segments = intersectionPath.segments
} else {
}
intersectionPath.remove()
intersectionPath = null
let oldCompoundPolygon = compoundPolygon
compoundPolygon = oldCompoundPolygon?.intersect(
usePaperStore.getState().rasterPath!
) as paper.Path
compoundPolygon.closed = false
oldCompoundPolygon?.remove()
} else {
let intersectionPath: paper.Path | null = savePath.intersect(
usePaperStore.getState().rasterPath!,
{
insert: false,
trace: false,
}
) as paper.Path
savePath.segments = intersectionPath.segments
intersectionPath.remove()
intersectionPath = null
}
}
brushTool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event.button !== 0) return
// 绘制时去除选中辅助
if (usePaperSupportStore.getState().selectedCircles.length) {
usePaperSupportStore.getState().clearAll()
}
// doubleclick
if (lastPoint && lastPoint.x === e.point.x && lastPoint.y === e.point.y) {
if (myPath) {
// 初始化 compoundPolygon
compoundPolygon = new paper.Path()
if (useTopToolsStore.getState().pressA)
compoundPolygon = myPath.clone()
// 去除超出图片边界的部分
handleBrushIntersectRaster(myPath)
// 去除超出图片边界的部分
// pressA 模式下添加线段
if (useTopToolsStore.getState().pressA) {
let ids =
useObjectStore.getState().selectedPath[
useBottomToolsStore.getState().activeImage
]
let path = usePaperStore.getState().getItemById(ids[0])
let paths =
usePaperStore
.getState()
.getItemsById(ids[0])
.filter((item) => item instanceof paper.Path && item.area) || []
// compoundPolygon === Path
let oldCompoundPolygon: any = compoundPolygon
let children: paper.Path[] = []
if (oldCompoundPolygon instanceof paper.CompoundPath) {
children = oldCompoundPolygon.children as paper.Path[]
} else if (oldCompoundPolygon instanceof paper.Path) {
children = [oldCompoundPolygon]
}
compoundPolygon = usePaperStore.getState().addCompoundPath(
renderCompoundPath,
[...paths!, ...children] as paper.Path[],
{
// option
...usePaperStore.getState().toolOption,
fillColor: undefined,
closed: false,
},
{ ...oldCompoundPolygon.data, id: path?.data.id, type: "brush" }
)
// oldCompoundPolygon?.remove();
oldCompoundPolygon.data.id = path?.data.id
}
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) =>
item.category_id.toString() ===
useTopToolsStore.getState().activeOperation
)
let label_class = category?.label_class || ""
let subAttr = Object.assign(
{},
useTopToolsStore.getState().subAttributes[label_class]
)
// 多线段存值
if (compoundPolygon instanceof paper.CompoundPath) {
if (compoundPolygon.children && compoundPolygon.children.length) {
let polygonPoints: any[] = []
let polygonHollowPoints: any[] = []
;(compoundPolygon.children as paper.Path[]).forEach((path) => {
let pathData = JSON.parse(path.exportJSON({ precision: 20 }))
// 在镂空或分割的区域中 路径方向是false
if (path.clockwise) {
polygonPoints.push(pathData[1]?.segments)
} else {
if (path.data.id && path.segments?.length) {
polygonHollowPoints.push(pathData[1]?.segments)
}
}
})
useLabelStore
.getState()
.setOperation(
useBottomToolsStore.getState().activeImage,
useTopToolsStore.getState().activeOperation,
[
useTopToolsStore.getState().pressA
? compoundPolygon.data.id
: myPath.data.id,
polygonPoints,
{
...initialDetail,
uid: usePermissionStore.getState().user_id,
comment: [
...useLabelStore.getState().labelDefaultComments,
],
create_timestamp: dayjs().unix(),
sub_attributes: subAttr,
image_id: useBottomToolsStore.getState().activeImageId,
category_id: Number(
useTopToolsStore.getState().activeOperation
),
},
polygonHollowPoints,
]
)
useRightToolsStore
.getState()
.pushPathId(
useTopToolsStore.getState().pressA
? compoundPolygon.data.id
: myPath.data.id
)
}
} else if (
compoundPolygon instanceof paper.Path &&
myPath.data.id &&
myPath.segments?.length
) {
let pathData = JSON.parse(myPath.exportJSON({ precision: 20 }))
useLabelStore
.getState()
.setOperation(
useBottomToolsStore.getState().activeImage,
useTopToolsStore.getState().activeOperation,
[
myPath.data.id,
// todo intersect+a | +a会变成CompoundPath 理论上已解决这个todo
[pathData[1]?.segments, ...[]],
{
...initialDetail,
uid: usePermissionStore.getState().user_id,
comment: [...useLabelStore.getState().labelDefaultComments],
create_timestamp: dayjs().unix(),
sub_attributes: subAttr,
image_id: useBottomToolsStore.getState().activeImageId,
category_id: Number(
useTopToolsStore.getState().activeOperation
),
},
[],
]
)
useRightToolsStore.getState().pushPathId(myPath.data.id)
}
// 如果该类别存在子属性,则弹出子属性对话框
if (category && category.sub_attributes.length) {
const { top, left } = getShowContextMenuPosition(
e.point,
category,
-30,
-30
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: myPath.data.imageId,
operationId: myPath.data.operationId,
id: myPath.data.id,
})
}
// 设置其为selectedPath
useObjectStore
.getState()
.updateSelectedPath(
useBottomToolsStore.getState().activeImage,
"ADD",
useTopToolsStore.getState().pressA
? compoundPolygon.data.id
: myPath.data.id
)
forceUpdate()
if (compoundPolygon instanceof paper.CompoundPath) {
myPath.remove()
myPath = null
points = []
} else {
compoundPolygon?.remove()
compoundPolygon = null
myPath = null
points = []
}
brushCircles.forEach((item) => {
item.remove()
})
useTopToolsStore.getState().setPressA(false)
return
}
} else {
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: false,
position: {
top: 0,
left: 0,
},
imageId: "",
operationId: "",
id: 0,
})
}
let temp = renderPath(usePaperStore.getState().group!, {})
let point = temp.globalToLocal(e.point)
// add point
points.push(point)
if (myPath) {
myPath.remove()
}
myPath = usePaperStore.getState().addBrush(
renderPath,
points,
{
// 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,
}
)
let circle = usePaperStore
.getState()
.getClickedCircle(
point,
brushCircles.length,
myPath?.data.id,
myPath?.data.blankColor
)
brushCircles.push(circle)
lastPoint = e.point
temp.remove()
}
brushTool.onMouseMove = (e: paper.MouseEvent) => {
handleMouseMove(e)
if (myPath) {
let temp = renderPath(usePaperStore.getState().group!, {})
let point = temp.globalToLocal(e.point)
myPath.remove()
myPath = usePaperStore.getState().addBrush(
renderPath,
points.concat(point),
{
// option
...usePaperStore.getState().toolOption,
fillColor: undefined,
closed: false,
},
{}
)
}
}
brushTool.onKeyDown = (e: paper.KeyEvent) => {
if (e.key === "escape" || e.key === "alt") {
if (myPath) {
brushCircles.forEach((item) => {
item.remove()
})
myPath.remove()
myPath = null
points = []
}
} else if (e.key === "delete") {
handleDelete()
forceUpdate()
} else if (e.key === "shift") {
handleShift(true)
}
}
brushTool.onKeyUp = (e: paper.KeyEvent) => {
if (e.key === "shift") {
handleShift(false)
}
}
set((state: PaperState) => ({
...state,
brushTool: brushTool,
}))
},
addPoint: (
renderCircle: (paperGroup: paper.Group, option: any) => paper.Path.Circle,
point: paper.Point | undefined,
option: any,
data: any
) => {
let myCircle = renderCircle(usePaperStore.getState().group!, {
center: point,
...option,
strokeScaling: false,
})
const currentScaling = usePaperStore.getState().group!.scaling.x
const newScale = useTopToolsStore.getState().scale / currentScaling
myCircle.scale(newScale / currentScaling)
myCircle.data = Object.assign({ type: "circle", ...option }, data)
myCircle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event && e.event.button === 2) {
// console.log(e.event.clientX, e.event.clientY);
if (myCircle.data.id) {
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) =>
item.category_id.toString() === myCircle.data.operationId
)
const { top, left } = getShowContextMenuPosition(
e.point,
category!,
5,
5
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: myCircle.data.imageId,
operationId: myCircle.data.operationId,
id: myCircle.data.id,
})
}
}
}
myCircle.onMouseEnter = (e: paper.MouseEvent) => {
if (usePaperStore.getState().mode === "pan") {
e.target.strokeWidth += 2
if (usePaperStore.getState().el)
usePaperStore.getState().el!.style.cursor = "move"
}
}
myCircle.onMouseLeave = (e: paper.MouseEvent) => {
if (usePaperStore.getState().mode === "pan") {
e.target.strokeWidth -= 2
if (usePaperStore.getState().el)
usePaperStore.getState().el!.style.cursor = "default"
}
}
return myCircle
},
addPointLine: (
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
points: paper.Point[] | undefined,
option: any,
data: any
) => {
let myCircleLine = renderPath(
usePaperStore.getState().group!,
Object.assign(
{
...option,
fillColor: undefined,
closed: false,
dashArray: [4, 4],
strokeScaling: false,
},
{ segments: points }
)
)
myCircleLine.data = Object.assign({ type: "point", ...option }, data)
return myCircleLine
},
initPointTool: (
paperPointTool: paper.Tool,
renderCircle: (paperGroup: paper.Group, option: any) => paper.Path.Circle,
renderPath: (paperGroup: paper.Group, option: any) => paper.Path,
forceUpdate: DispatchWithoutAction
) => {
let myCircle: paper.Path.Circle[] = []
let myCircleText: paper.PointText[] = []
let textNumbers: number[] = []
let myPath: paper.Path | null
let lastPoint: paper.Point
let points: paper.Point[] = []
let pointTool = paperPointTool
pointTool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event.button !== 0) return
// 绘制时去除选中辅助
if (usePaperSupportStore.getState().selectedCircles.length) {
usePaperSupportStore.getState().clearAll()
}
// doubleclick
if (lastPoint && lastPoint.x === e.point.x && lastPoint.y === e.point.y) {
if (myPath) {
// 去除超出图片边界的部分
myCircle = myCircle.filter((circle) => {
if (circle.data.id === myPath!.data.id) {
if (
usePaperStore
.getState()
.rasterPath!.bounds.contains(circle.bounds.center)
) {
return true
} else {
circle.remove()
return false
}
} else {
return false
}
})
myPath.segments = myPath.segments.filter((segment) => {
return usePaperStore
.getState()
.rasterPath!.bounds.contains(segment.point)
})
// 去除超出图片边界的部分 end
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) =>
item.category_id.toString() ===
useTopToolsStore.getState().activeOperation
)
let label_class = category?.label_class || ""
let subAttr = Object.assign(
{},
useTopToolsStore.getState().subAttributes[label_class]
)
// 存值
// let circles = usePaperStore.getState().group!.getItems({
// data: { type: "circle", id: myPath.data.id },
// });
let circles = myCircle.filter((item) => {
return item.data.id === myPath!.data.id
})
if (circles.length) {
let segments = circles.map((circle, circleIndex) => {
// 重新设置显示数字
circle.data.text = circleIndex + 1
return {
point: [circle.position.x, circle.position.y],
attributes: circle.data.attributes,
}
})
useLabelStore
.getState()
.setOperation(
useBottomToolsStore.getState().activeImage,
useTopToolsStore.getState().activeOperation,
[
myPath.data.id,
// 关键点 没有多区域
[segments.map((item) => item.point)],
{
...initialDetail,
uid: usePermissionStore.getState().user_id,
comment: [...useLabelStore.getState().labelDefaultComments],
create_timestamp: dayjs().unix(),
sub_attributes: subAttr,
image_id: useBottomToolsStore.getState().activeImageId,
category_id: Number(
useTopToolsStore.getState().activeOperation
),
circles: segments,
},
[],
]
)
// 如果该类别存在子属性,则弹出子属性对话框
if (category && category.sub_attributes.length) {
const { top, left } = getShowContextMenuPosition(
e.point,
category,
-30,
-30
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: myPath.data.imageId,
operationId: myPath.data.operationId,
id: myPath.data.id,
})
}
// 设置其为selectedPath
useObjectStore
.getState()
.updateSelectedPath(
useBottomToolsStore.getState().activeImage,
"ADD",
myPath.data.id
)
useRightToolsStore.getState().pushPathId(myPath.data.id)
forceUpdate()
}
myPath = null
points = []
textNumbers = []
myCircleText.forEach((item) => {
item.remove()
})
myCircleText = []
// 避免后续按键删除已绘制的circle
myCircle = []
return
}
} else {
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: false,
position: {
top: 0,
left: 0,
},
imageId: "",
operationId: "",
id: 0,
})
}
let temp = renderCircle(usePaperStore.getState().group!, {})
let point = temp.globalToLocal(e.point)
let text = textNumbers.reduce((a, b) => Math.max(a, b), 0) + 1
let circle = usePaperStore.getState().addPoint(
renderCircle,
point,
{
fillColor: null,
// center: point,
strokeColor: usePaperStore.getState().toolOption?.strokeColor,
radius: 3,
strokeScaling: false,
},
{
imageId: useBottomToolsStore.getState().activeImage,
operationId: useTopToolsStore.getState().activeOperation,
text: text,
id:
useRightToolsStore
.getState()
.pathIds.reduce((a, b) => Math.max(a, b), 0) + 1,
}
)
// add circle
myCircle.push(circle)
let pointText = new paper.PointText({
parent: usePaperStore.getState().group!,
point: point,
content: text.toString(),
justification: "right",
fillColor: usePaperStore.getState().toolOption?.strokeColor,
fontSize: 12,
})
const currentScaling = usePaperStore.getState().group!.scaling.x
const newScale = useTopToolsStore.getState().scale / currentScaling
pointText.scale(newScale / currentScaling)
textNumbers.push(text)
myCircleText.push(pointText)
// add point
points.push(point)
if (myPath) {
myPath.remove()
}
myPath = usePaperStore.getState().addPointLine(
renderPath,
points,
{
// 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,
}
)
lastPoint = e.point
// group.current?.addChild(myCircle);
temp.remove()
}
pointTool.onMouseMove = (e: paper.MouseEvent) => {
handleMouseMove(e)
if (myPath) {
let temp = renderPath(usePaperStore.getState().group!, {})
let point = temp.globalToLocal(e.point)
myPath.remove()
myPath = usePaperStore.getState().addPointLine(
renderPath,
points.concat(point),
{
// option
...usePaperStore.getState().toolOption,
fillColor: undefined,
closed: false,
},
{}
)
}
}
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 = []
}
} else if (e.key === "delete") {
handleDelete()
forceUpdate()
} else if (e.key === "shift") {
handleShift(true)
}
}
pointTool.onKeyUp = (e: paper.KeyEvent) => {
if (e.key === "shift") {
handleShift(false)
}
}
set((state: PaperState) => ({
...state,
pointTool: pointTool,
}))
},
getPointCircle: (
point: paper.Point,
index: number,
id: number,
color: any
) => {
let circle: paper.Path.Circle = new paper.Path.Circle(
Object.assign(
{ parent: usePaperStore.getState().group!, center: point },
{
fillColor: color,
// center: point,
strokeColor: usePaperStore.getState().toolOption?.strokeColor,
radius: 3,
strokeScaling: false,
},
{
data: {
type: "circle",
fillColor: color,
strokeColor: usePaperStore.getState().toolOption?.strokeColor,
imageId: useBottomToolsStore.getState().activeImage,
// bug
operationId: useTopToolsStore.getState().activeOperation,
text: +index + 1,
id: id,
},
}
)
)
const currentScaling = usePaperStore.getState().group!.scaling.x
const newScale = useTopToolsStore.getState().scale / currentScaling
circle.scale(newScale / currentScaling)
circle.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
if (e.event && e.event.button === 2) {
// console.log(e.event.clientX, e.event.clientY);
if (circle.data.id) {
let category = useTopToolsStore
.getState()
.objectOperations.find(
(item) => item.category_id.toString() === circle.data.operationId
)
const { top, left } = getShowContextMenuPosition(
e.point,
category!,
5,
5
)
useOtherToolsStore.getState().setShowContextMenu({
showContextMenu: true,
position: { top, left },
imageId: circle.data.imageId,
operationId: circle.data.operationId,
id: circle.data.id,
})
}
}
}
circle.onMouseEnter = (e: paper.MouseEvent) => {
if (usePaperStore.getState().mode === "pan") {
e.target.strokeWidth += 2
if (usePaperStore.getState().el)
usePaperStore.getState().el!.style.cursor = "move"
}
}
circle.onMouseLeave = (e: paper.MouseEvent) => {
if (usePaperStore.getState().mode === "pan") {
e.target.strokeWidth -= 2
if (usePaperStore.getState().el)
usePaperStore.getState().el!.style.cursor = "default"
}
}
return circle
},
initSupportTool: (tool, renderPath) => {
let points: Array<[number, number]> = []
let tags: number[] = []
tool.onMouseDown = (e: { event: MouseEvent; point: paper.Point }) => {
let point = usePaperStore.getState().group!.globalToLocal(e.point)
if (e.event.button === 1) return
points.push([point.x, point.y])
tags.push(e.event.button === 0 ? 1 : 0)
renderPath(points, tags, points.length === 1)
}
tool.onKeyDown = (e: paper.KeyEvent) => {
if (e.key === "escape") {
points = []
tags = []
clearSupportAnnotationItem()
usePaperStore.getState().setPaperMode("pan")
}
if (e.key === "f1") {
points = []
tags = []
}
}
set((state: PaperState) => ({
...state,
supportTool: tool,
}))
},
getClickedCircle: (
point: paper.Point,
index: number,
id: number,
color: any
) => {
const currentScaling = usePaperStore.getState().group!.scaling.x
const newScale = useTopToolsStore.getState().scale / currentScaling
let circle: paper.Path.Circle = new paper.Path.Circle(
Object.assign(
{ parent: usePaperStore.getState().group!, center: point },
{
fillColor: color,
// center: point,
strokeColor: "#FFF",
radius: 3,
strokeScaling: false,
},
{
data: { index, type: "pathCircle", id },
}
)
)
circle.scale(newScale / currentScaling)
circle.onMouseEnter = (_e: any) => {
circle.strokeWidth = 4
circle.fillColor = new paper.Color("#FFF")
}
circle.onMouseLeave = (_e: any) => {
circle.strokeWidth = 2
circle.fillColor = color
}
return circle
},
getBuffPath: (item: paper.Segment, index: number, id: number, color: any) => {
if (!item.next) return null
let bufferPath: paper.Path = new paper.Path(
Object.assign(
{
parent: usePaperStore.getState().group!,
segments: [item.point, item.next],
strokeColor: color, // 设置为透明,以便不可见
strokeScaling: false,
strokeWidth: 4, // 设置一个较大的strokeWidth作为缓冲区
strokeCap: "butt",
onSelect: function (event: { stopPropagation: () => void }) {
event.stopPropagation() // 阻止事件冒泡
},
},
{
data: { index, type: "pathBuff", id },
}
)
)
// 复制段的handleIn和handleOut到缓冲路径
// bufferPath.segments[0].handleIn = item.handleIn;
// bufferPath.segments[1].handleOut = item.handleOut;
let edge: paper.Path | null
// 鼠标移入时高亮显示
bufferPath.onMouseEnter = function (event: {
stopPropagation: () => void
}) {
if (!edge) {
edge = new paper.Path(
Object.assign(
{
parent: usePaperStore.getState().group!,
segments: [
bufferPath.segments[0].point,
bufferPath.segments[1].point,
],
strokeColor: "white", // 初始颜色与原始路径相同
strokeWidth: 2,
strokeScaling: false,
strokeCap: "butt",
},
{
data: { index, type: "pathBuff" },
}
)
)
// edge.bringToFront();
event.stopPropagation() // 阻止事件冒泡
}
}
// 鼠标移出时取消高亮
bufferPath.onMouseLeave = function (event: {
stopPropagation: () => void
}) {
edge?.remove()
edge = null
event.stopPropagation() // 阻止事件冒泡
}
return bufferPath
},
setPaperMode: (modeParam) => {
set((state: PaperState) => ({
...state,
mode: modeParam,
}))
switch (modeParam) {
case "pan":
usePaperStore.getState().panTool?.activate()
if (usePaperStore.getState().el) {
usePaperStore.getState().el!.style.cursor = "default"
}
break
case "polygon":
usePaperStore.getState().polygonTool?.activate()
if (usePaperStore.getState().el) {
usePaperStore.getState().el!.style.cursor = "crosshair"
}
break
case "rectangle":
usePaperStore.getState().rectangleTool?.activate()
if (usePaperStore.getState().el) {
usePaperStore.getState().el!.style.cursor = "crosshair"
}
break
case "brush":
usePaperStore.getState().brushTool?.activate()
if (usePaperStore.getState().el) {
usePaperStore.getState().el!.style.cursor = "crosshair"
}
break
case "point":
usePaperStore.getState().pointTool?.activate()
if (usePaperStore.getState().el) {
usePaperStore.getState().el!.style.cursor = "crosshair"
}
break
case "support":
usePaperStore.getState().supportTool?.activate()
if (usePaperStore.getState().el) {
usePaperStore.getState().el!.style.cursor = "crosshair"
}
break
default:
usePaperStore.getState().viewTool?.activate()
break
}
},
setGroupScale: (scale: number) => {
if (usePaperStore.getState().group) {
const currentScaling = usePaperStore.getState().group!.scaling.x
const newScale = scale / currentScaling
const point = usePositionStore.getState().currentMousePosition
if (point[0] !== 0 || point[1] !== 0)
usePaperStore.getState().group!.scale(newScale, new paper.Point(point))
else usePaperStore.getState().group!.scale(newScale)
let texts = usePaperStore.getState().group!.getItems({
data: { type: "text" },
}) as paper.Path[]
texts.forEach((text) => {
text.scale(1 / newScale)
})
let pathCircles = usePaperStore.getState().group!.getItems({
data: { type: "pathCircle" },
}) as paper.Path[]
pathCircles.forEach((pathCircle) => {
pathCircle.scale(1 / newScale)
})
let circles = usePaperStore.getState().group!.getItems({
data: { type: "circle" },
}) as paper.Path[]
circles.forEach((circle) => {
circle.scale(1 / newScale)
})
}
},
getAll: () => {
let polygon = usePaperStore.getState().group!.getItems({
data: { type: "polygon" },
}) as paper.Path[]
return polygon
},
getItemById: (id: number) => {
// let item = usePaperStore.getState().group!.getItems({
// id: id,
// }) as paper.Path[];
let savedItem: paper.Path[] = (
usePaperStore.getState().group!.getItems({
data: { id: id },
}) as paper.Path[]
).filter((item) =>
["rectangle", "polygon", "brush", "point"].includes(item.data.type)
)
if (savedItem?.[0]) {
return savedItem[0]
} else {
return null
}
},
getItemsById: (id: number) => {
// let item = usePaperStore.getState().group!.getItems({
// id: id,
// }) as paper.Path[];
let savedItem = usePaperStore.getState().group!.getItems({
data: { id: id },
}) as paper.Path[]
if (savedItem?.length) {
return savedItem
} else {
return []
}
},
}))