feat(shortcut): ,/
This commit is contained in:
@@ -12,6 +12,7 @@ import ScaleComponent from "./components/ScaleComponent"
|
||||
|
||||
import { Box, Button, Flex, LoadingOverlay, Stack } from "@mantine/core"
|
||||
import { notifications, showNotification } from "@mantine/notifications"
|
||||
import paper from "paper"
|
||||
import { getLabelResult, getServerImage } from "./api/label"
|
||||
import {
|
||||
Comment,
|
||||
@@ -56,8 +57,8 @@ import {
|
||||
import { useTopToolsStore } from "./useTopToolsStore"
|
||||
import { findGroupKey } from "./util"
|
||||
import { safeClone } from "./utils/clone"
|
||||
import { toggleObjectHideByShortcut } from "./utils/objectVisibility"
|
||||
import { labelTypeMap } from "./utils/constants"
|
||||
import { toggleObjectHideByShortcut } from "./utils/objectVisibility"
|
||||
|
||||
// Splitter component - will need custom implementation or alternative
|
||||
|
||||
@@ -1511,6 +1512,395 @@ const LabelPage = ({
|
||||
)
|
||||
}, [resolveSelectedObjectContext])
|
||||
|
||||
const rerenderActiveImageByLabel = useCallback(
|
||||
(
|
||||
imageId: string,
|
||||
imageLabel: Map<string, [number, any[], any, any[]][]>,
|
||||
selectedIds: number[]
|
||||
) => {
|
||||
const paperGroup = usePaperStore.getState().group
|
||||
if (paperGroup) {
|
||||
paperGroup
|
||||
.getItems({})
|
||||
.filter((item) => item.data.id)
|
||||
.forEach((item) => {
|
||||
item.remove()
|
||||
})
|
||||
|
||||
for (const [categoryId] of imageLabel.entries()) {
|
||||
paperContainerRef.current?.renderPolygons?.(categoryId, imageLabel)
|
||||
}
|
||||
}
|
||||
|
||||
useObjectStore.setState((state) => ({
|
||||
...state,
|
||||
selectedPath: {
|
||||
...state.selectedPath,
|
||||
[imageId]: selectedIds,
|
||||
},
|
||||
}))
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const removeObjectFromGroupMap = useCallback(
|
||||
(imageId: string, id: number) => {
|
||||
const nextPathGroupMap = safeClone(
|
||||
useRightToolsStore.getState().pathGroupMap
|
||||
)
|
||||
const imageGroupMap = nextPathGroupMap.get(imageId)
|
||||
if (!imageGroupMap) return
|
||||
|
||||
for (let [groupId, pathIds] of imageGroupMap.entries()) {
|
||||
const nextPathIds = pathIds.filter((pathId) => pathId !== id)
|
||||
if (nextPathIds.length > 1) {
|
||||
imageGroupMap.set(groupId, nextPathIds)
|
||||
} else {
|
||||
imageGroupMap.delete(groupId)
|
||||
}
|
||||
}
|
||||
|
||||
if (!imageGroupMap.size) nextPathGroupMap.delete(imageId)
|
||||
useRightToolsStore.getState().setPathGroupMap(nextPathGroupMap)
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const hasShapeSegments = useCallback((shape: any) => {
|
||||
if (!shape) return false
|
||||
if (shape instanceof paper.CompoundPath) {
|
||||
return (shape.children as paper.Path[]).some(
|
||||
(child) => child.segments?.length
|
||||
)
|
||||
}
|
||||
if (shape instanceof paper.Path) {
|
||||
return !!shape.segments?.length
|
||||
}
|
||||
return false
|
||||
}, [])
|
||||
|
||||
const createWorkingPolygonShape = useCallback((items: any[]) => {
|
||||
const compound = items.find((item) => item instanceof paper.CompoundPath)
|
||||
if (compound) {
|
||||
return (compound as paper.CompoundPath).clone({ insert: false }) as
|
||||
| paper.Path
|
||||
| paper.CompoundPath
|
||||
}
|
||||
|
||||
const paths = items.filter(
|
||||
(item) => item instanceof paper.Path
|
||||
) as paper.Path[]
|
||||
if (!paths.length) return null
|
||||
if (paths.length === 1) {
|
||||
return paths[0].clone({ insert: false }) as
|
||||
| paper.Path
|
||||
| paper.CompoundPath
|
||||
}
|
||||
|
||||
const merged = new paper.CompoundPath({ insert: false })
|
||||
paths.forEach((path) => {
|
||||
merged.addChild(path.clone({ insert: false }))
|
||||
})
|
||||
return merged as paper.Path | paper.CompoundPath
|
||||
}, [])
|
||||
|
||||
const collectPolygonPoints = useCallback(
|
||||
(shape: paper.Path | paper.CompoundPath) => {
|
||||
const polygonPoints: any[] = []
|
||||
const polygonHollowPoints: any[] = []
|
||||
|
||||
const collectPath = (path: paper.Path) => {
|
||||
if (!path.segments?.length) return
|
||||
const pathData = JSON.parse(path.exportJSON({ precision: 20 }))
|
||||
if (path.clockwise) {
|
||||
polygonPoints.push(pathData[1]?.segments)
|
||||
} else {
|
||||
polygonHollowPoints.push(pathData[1]?.segments)
|
||||
}
|
||||
}
|
||||
|
||||
if (shape instanceof paper.CompoundPath) {
|
||||
;(shape.children as paper.Path[]).forEach((path) => collectPath(path))
|
||||
} else {
|
||||
collectPath(shape)
|
||||
}
|
||||
|
||||
return { polygonPoints, polygonHollowPoints }
|
||||
},
|
||||
[]
|
||||
)
|
||||
|
||||
const applySelectedPolygonOverlap = useCallback(
|
||||
(mode: "subtract" | "unite") => {
|
||||
const selectedContext = resolveSelectedObjectContext()
|
||||
if (!selectedContext) {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
message: "请先选中单个对象",
|
||||
})
|
||||
return
|
||||
}
|
||||
if (selectedContext.objectType !== "polygon") {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
message:
|
||||
mode === "subtract"
|
||||
? "镂空快捷键仅支持 polygon 类型对象"
|
||||
: "融合快捷键仅支持 polygon/brush 类型对象",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const group = usePaperStore.getState().group
|
||||
if (!group) return
|
||||
|
||||
const targetItems = group
|
||||
.getItems({ data: { id: selectedContext.objectId } })
|
||||
.filter((item) => item.data?.type === "polygon")
|
||||
if (!targetItems.length) return
|
||||
|
||||
let workingShape = createWorkingPolygonShape(targetItems)
|
||||
if (!workingShape || !hasShapeSegments(workingShape)) return
|
||||
|
||||
const otherPolygonPaths: paper.Path[] = []
|
||||
group.children.forEach((item: any) => {
|
||||
if (
|
||||
!item.visible ||
|
||||
!item.data?.id ||
|
||||
item.data.id === selectedContext.objectId ||
|
||||
item.data.type !== "polygon"
|
||||
)
|
||||
return
|
||||
if (item instanceof paper.Path) {
|
||||
if (item.segments?.length) otherPolygonPaths.push(item)
|
||||
} else if (item instanceof paper.CompoundPath) {
|
||||
;(item.children as paper.Path[]).forEach((child) => {
|
||||
if (child.segments?.length) otherPolygonPaths.push(child)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
let changed = false
|
||||
|
||||
for (const eachPath of otherPolygonPaths) {
|
||||
if (!workingShape || !hasShapeSegments(workingShape)) break
|
||||
|
||||
const eachPathClone = eachPath.clone({ insert: false }) as paper.Path
|
||||
const intersectionPath = (workingShape as any).intersect(
|
||||
eachPathClone,
|
||||
{
|
||||
insert: false,
|
||||
}
|
||||
) as paper.Path | paper.CompoundPath
|
||||
const hasIntersection = hasShapeSegments(intersectionPath)
|
||||
intersectionPath?.remove()
|
||||
if (!hasIntersection) {
|
||||
eachPathClone.remove()
|
||||
continue
|
||||
}
|
||||
|
||||
const nextShape: paper.Path | paper.CompoundPath =
|
||||
mode === "subtract"
|
||||
? ((workingShape as any).subtract(eachPathClone, {
|
||||
insert: false,
|
||||
}) as paper.Path | paper.CompoundPath)
|
||||
: ((workingShape as any).unite(eachPathClone, {
|
||||
insert: false,
|
||||
}) as paper.Path | paper.CompoundPath)
|
||||
eachPathClone.remove()
|
||||
workingShape?.remove()
|
||||
workingShape = nextShape
|
||||
changed = true
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
workingShape?.remove()
|
||||
return
|
||||
}
|
||||
if (!workingShape) return
|
||||
|
||||
const raster = usePaperStore.getState().rasterPath
|
||||
if (raster && hasShapeSegments(workingShape)) {
|
||||
const clippedShape = (workingShape as any).intersect(raster, {
|
||||
insert: false,
|
||||
trace: false,
|
||||
}) as paper.Path | paper.CompoundPath
|
||||
workingShape?.remove()
|
||||
workingShape = clippedShape
|
||||
}
|
||||
if (!workingShape) return
|
||||
|
||||
const { polygonPoints, polygonHollowPoints } =
|
||||
collectPolygonPoints(workingShape)
|
||||
workingShape?.remove()
|
||||
|
||||
const nextLabel = safeClone(useLabelStore.getState().label)
|
||||
const imageLabel = nextLabel.get(selectedContext.imageId)
|
||||
if (!imageLabel) return
|
||||
const operationPaths = safeClone(
|
||||
imageLabel.get(selectedContext.operationId) || []
|
||||
)
|
||||
const selectedIndex = operationPaths.findIndex(
|
||||
(item) => item[0] === selectedContext.objectId
|
||||
)
|
||||
if (selectedIndex === -1) return
|
||||
|
||||
const currentTuple = operationPaths[selectedIndex]
|
||||
if (polygonPoints.length) {
|
||||
operationPaths[selectedIndex] = [
|
||||
selectedContext.objectId,
|
||||
polygonPoints,
|
||||
safeClone(currentTuple?.[2] || {}),
|
||||
polygonHollowPoints,
|
||||
]
|
||||
imageLabel.set(selectedContext.operationId, operationPaths)
|
||||
} else {
|
||||
imageLabel.set(
|
||||
selectedContext.operationId,
|
||||
operationPaths.filter((item) => item[0] !== selectedContext.objectId)
|
||||
)
|
||||
removeObjectFromGroupMap(
|
||||
selectedContext.imageId,
|
||||
selectedContext.objectId
|
||||
)
|
||||
}
|
||||
|
||||
useLabelStore.getState().setLabel(nextLabel)
|
||||
useLabelStore.getState().pushStateStack(nextLabel)
|
||||
|
||||
rerenderActiveImageByLabel(
|
||||
selectedContext.imageId,
|
||||
imageLabel,
|
||||
polygonPoints.length ? [selectedContext.objectId] : []
|
||||
)
|
||||
},
|
||||
[
|
||||
collectPolygonPoints,
|
||||
createWorkingPolygonShape,
|
||||
hasShapeSegments,
|
||||
removeObjectFromGroupMap,
|
||||
rerenderActiveImageByLabel,
|
||||
resolveSelectedObjectContext,
|
||||
]
|
||||
)
|
||||
|
||||
const applySelectedBrushIntersectUnite = useCallback(() => {
|
||||
const selectedContext = resolveSelectedObjectContext()
|
||||
if (!selectedContext) {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
message: "请先选中单个对象",
|
||||
})
|
||||
return
|
||||
}
|
||||
if (selectedContext.objectType !== "brush") {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
message: "融合快捷键仅支持 polygon/brush 类型对象",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const group = usePaperStore.getState().group
|
||||
if (!group) return
|
||||
|
||||
const selectedBrushPaths = group
|
||||
.getItems({ data: { id: selectedContext.objectId } })
|
||||
.filter((item) => item.data?.type === "brush")
|
||||
.flatMap((item) => {
|
||||
if (item instanceof paper.CompoundPath)
|
||||
return item.children as paper.Path[]
|
||||
if (item instanceof paper.Path) return [item]
|
||||
return []
|
||||
})
|
||||
.filter((path) => path.segments?.length)
|
||||
|
||||
if (!selectedBrushPaths.length) return
|
||||
|
||||
const otherBrushPathMap = new Map<number, paper.Path[]>()
|
||||
group.children.forEach((item: any) => {
|
||||
if (
|
||||
!item.visible ||
|
||||
!item.data?.id ||
|
||||
item.data.id === selectedContext.objectId ||
|
||||
item.data.type !== "brush"
|
||||
)
|
||||
return
|
||||
|
||||
const targetId = Number(item.data.id)
|
||||
const currentPaths = otherBrushPathMap.get(targetId) || []
|
||||
if (item instanceof paper.CompoundPath) {
|
||||
otherBrushPathMap.set(targetId, [
|
||||
...currentPaths,
|
||||
...(item.children as paper.Path[]),
|
||||
])
|
||||
} else if (item instanceof paper.Path) {
|
||||
otherBrushPathMap.set(targetId, [...currentPaths, item])
|
||||
}
|
||||
})
|
||||
|
||||
const intersectingIds = new Set<number>()
|
||||
for (const [otherId, paths] of otherBrushPathMap.entries()) {
|
||||
const hasIntersection = paths.some((otherPath) => {
|
||||
return selectedBrushPaths.some((selectedPath) => {
|
||||
if (!selectedPath.bounds.intersects(otherPath.bounds)) return false
|
||||
return selectedPath.getIntersections(otherPath).length > 0
|
||||
})
|
||||
})
|
||||
if (hasIntersection) intersectingIds.add(otherId)
|
||||
}
|
||||
if (!intersectingIds.size) return
|
||||
|
||||
const nextLabel = safeClone(useLabelStore.getState().label)
|
||||
const imageLabel = nextLabel.get(selectedContext.imageId)
|
||||
if (!imageLabel) return
|
||||
|
||||
const selectedOperationPaths = safeClone(
|
||||
imageLabel.get(selectedContext.operationId) || []
|
||||
)
|
||||
const selectedIndex = selectedOperationPaths.findIndex(
|
||||
(item) => item[0] === selectedContext.objectId
|
||||
)
|
||||
if (selectedIndex === -1) return
|
||||
|
||||
const selectedTuple = selectedOperationPaths[selectedIndex]
|
||||
const mergedPoints = safeClone(selectedTuple?.[1] || [])
|
||||
const pointSet = new Set(
|
||||
mergedPoints.map((pointItem) => JSON.stringify(pointItem))
|
||||
)
|
||||
|
||||
let appendedCount = 0
|
||||
imageLabel.forEach((paths) => {
|
||||
paths.forEach((item) => {
|
||||
if (!intersectingIds.has(item[0])) return
|
||||
;(item[1] || []).forEach((pathPoints: any) => {
|
||||
const key = JSON.stringify(pathPoints)
|
||||
if (pointSet.has(key)) return
|
||||
pointSet.add(key)
|
||||
mergedPoints.push(safeClone(pathPoints))
|
||||
appendedCount++
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
if (!appendedCount) return
|
||||
|
||||
selectedOperationPaths[selectedIndex] = [
|
||||
selectedContext.objectId,
|
||||
mergedPoints,
|
||||
safeClone(selectedTuple?.[2] || {}),
|
||||
safeClone(selectedTuple?.[3] || []),
|
||||
]
|
||||
imageLabel.set(selectedContext.operationId, selectedOperationPaths)
|
||||
|
||||
useLabelStore.getState().setLabel(nextLabel)
|
||||
useLabelStore.getState().pushStateStack(nextLabel)
|
||||
|
||||
rerenderActiveImageByLabel(selectedContext.imageId, imageLabel, [
|
||||
selectedContext.objectId,
|
||||
])
|
||||
}, [rerenderActiveImageByLabel, resolveSelectedObjectContext])
|
||||
|
||||
const handleMergeSelectedObjects = useCallback(() => {
|
||||
const activeImage = useBottomToolsStore.getState().activeImage
|
||||
const selectedIds =
|
||||
@@ -1655,7 +2045,7 @@ const LabelPage = ({
|
||||
const mode = usePaperStore.getState().mode
|
||||
const lowerKey = e.key.toLowerCase()
|
||||
|
||||
if (e.repeat && ["a", "d", "h"].includes(lowerKey)) return
|
||||
if (e.repeat && ["a", "d", "h", ",", "."].includes(lowerKey)) return
|
||||
|
||||
if (!e.ctrlKey && !e.metaKey && !e.altKey && lowerKey === "h") {
|
||||
e.preventDefault()
|
||||
@@ -1675,6 +2065,47 @@ const LabelPage = ({
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!e.ctrlKey &&
|
||||
!e.metaKey &&
|
||||
!e.altKey &&
|
||||
[",", ","].includes(e.key)
|
||||
) {
|
||||
e.preventDefault()
|
||||
applySelectedPolygonOverlap("subtract")
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
!e.ctrlKey &&
|
||||
!e.metaKey &&
|
||||
!e.altKey &&
|
||||
[".", "。"].includes(e.key)
|
||||
) {
|
||||
e.preventDefault()
|
||||
const selectedContext = resolveSelectedObjectContext()
|
||||
if (!selectedContext) {
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
message: "请先选中单个对象",
|
||||
})
|
||||
return
|
||||
}
|
||||
if (selectedContext.objectType === "polygon") {
|
||||
applySelectedPolygonOverlap("unite")
|
||||
return
|
||||
}
|
||||
if (selectedContext.objectType === "brush") {
|
||||
applySelectedBrushIntersectUnite()
|
||||
return
|
||||
}
|
||||
notifications.show({
|
||||
color: "yellow",
|
||||
message: "融合快捷键仅支持 polygon/brush 类型对象",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (e.altKey && e.key.toLowerCase() === "x") {
|
||||
e.preventDefault()
|
||||
if (!isView) topRef.current?.commitTaskByActionKey?.()
|
||||
@@ -1896,6 +2327,8 @@ const LabelPage = ({
|
||||
element?.removeEventListener("mousemove", mousemove)
|
||||
}
|
||||
}, [
|
||||
applySelectedBrushIntersectUnite,
|
||||
applySelectedPolygonOverlap,
|
||||
editMode,
|
||||
enterSelectedObjectDrawMode,
|
||||
focusInput,
|
||||
@@ -1911,6 +2344,7 @@ const LabelPage = ({
|
||||
setPressA,
|
||||
setScale,
|
||||
setShift,
|
||||
resolveSelectedObjectContext,
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -2866,6 +2866,96 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
return nextData
|
||||
}
|
||||
|
||||
const getSegmentPoint = (segment: any): [number, number] | null => {
|
||||
if (
|
||||
Array.isArray(segment) &&
|
||||
segment.length >= 2 &&
|
||||
typeof segment[0] === "number" &&
|
||||
typeof segment[1] === "number"
|
||||
) {
|
||||
return [segment[0], segment[1]]
|
||||
}
|
||||
if (
|
||||
Array.isArray(segment) &&
|
||||
Array.isArray(segment[0]) &&
|
||||
segment[0].length >= 2 &&
|
||||
typeof segment[0][0] === "number" &&
|
||||
typeof segment[0][1] === "number"
|
||||
) {
|
||||
return [segment[0][0], segment[0][1]]
|
||||
}
|
||||
if (
|
||||
segment?.point &&
|
||||
typeof segment.point.x === "number" &&
|
||||
typeof segment.point.y === "number"
|
||||
) {
|
||||
return [segment.point.x, segment.point.y]
|
||||
}
|
||||
if (
|
||||
segment?.point &&
|
||||
Array.isArray(segment.point) &&
|
||||
segment.point.length >= 2 &&
|
||||
typeof segment.point[0] === "number" &&
|
||||
typeof segment.point[1] === "number"
|
||||
) {
|
||||
return [segment.point[0], segment.point[1]]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const normalizePolygonFromSegments = (segments: any[]) => {
|
||||
const polygon = (segments || [])
|
||||
.map((segment) => getSegmentPoint(segment))
|
||||
.filter((point): point is [number, number] => !!point)
|
||||
|
||||
if (polygon.length > 1) {
|
||||
const [firstX, firstY] = polygon[0]
|
||||
const [lastX, lastY] = polygon[polygon.length - 1]
|
||||
if (
|
||||
Math.abs(firstX - lastX) < 0.000001 &&
|
||||
Math.abs(firstY - lastY) < 0.000001
|
||||
) {
|
||||
polygon.pop()
|
||||
}
|
||||
}
|
||||
return polygon
|
||||
}
|
||||
|
||||
const isPointInPolygon = (
|
||||
point: [number, number],
|
||||
polygon: [number, number][]
|
||||
) => {
|
||||
if (polygon.length < 3) return false
|
||||
const [x, y] = point
|
||||
let inside = false
|
||||
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||
const [xi, yi] = polygon[i]
|
||||
const [xj, yj] = polygon[j]
|
||||
const dy = yj - yi
|
||||
if (yi > y !== yj > y) {
|
||||
const crossX = ((xj - xi) * (y - yi)) / (dy || 0.000001) + xi
|
||||
if (x < crossX) inside = !inside
|
||||
}
|
||||
}
|
||||
return inside
|
||||
}
|
||||
|
||||
const getHollowFlagsByContainment = (allSegments: any[][]) => {
|
||||
const polygons = allSegments.map((segments) =>
|
||||
normalizePolygonFromSegments(segments)
|
||||
)
|
||||
return polygons.map((polygon, index) => {
|
||||
if (polygon.length < 3) return false
|
||||
const samplePoint = polygon[0]
|
||||
let containDepth = 0
|
||||
polygons.forEach((otherPolygon, otherIndex) => {
|
||||
if (otherIndex === index || otherPolygon.length < 3) return
|
||||
if (isPointInPolygon(samplePoint, otherPolygon)) containDepth += 1
|
||||
})
|
||||
return containDepth % 2 === 1
|
||||
})
|
||||
}
|
||||
|
||||
const handleSelectedPolygonSubtract = (draftPath: paper.Path) => {
|
||||
const activeImage = useBottomToolsStore.getState().activeImage
|
||||
const selectedIds =
|
||||
@@ -2915,36 +3005,37 @@ export const usePaperStore = create<PaperState>((set) => ({
|
||||
clippedDraft = null
|
||||
targetShape.remove()
|
||||
|
||||
if (nextPolygon instanceof paper.CompoundPath) {
|
||||
nextPolygon.data = Object.assign({}, targetData)
|
||||
;(nextPolygon.children as paper.Path[]).forEach((child) => {
|
||||
child.data = Object.assign({}, targetData, {
|
||||
isHollowPolygon: !child.clockwise,
|
||||
})
|
||||
})
|
||||
} else {
|
||||
nextPolygon.data = Object.assign({}, targetData)
|
||||
}
|
||||
|
||||
const points: any[] = []
|
||||
const hollowPoints: any[] = []
|
||||
const collectPathPoints = (path: paper.Path) => {
|
||||
if (!path.segments?.length) return
|
||||
const nextPaths =
|
||||
nextPolygon instanceof paper.CompoundPath
|
||||
? (nextPolygon.children as paper.Path[])
|
||||
: ([nextPolygon] as paper.Path[])
|
||||
const nextPathSegments = nextPaths.map((path) => {
|
||||
if (!path.segments?.length) return null
|
||||
const pathData = JSON.parse(path.exportJSON({ precision: 20 }))
|
||||
if (path.clockwise) {
|
||||
points.push(pathData[1]?.segments)
|
||||
} else {
|
||||
hollowPoints.push(pathData[1]?.segments)
|
||||
}
|
||||
}
|
||||
return pathData?.[1]?.segments || null
|
||||
})
|
||||
const hollowFlags = getHollowFlagsByContainment(
|
||||
nextPathSegments.map((segments) => segments || [])
|
||||
)
|
||||
|
||||
if (nextPolygon instanceof paper.CompoundPath) {
|
||||
;(nextPolygon.children as paper.Path[]).forEach((child) => {
|
||||
collectPathPoints(child)
|
||||
})
|
||||
} else {
|
||||
collectPathPoints(nextPolygon)
|
||||
nextPolygon.data = Object.assign({}, targetData)
|
||||
}
|
||||
nextPaths.forEach((path, index) => {
|
||||
const segments = nextPathSegments[index]
|
||||
if (!segments?.length) return
|
||||
const isHollowPolygon = !!hollowFlags[index]
|
||||
path.data = Object.assign({}, targetData, {
|
||||
isHollowPolygon,
|
||||
})
|
||||
if (isHollowPolygon) {
|
||||
hollowPoints.push(segments)
|
||||
} else {
|
||||
points.push(segments)
|
||||
}
|
||||
})
|
||||
|
||||
const operationId = (
|
||||
targetData.operationId ||
|
||||
|
||||
Reference in New Issue
Block a user