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 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((set) => ({ currentMousePosition: [0, 0], setCurrentMousePosition: (point) => set((state) => ({ ...state, currentMousePosition: point })), })) // store paper export const usePaperStore = create((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 [] } }, }))