From dbfa6652bf54eba141799681bf7eb1b74294c178 Mon Sep 17 00:00:00 2001 From: lijingchi Date: Mon, 6 Feb 2023 17:15:04 +0800 Subject: [PATCH] feat(pointcloud): Supports moving multiple selected rects --- .../toolOperation/pointCloud2dOperation.ts | 85 ++++++++- .../core/toolOperation/polygonOperation.ts | 172 +++++++++++------- .../hooks/usePointCloudViews.ts | 22 ++- 3 files changed, 203 insertions(+), 76 deletions(-) diff --git a/packages/lb-annotation/src/core/toolOperation/pointCloud2dOperation.ts b/packages/lb-annotation/src/core/toolOperation/pointCloud2dOperation.ts index 6ba9ac97c..6a948ad85 100644 --- a/packages/lb-annotation/src/core/toolOperation/pointCloud2dOperation.ts +++ b/packages/lb-annotation/src/core/toolOperation/pointCloud2dOperation.ts @@ -7,7 +7,7 @@ */ import { IPointCloudConfig, toolStyleConverter } from '@labelbee/lb-utils'; -import { ESortDirection } from '@/constant/annotation'; +import { EDragTarget, ESortDirection } from '@/constant/annotation'; import { EPolygonPattern } from '@/constant/tool'; import { IPolygonData, IPolygonPoint } from '@/types/tool/polygon'; import AxisUtils from '@/utils/tool/AxisUtils'; @@ -16,6 +16,7 @@ import DrawUtils from '@/utils/tool/DrawUtils'; import PolygonUtils from '@/utils/tool/PolygonUtils'; import { polygonConfig } from '@/constant/defaultConfig'; import PolygonOperation, { IPolygonOperationProps } from './polygonOperation'; +import { BasicToolOperation } from './basicToolOperation'; interface IPointCloud2dOperationProps { showDirectionLine?: boolean; @@ -51,13 +52,21 @@ class PointCloud2dOperation extends PolygonOperation { return this.selectedIDs; } + get enableDrag() { + return Boolean(this.selectedIDs.length > 0 && this.dragInfo); + } + /** * Update selectedIDs and rerender * @param selectedIDs */ public setSelectedIDs(selectedIDs: string[]) { this.selectedIDs = selectedIDs; - this.setSelectedID(this.selectedIDs.length === 1 ? this.selectedIDs[0] : ''); + + if (this.selectedIDs.length < 2) { + this.setSelectedID(this.selectedIDs.length === 1 ? this.selectedIDs[0] : ''); + } + this.render(); } @@ -162,7 +171,7 @@ class PointCloud2dOperation extends PolygonOperation { } public renderSingleSelectedPolygon = (selectedPolygon: IPolygonData) => { - if (this.selectedPolygons) { + if (selectedPolygon) { const color = this.getPointCloudLineColor(selectedPolygon.attribute); // const toolData = StyleUtils.getStrokeAndFill(toolColor, selectedPolygon.valid, { isSelected: true }); @@ -265,11 +274,7 @@ class PointCloud2dOperation extends PolygonOperation { this.setSelectedID(newID); } - /** - * Overwrite and prevent selectedChange emit - * @override - */ - public setSelectedID(newID?: string) { + public updateTextAttribute(newID?: string) { const oldID = this.selectedID; if (newID !== oldID && oldID) { // 触发文本切换的操作 @@ -280,7 +285,14 @@ class PointCloud2dOperation extends PolygonOperation { if (!newID) { this._textAttributInstance?.clearTextAttribute(); } + } + /** + * Overwrite and prevent selectedChange emit + * @override + */ + public setSelectedID(newID?: string) { + this.updateTextAttribute(newID); this.selectedID = newID; this.selectedIDs = newID ? [newID] : []; @@ -336,6 +348,63 @@ class PointCloud2dOperation extends PolygonOperation { this.emit('validUpdate', id); } + + public onDragMove(e: MouseEvent) { + const newPolygonList = this.polygonList.map((v) => { + if (this.selectedIDs.includes(v.id)) { + const selectedPointList = this.dragPolygon(e, v); + + if (!selectedPointList) { + return v; + } + + const newData = { + ...v, + pointList: selectedPointList as IPolygonPoint[], + }; + + // 非矩形模式下拖动,矩形模式下生成的框将会转换为非矩形框 + if (v.isRect === true && this.pattern === EPolygonPattern.Normal) { + Object.assign(newData, { isRect: false }); + } + + return newData; + } + + return v; + }); + + this.dragInfo!.dragPrevCoord = this.getCoordinateUnderZoom(e); + + this.setPolygonList(newPolygonList); + this.render(); + } + + public onMouseDown(e: MouseEvent) { + if ( + BasicToolOperation.prototype.onMouseDown.call(this, e) || + this.forbidMouseOperation || + e.ctrlKey === true || + e.button !== 0 + ) { + return; + } + + if (this.selectedIDs.length < 2) { + return super.onMouseDown(e); + } + + const dragStartCoord = this.getCoordinateUnderZoom(e); + + this.dragInfo = { + dragStartCoord, + dragTarget: EDragTarget.Plane, + initPointList: [], + changePointIndex: [0], + originPolygon: this.selectedPolygon, + dragPrevCoord: dragStartCoord, + }; + } } export default PointCloud2dOperation; diff --git a/packages/lb-annotation/src/core/toolOperation/polygonOperation.ts b/packages/lb-annotation/src/core/toolOperation/polygonOperation.ts index deec6d470..656633e4c 100644 --- a/packages/lb-annotation/src/core/toolOperation/polygonOperation.ts +++ b/packages/lb-annotation/src/core/toolOperation/polygonOperation.ts @@ -25,6 +25,7 @@ import StyleUtils from '../../utils/tool/StyleUtils'; import uuid from '../../utils/uuid'; import { BasicToolOperation, IBasicToolOperationProps } from './basicToolOperation'; import TextAttributeClass from './textAttributeClass'; +import _ from 'lodash'; const TEXT_MAX_WIDTH = 164; @@ -52,13 +53,14 @@ class PolygonOperation extends BasicToolOperation { public isCombined: boolean; // 是否开启合并操作 - private dragInfo?: { + public dragInfo?: { dragStartCoord: ICoordinate; initPointList: IPolygonPoint[]; changePointIndex?: number[]; // 用于存储拖拽点 / 边的下标 dragTarget: EDragTarget; originPolygon?: IPolygonData; // For comparing data before and after drag and drop. + dragPrevCoord: ICoordinate; }; private drawingHistory: ActionsHistory; // 用于正在编辑中的历史记录 @@ -118,6 +120,10 @@ class PolygonOperation extends BasicToolOperation { return this.polygonList.find((v) => v.id === this.hoverID && v.id !== this.selectedID); } + public get enableDrag() { + return Boolean(this.selectedID && this.dragInfo); + } + public get polygonListUnderZoom() { return this.polygonList.map((polygon) => ({ ...polygon, @@ -960,6 +966,7 @@ class PolygonOperation extends BasicToolOperation { initPointList, changePointIndex, originPolygon: this.selectedPolygon, + dragPrevCoord: dragStartCoord, }; return true; @@ -1148,24 +1155,16 @@ class PolygonOperation extends BasicToolOperation { return false; } - public onDragMove(e: MouseEvent) { - if (!this.dragInfo || !this.selectedID) { - return; - } - - const { selectedPolygon } = this; - let selectedPointList: IPolygonPoint[] | undefined = selectedPolygon?.pointList; - if (!selectedPointList) { - return; - } - - const { initPointList, dragStartCoord, dragTarget, changePointIndex } = this.dragInfo; + /** + * According to the mode of dragTarget, get the offset when dragging + * @param e {MouseEvent} + * @param selectedPolygon {IPolygonData} + * @returns + */ + public getDragOffset(e: MouseEvent, selectedPolygon: IPolygonData) { const coordinate = this.getCoordinateUnderZoom(e); - let offset = { - x: (coordinate.x - dragStartCoord.x) / this.zoom, - y: (coordinate.y - dragStartCoord.y) / this.zoom, - }; + const { dragTarget, dragPrevCoord, changePointIndex, initPointList, dragStartCoord } = this.dragInfo!; /** * 矩形拖动 @@ -1183,21 +1182,45 @@ class PolygonOperation extends BasicToolOperation { const secondPointIndex = MathUtils.getArrayIndex(changePointIndex[0] - 1, 4); const basicLine: [ICoordinate, ICoordinate] = [initPointList[firstPointIndex], initPointList[secondPointIndex]]; - offset = MathUtils.getRectPerpendicularOffset(dragStartCoord, coordinate, basicLine); - offset = { - x: offset.x / this.zoom, - y: offset.y / this.zoom, + const perpendicularOffset = MathUtils.getRectPerpendicularOffset(dragStartCoord, coordinate, basicLine); + return { + x: perpendicularOffset.x / this.zoom, + y: perpendicularOffset.y / this.zoom, }; } + if (this.dragInfo?.dragTarget === EDragTarget.Plane) { + return { + x: (coordinate.x - dragPrevCoord.x) / this.zoom, + y: (coordinate.y - dragPrevCoord.y) / this.zoom, + }; + } + + return { + x: (coordinate.x - dragStartCoord.x) / this.zoom, + y: (coordinate.y - dragStartCoord.y) / this.zoom, + }; + } + + public dragPolygon(e: MouseEvent, selectedPolygon: IPolygonData) { + let selectedPointList: IPolygonPoint[] | undefined = _.cloneDeep(selectedPolygon?.pointList); + + if (!selectedPointList || !this.dragInfo) { + return; + } + + const { initPointList, dragTarget, changePointIndex } = this.dragInfo!; + + const offset = this.getDragOffset(e, selectedPolygon); + this.dragStatus = EDragStatus.Move; switch (dragTarget) { case EDragTarget.Plane: selectedPointList = selectedPointList.map((v, i) => ({ ...v, - x: initPointList[i].x + offset.x, - y: initPointList[i].y + offset.y, + x: v.x + offset.x, + y: v.y + offset.y, })); break; @@ -1245,8 +1268,22 @@ class PolygonOperation extends BasicToolOperation { } } + return selectedPointList; + } + + /** + * Update polygon position while enableDrag is true + * @param e {MouseEvent} + */ + public onDragMove(e: MouseEvent) { const newPolygonList = this.polygonList.map((v) => { if (v.id === this.selectedID) { + const selectedPointList = this.dragPolygon(e, v); + + if (!selectedPointList) { + return v; + } + const newData = { ...v, pointList: selectedPointList as IPolygonPoint[], @@ -1263,6 +1300,8 @@ class PolygonOperation extends BasicToolOperation { return v; }); + this.dragInfo!.dragPrevCoord = this.getCoordinateUnderZoom(e); + this.setPolygonList(newPolygonList); this.render(); } @@ -1272,7 +1311,7 @@ class PolygonOperation extends BasicToolOperation { return; } - if (this.selectedID && this.dragInfo) { + if (this.enableDrag) { this.onDragMove(e); return; } @@ -1519,48 +1558,59 @@ class PolygonOperation extends BasicToolOperation { } } - public renderSelectedPolygon() { - // 3. 选中多边形的渲染 - if (this.selectedID) { - const selectdPolygon = this.selectedPolygon; + /** + * Filter selected polygon and render(Implement for multiple selected) + * @param selectedPolygon + */ + public renderSelectedPolygons() { + this.polygonList.forEach((polygon) => { + if (polygon.id === this.selectedID) { + this.renderSelectedPolygon(polygon); + } + }); + } - if (selectdPolygon) { - const toolColor = this.getColor(selectdPolygon.attribute); - const toolData = StyleUtils.getStrokeAndFill(toolColor, selectdPolygon.valid, { isSelected: true }); + /** + * Render selected polygon + * @param selectedPolygon + */ + public renderSelectedPolygon(selectedPolygon: IPolygonData) { + if (selectedPolygon) { + const toolColor = this.getColor(selectedPolygon.attribute); + const toolData = StyleUtils.getStrokeAndFill(toolColor, selectedPolygon.valid, { isSelected: true }); - DrawUtils.drawSelectedPolygonWithFillAndLine( - this.canvas, - AxisUtils.changePointListByZoom(selectdPolygon.pointList, this.zoom, this.currentPos), - { - fillColor: toolData.fill, - strokeColor: toolData.stroke, - pointColor: 'white', - thickness: 2, - lineCap: 'round', - isClose: true, - lineType: this.config?.lineType, - }, - ); + DrawUtils.drawSelectedPolygonWithFillAndLine( + this.canvas, + AxisUtils.changePointListByZoom(selectedPolygon.pointList, this.zoom, this.currentPos), + { + fillColor: toolData.fill, + strokeColor: toolData.stroke, + pointColor: 'white', + thickness: 2, + lineCap: 'round', + isClose: true, + lineType: this.config?.lineType, + }, + ); - let showText = `${ - AttributeUtils.getAttributeShowText(selectdPolygon.attribute, this.config.attributeList) ?? '' - }`; - if (this.config?.isShowOrder && selectdPolygon?.order > 0) { - showText = `${selectdPolygon.order} ${showText}`; - } + let showText = `${ + AttributeUtils.getAttributeShowText(selectedPolygon.attribute, this.config.attributeList) ?? '' + }`; + if (this.config?.isShowOrder && selectedPolygon?.order > 0) { + showText = `${selectedPolygon.order} ${showText}`; + } - DrawUtils.drawText( - this.canvas, - AxisUtils.changePointByZoom(selectdPolygon.pointList[0], this.zoom, this.currentPos), - showText, - { - color: toolData.stroke, - ...DEFAULT_TEXT_OFFSET, - }, - ); + DrawUtils.drawText( + this.canvas, + AxisUtils.changePointByZoom(selectedPolygon.pointList[0], this.zoom, this.currentPos), + showText, + { + color: toolData.stroke, + ...DEFAULT_TEXT_OFFSET, + }, + ); - this.renderTextAttribute(); - } + this.renderTextAttribute(); } } diff --git a/packages/lb-components/src/components/pointCloudView/hooks/usePointCloudViews.ts b/packages/lb-components/src/components/pointCloudView/hooks/usePointCloudViews.ts index c88fd7c37..f6898d7c8 100644 --- a/packages/lb-components/src/components/pointCloudView/hooks/usePointCloudViews.ts +++ b/packages/lb-components/src/components/pointCloudView/hooks/usePointCloudViews.ts @@ -3,7 +3,12 @@ * @author Glenfiddish * @createdate 2022-08-17 */ -import { PointCloudAnnotation, PointCloud, MathUtils, getCuboidFromPointCloudBox } from '@labelbee/lb-annotation'; +import { + PointCloudAnnotation, + PointCloud, + MathUtils, + getCuboidFromPointCloudBox, +} from '@labelbee/lb-annotation'; import { IPointCloudBox, EPerspectiveView, @@ -381,7 +386,7 @@ export const usePointCloudViews = () => { const newImgList = imgList as any[]; const extraData = { - attribute: topViewInstance.pointCloud2dOperation.defaultAttribute ?? '' + attribute: topViewInstance.pointCloud2dOperation.defaultAttribute ?? '', }; if (trackConfigurable === true) { @@ -428,12 +433,17 @@ export const usePointCloudViews = () => { const polygonOperation = topViewInstance?.pointCloud2dOperation; polygonOperation.setSelectedIDs(selectedIDs); - if (!boxParams || !polygonOperation) { + + if (selectedIDs.length === 0 || !polygonOperation) { return; } const polygon = polygonOperation.selectedPolygon; - syncPointCloudViews(PointCloudView.Top, polygon, boxParams); + + if (selectedIDs.length === 1 && boxParams) { + syncPointCloudViews(PointCloudView.Top, polygon, boxParams!); + return; + } }; /** @@ -453,8 +463,7 @@ export const usePointCloudViews = () => { // Update count if (mainViewInstance) { const { count } = mainViewInstance.getSensesPointZAxisInPolygon( - getCuboidFromPointCloudBox(newBoxParams) - .polygonPointList as IPolygonPoint[], + getCuboidFromPointCloudBox(newBoxParams).polygonPointList as IPolygonPoint[], [ newBoxParams.center.z - newBoxParams.depth / 2, newBoxParams.center.z + newBoxParams.depth / 2, @@ -518,7 +527,6 @@ export const usePointCloudViews = () => { */ const syncPointCloudViews = (omitView: string, polygon: any, boxParams: IPointCloudBox) => { const dataUrl = currentData?.url; - const viewToBeUpdated = { [PointCloudView.Side]: () => { synchronizeSideView(boxParams, polygon, sideViewInstance, dataUrl);