diff --git a/packages/lb-annotation/src/core/toolOperation/polygonOperation.ts b/packages/lb-annotation/src/core/toolOperation/polygonOperation.ts index a0605c551..c11c2f423 100644 --- a/packages/lb-annotation/src/core/toolOperation/polygonOperation.ts +++ b/packages/lb-annotation/src/core/toolOperation/polygonOperation.ts @@ -24,6 +24,7 @@ import StyleUtils from '../../utils/tool/StyleUtils'; import uuid from '../../utils/uuid'; import { BasicToolOperation, IBasicToolOperationProps } from './basicToolOperation'; import TextAttributeClass from './textAttributeClass'; +import { i18n } from '@labelbee/lb-utils'; const TEXT_MAX_WIDTH = 164; @@ -49,6 +50,8 @@ class PolygonOperation extends BasicToolOperation { public pattern: EPolygonPattern; // 当前多边形标注形式 + public isCombined: boolean; // 是否开启合并操作 + private dragInfo?: { dragStartCoord: ICoordinate; initPointList: IPolygonPoint[]; @@ -75,6 +78,7 @@ class PolygonOperation extends BasicToolOperation { this.drawingHistory = new ActionsHistory(); this.isCtrl = false; this.isAlt = false; + this.isCombined = false; this.pattern = EPolygonPattern.Normal; this.getCurrentSelectedData = this.getCurrentSelectedData.bind(this); @@ -711,6 +715,11 @@ class PolygonOperation extends BasicToolOperation { break; case EKeyCode.Z: + if (e.altKey) { + this.onCombinedExecute(); + return; + } + this.setIsHidden(!this.isHidden); this.render(); break; @@ -980,9 +989,63 @@ class PolygonOperation extends BasicToolOperation { newPolygonList = newPolygonList.filter((v) => v.id !== this.selectedID); } this.setPolygonList(newPolygonList); + this.history.pushHistory(newPolygonList); this.render(); } + public onCombinedExecute() { + if (!this.selectedID) { + this.emit('messageInfo', i18n.t('PolygonsToBeCombinedNeedToBeSelected')); + return; + } + this.isCombined = !this.isCombined; + } + + public combine(e: MouseEvent) { + // 没有选中和 hover 都退出 + const hoverID = this.getHoverID(e); + if (!hoverID || !this.selectedID || this.selectedID === hoverID) { + return; + } + + if (this.config?.lineType !== ELineTypes.Line) { + this.emit('messageInfo', i18n.t('CurveModeDoesNotSupportCutting')); + return; + } + + const selectedPolygon = this.polygonList.find((v) => v.id === this.selectedID); + const combinedPolygon = this.currentShowList.find((v) => v.id === hoverID); + if (!combinedPolygon || !selectedPolygon) { + return; + } + + const composeData = PolygonUtils.combinePolygonWithPolygon(selectedPolygon, combinedPolygon); + + if (!composeData) { + return; + } + + const { newPolygon, unionList } = composeData; + if (unionList.length === 1 && newPolygon) { + const newPolygonList = this.polygonList + .filter((v) => !unionList.includes(v.id)) + .map((v) => { + if (v.id === this.selectedID) { + return newPolygon; + } + + return v; + }); + this.setPolygonList(newPolygonList); + this.history.pushHistory(newPolygonList); + this.render(); + + this.emit('messageInfo', i18n.t('CombineSuccess')); + } else { + this.emit('messageInfo', i18n.t('CombiningFailedNotify')); + } + this.isCombined = false; + } /** * 判断是否在边界外 * @param selectedPointList @@ -1202,6 +1265,20 @@ class PolygonOperation extends BasicToolOperation { } public onMouseUp(e: MouseEvent) { + if (this.isCombined) { + switch (e.button) { + case 0: + this.combine(e); + break; + + case 2: + this.isCombined = false; + break; + } + + return; + } + if (super.onMouseUp(e) || this.forbidMouseOperation || !this.imgInfo) { return undefined; } @@ -1531,10 +1608,47 @@ class PolygonOperation extends BasicToolOperation { } super.render(); - this.renderCursorLine(this.getLineColor(this.defaultAttribute)); this.renderPolygon(); + this.renderCursorLine(this.getLineColor(this.defaultAttribute)); } + public renderCursorLine(lineColor: string) { + super.renderCursorLine(lineColor); + if (this.isCombined) { + const { x, y } = this.coord; + const padding = 10; // 框的边界 + const rectWidth = 186; // 框的宽度 + const rectHeight = 32; // 框的高度 + DrawUtils.drawRectWithFill( + this.canvas, + { + x: x + padding, + y: y - padding * 4 - 1, + width: rectWidth, + height: rectHeight, + } as IRect, + { color: 'black' }, + ); + + DrawUtils.drawText(this.canvas, { x, y }, i18n.t('ClickAnotherPolygon'), { + textAlign: 'center', + color: 'white', + offsetX: rectWidth / 2 + padding, + offsetY: -(rectHeight / 2 + padding / 2), + }); + + DrawUtils.drawRect( + this.canvas, + { + x: x - padding, + y: y - padding, + width: padding * 2, + height: padding * 2, + } as IRect, + { lineDash: [6], color: 'white' }, + ); + } + } /** 撤销 */ public undo() { if (this.drawingPointList.length > 0) { diff --git a/packages/lb-annotation/src/utils/tool/PolygonUtils.ts b/packages/lb-annotation/src/utils/tool/PolygonUtils.ts index 4cca75640..1a5dce0ed 100644 --- a/packages/lb-annotation/src/utils/tool/PolygonUtils.ts +++ b/packages/lb-annotation/src/utils/tool/PolygonUtils.ts @@ -5,12 +5,7 @@ import { ELineTypes, SEGMENT_NUMBER } from '../../constant/tool'; import AxisUtils from './AxisUtils'; import MathUtils from '../MathUtils'; import LineToolUtils from './LineToolUtils'; -import { difference, polygon } from '@turf/turf'; - -declare interface IAxis { - x: number; - y: number; -} +import { difference, polygon, union } from '@turf/turf'; export default class PolygonUtils { static getHoverPolygonID( @@ -649,7 +644,7 @@ export default class PolygonUtils { /** * 获取当前点与多边形点集最近的点,并返回 Index */ - public static getClosePointDistanceFromPolygon(point: IAxis, pointList: IPolygonPoint[]) { + public static getClosePointDistanceFromPolygon(point: ICoordinate, pointList: IPolygonPoint[]) { let minLen = Number.MAX_SAFE_INTEGER; let index = -1; @@ -663,4 +658,51 @@ export default class PolygonUtils { return index; } + + /** + * 多边形合成多边形 + * @param selectedPolygon + * @param combinedPolygon + */ + public static combinePolygonWithPolygon( + selectedPolygon: IPolygonData, + combinedPolygon: IPolygonData, + ): + | { + newPolygon: IPolygonData; + unionList: string[]; + } + | undefined { + try { + let turfSelectedPolygon = polygon([ + [...PolygonUtils.concatBeginAndEnd(selectedPolygon.pointList.map((v) => [v.x, v.y]))], + ]); + const turfCombinedPolygon = polygon([ + [...PolygonUtils.concatBeginAndEnd(combinedPolygon.pointList.map((v) => [v.x, v.y]))], + ]); + const unionPolygon = union(turfSelectedPolygon, turfCombinedPolygon); + const unionList: string[] = []; + let newPolygon = selectedPolygon; + if (unionPolygon?.geometry?.coordinates?.length === 1) { + unionList.push(combinedPolygon.id); + const pointList = unionPolygon?.geometry.coordinates.map((polygon) => { + // 多边形需要另外判断 + if (unionPolygon?.geometry?.type === 'MultiPolygon') { + //@ts-ignore + return polygon[0].reduce(PolygonUtils.deletePolygonLastPoint, []); + } + //@ts-ignore + return polygon.reduce(PolygonUtils.deletePolygonLastPoint, []); + })[0]; + newPolygon.pointList = pointList; + } + + return { + newPolygon, + unionList, + }; + } catch (e) { + console.error(e); + } + } } diff --git a/packages/lb-components/src/assets/annotation/toolHotKeyIcon/icon_polygonMerge_kj.svg b/packages/lb-components/src/assets/annotation/toolHotKeyIcon/icon_polygonMerge_kj.svg new file mode 100644 index 000000000..48120b4b6 --- /dev/null +++ b/packages/lb-components/src/assets/annotation/toolHotKeyIcon/icon_polygonMerge_kj.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/lb-components/src/views/MainView/toolFooter/FooterTips/ToolHotKey/polygon/index.tsx b/packages/lb-components/src/views/MainView/toolFooter/FooterTips/ToolHotKey/polygon/index.tsx index 15a4b52c7..bd30c0862 100644 --- a/packages/lb-components/src/views/MainView/toolFooter/FooterTips/ToolHotKey/polygon/index.tsx +++ b/packages/lb-components/src/views/MainView/toolFooter/FooterTips/ToolHotKey/polygon/index.tsx @@ -1,9 +1,9 @@ -import DrawPolygonSvg from '@/assets/annotation/toolHotKeyIcon/icon_line_kj.svg' -import DrawInvalidPolygonSvg from '@/assets/annotation/toolHotKeyIcon/icon_polygonNull_kj.svg' -import SelectedPolygonSvg from '@/assets/annotation/toolHotKeyIcon/icon_polygonActive_kj.svg' -import ChangePolygonAttribute from '@/assets/annotation/toolHotKeyIcon/icon_polygonChange_kj.svg' -import DeletePolygonSvg from '@/assets/annotation/toolHotKeyIcon/icon_polygonDel_kj.svg' -import MouseLeftSvg from '@/assets/annotation/toolHotKeyIcon/icon_mouse_left_kj.svg' +import DrawPolygonSvg from '@/assets/annotation/toolHotKeyIcon/icon_line_kj.svg'; +import DrawInvalidPolygonSvg from '@/assets/annotation/toolHotKeyIcon/icon_polygonNull_kj.svg'; +import SelectedPolygonSvg from '@/assets/annotation/toolHotKeyIcon/icon_polygonActive_kj.svg'; +import ChangePolygonAttribute from '@/assets/annotation/toolHotKeyIcon/icon_polygonChange_kj.svg'; +import DeletePolygonSvg from '@/assets/annotation/toolHotKeyIcon/icon_polygonDel_kj.svg'; +import MouseLeftSvg from '@/assets/annotation/toolHotKeyIcon/icon_mouse_left_kj.svg'; import MouseRightSvg from '@/assets/annotation/toolHotKeyIcon/icon_mouse_right_kj.svg'; import IconLineContKj from '@/assets/annotation/toolHotKeyIcon/icon_lineCont_kj.svg'; @@ -11,6 +11,7 @@ import IconPolygonInsertKj from '@/assets/annotation/toolHotKeyIcon/icon_polygon import IconUnGripKj from '@/assets/annotation/toolHotKeyIcon/icon_unGrip_kj.svg'; import IconPointSpecialKj from '@/assets/annotation/toolHotKeyIcon/icon_pointSpecial_kj.svg'; import IconSegment from '@/assets/annotation/toolHotKeyIcon/icon_segment.svg'; +import IconPolygonMerge from '@/assets/annotation/toolHotKeyIcon/icon_polygonMerge_kj.svg'; import IconAI from '@/assets/annotation/toolHotKeyIcon/icon_AI.svg'; import IconSwapOutlined from '@/assets/annotation/toolHotKeyIcon/icon_swap_outlined.svg'; @@ -32,7 +33,7 @@ import { // hidden, // changeSpecialLine, // saveResult, - dargWithLeftClick + dargWithLeftClick, } from '../common'; export const polygon = { @@ -107,6 +108,13 @@ export const splitPolygon = { shortCut: ['ALT', 'X'], }; +export const combinePolygon = { + name: 'CombineOverlapArea', + icon: IconPolygonMerge, + noticeInfo: '', + shortCut: ['Alt', 'Z'], +}; + export const segmentByAlgorithm = { name: 'SegmentationRecognition', icon: IconAI, @@ -152,6 +160,7 @@ const pointToolShortCutTable = [ tabChangeSelected, tabReverseChangeSelected, // changeRenderPattern, + combinePolygon, splitPolygon, ]; export default pointToolShortCutTable; diff --git a/packages/lb-utils/src/i18n/resources.json b/packages/lb-utils/src/i18n/resources.json index 0598366be..62cc0a91b 100644 --- a/packages/lb-utils/src/i18n/resources.json +++ b/packages/lb-utils/src/i18n/resources.json @@ -113,7 +113,13 @@ "imageInvalidAndSkip": "Image invalid, please skip", "videoErrorAndReload": "Failed to load video, please reload", "imageErrorAndReload": "Failed to load image, please reload", - "orMaskAsInvalid": " or mask it as invalid" + "orMaskAsInvalid": " or mask it as invalid", + "ClickAnotherPolygon": "Click another polygon", + "PolygonsToBeCombinedNeedToBeSelected": "Polygons to be combined need to be selected", + "CurveModeDoesNotSupportCutting": "Curve mode does not support cutting", + "CombineSuccess": "Combine success", + "CombiningFailedNotify": "Combining failed. Please check whether two polygons intersect or whether polygon combining forms a leak", + "CombineOverlapArea": "Combine Overlap Area" }, "cn": { "TextInput": "文本输入", @@ -229,6 +235,12 @@ "imageInvalidAndSkip": "无效图片,请跳过", "videoErrorAndReload": "视频加载失败, 请重新加载", "imageErrorAndReload": "图片加载失败, 请重新加载", - "orMaskAsInvalid": " 或 将其标为无效" + "orMaskAsInvalid": " 或 将其标为无效", + "ClickAnotherPolygon": "请点击希望合并的多边形", + "PolygonsToBeCombinedNeedToBeSelected": "需要选中需要合并的多边形", + "CurveModeDoesNotSupportCutting": "曲线模式暂不支持裁剪", + "CombineSuccess": "合并成功", + "CombiningFailedNotify": "合并失败,请检查是否两个多边形是否有相交,或多边形合并是否形成漏空", + "CombineOverlapArea": "合并重叠区域" } -} \ No newline at end of file +}