3D annotation view significant performance update & refactoring (#5442)

<!-- Raised an issue to propose your change
(https://github.com/cvat-ai/cvat/issues).
It helps to avoid duplication of efforts from multiple independent
contributors.
Discuss your ideas with maintainers to be sure that changes will be
approved and merged.
Read the
[CONTRIBUTION](https://github.com/cvat-ai/cvat/blob/develop/CONTRIBUTING.md)
guide. -->

<!-- Provide a general summary of your changes in the Title above -->

### Motivation and context
<!-- Why is this change required? What problem does it solve? If it
fixes an open
issue, please link to the issue here. Describe your changes in detail,
add
screenshots. -->

### How has this been tested?
<!-- Please describe in detail how you tested your changes.
Include details of your testing environment, and the tests you ran to
see how your change affects other areas of the code, etc. -->

### Checklist
<!-- Go over all the following points, and put an `x` in all the boxes
that apply.
If an item isn't applicable by a reason then ~~explicitly
strikethrough~~ the whole
line. If you don't do that github will show an incorrect process for the
pull request.
If you're unsure about any of these, don't hesitate to ask. We're here
to help! -->
- [x] I submit my changes into the `develop` branch
- [ ] I have added a description of my changes into
[CHANGELOG](https://github.com/cvat-ai/cvat/blob/develop/CHANGELOG.md)
file
- [ ] I have updated the [documentation](
https://github.com/cvat-ai/cvat/blob/develop/README.md#documentation)
accordingly
- [ ] I have added tests to cover my changes
- [ ] I have linked related issues ([read github docs](

https://help.github.com/en/github/managing-your-work-on-github/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword))
- [x] I have increased versions of npm packages if it is necessary
([cvat-canvas](https://github.com/cvat-ai/cvat/tree/develop/cvat-canvas#versioning),

[cvat-core](https://github.com/cvat-ai/cvat/tree/develop/cvat-core#versioning),
[cvat-data](https://github.com/cvat-ai/cvat/tree/develop/cvat-data#versioning)
and
[cvat-ui](https://github.com/cvat-ai/cvat/tree/develop/cvat-ui#versioning))

### License

- [x] I submit _my code changes_ under the same [MIT License](
https://github.com/cvat-ai/cvat/blob/develop/LICENSE) that covers the
project.
  Feel free to contact the maintainers if that's a concern.
main
Boris Sekachev 3 years ago committed by GitHub
parent ac78fab0b9
commit 6be7e4cceb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
{
"name": "cvat-canvas3d",
"version": "0.0.5",
"version": "0.0.6",
"description": "Part of Computer Vision Annotation Tool which presents its canvas3D library",
"main": "src/canvas3d.ts",
"scripts": {
@ -17,6 +17,7 @@
],
"devDependencies": {},
"dependencies": {
"cvat-core": "link:./../cvat-core",
"@types/three": "^0.125.3",
"camera-controls": "^1.25.3",
"three": "^0.126.1"

@ -3,6 +3,7 @@
//
// SPDX-License-Identifier: MIT
import { ObjectState } from '.';
import {
Canvas3dModel, Mode, DrawData, ActiveElement, GroupData, Configuration,
} from './canvas3dModel';
@ -10,10 +11,10 @@ import {
export interface Canvas3dController {
readonly drawData: DrawData;
readonly activeElement: ActiveElement;
readonly selected: any;
readonly groupData: GroupData;
readonly configuration: Configuration;
readonly imageIsDeleted: boolean;
readonly objects: ObjectState[];
mode: Mode;
group(groupData: GroupData): void;
}
@ -41,10 +42,6 @@ export class Canvas3dControllerImpl implements Canvas3dController {
return this.model.data.activeElement;
}
public get selected(): any {
return this.model.data.selected;
}
public get imageIsDeleted(): any {
return this.model.imageIsDeleted;
}
@ -57,6 +54,10 @@ export class Canvas3dControllerImpl implements Canvas3dController {
return this.model.configuration;
}
public get objects(): ObjectState[] {
return this.model.objects;
}
public group(groupData: GroupData): void {
this.model.group(groupData);
}

@ -3,6 +3,7 @@
//
// SPDX-License-Identifier: MIT
import { ObjectState } from '.';
import { MasterImpl } from './master';
export interface Size {
@ -17,7 +18,7 @@ export interface ActiveElement {
export interface GroupData {
enabled: boolean;
grouped?: [];
grouped: ObjectState[];
}
export interface Configuration {
@ -76,24 +77,20 @@ export enum UpdateReasons {
DRAW = 'draw',
SELECT = 'select',
CANCEL = 'cancel',
DATA_FAILED = 'data_failed',
DRAG_CANVAS = 'drag_canvas',
SHAPE_ACTIVATED = 'shape_activated',
GROUP = 'group',
FITTED_CANVAS = 'fitted_canvas',
CONFIG_UPDATED = 'config_updated',
SHAPES_CONFIG_UPDATED = 'shapes_config_updated',
}
export enum Mode {
IDLE = 'idle',
DRAG = 'drag',
RESIZE = 'resize',
DRAW = 'draw',
EDIT = 'edit',
INTERACT = 'interact',
DRAG_CANVAS = 'drag_canvas',
GROUP = 'group',
BUSY = 'busy',
}
export interface Canvas3dDataModel {
@ -106,13 +103,15 @@ export interface Canvas3dDataModel {
imageIsDeleted: boolean;
drawData: DrawData;
mode: Mode;
exception: Error | null;
objects: any[];
groupedObjects: any[];
selected: any;
objects: ObjectState[];
shapeProperties: ShapeProperties;
groupData: GroupData;
configuration: Configuration;
isFrameUpdating: boolean;
nextSetupRequest: {
frameData: any;
objectStates: ObjectState[];
} | null;
}
export interface Canvas3dModel {
@ -121,17 +120,20 @@ export interface Canvas3dModel {
readonly imageIsDeleted: boolean;
readonly groupData: GroupData;
readonly configuration: Configuration;
setup(frameData: any, objectStates: any[]): void;
readonly objects: ObjectState[];
setup(frameData: any, objectStates: ObjectState[]): void;
isAbleToChangeFrame(): boolean;
draw(drawData: DrawData): void;
cancel(): void;
dragCanvas(enable: boolean): void;
activate(clientID: string | null, attributeID: number | null): void;
configureShapes(shapeProperties: any): void;
configureShapes(shapeProperties: ShapeProperties): void;
configure(configuration: Configuration): void;
fit(): void;
group(groupData: GroupData): void;
destroy(): void;
updateCanvasObjects(): void;
unlockFrameUpdating(): void;
}
export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
@ -149,7 +151,6 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
width: 0,
},
objects: [],
groupedObjects: [],
image: null,
imageID: null,
imageOffset: 0,
@ -163,12 +164,10 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
initialState: null,
},
mode: Mode.IDLE,
exception: null,
groupData: {
enabled: false,
grouped: [],
},
selected: null,
shapeProperties: {
opacity: 40,
outlined: false,
@ -179,16 +178,38 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
configuration: {
resetZoom: false,
},
isFrameUpdating: false,
nextSetupRequest: null,
};
}
public setup(frameData: any, objectStates: any[]): void {
public updateCanvasObjects(): void {
this.notify(UpdateReasons.OBJECTS_UPDATED);
}
public unlockFrameUpdating(): void {
this.data.isFrameUpdating = false;
if (this.data.nextSetupRequest) {
try {
const { frameData, objectStates } = this.data.nextSetupRequest;
this.setup(frameData, objectStates);
} finally {
this.data.nextSetupRequest = null;
}
}
}
public setup(frameData: any, objectStates: ObjectState[]): void {
if (this.data.imageID !== frameData.number) {
if ([Mode.EDIT, Mode.DRAG, Mode.RESIZE].includes(this.data.mode)) {
if ([Mode.EDIT].includes(this.data.mode)) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`);
}
}
if ([Mode.EDIT, Mode.BUSY].includes(this.data.mode)) {
if (this.data.isFrameUpdating) {
this.data.nextSetupRequest = {
frameData, objectStates,
};
return;
}
@ -198,6 +219,7 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
return;
}
this.data.isFrameUpdating = true;
this.data.imageID = frameData.number;
frameData
.data((): void => {
@ -205,24 +227,17 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
this.notify(UpdateReasons.IMAGE_CHANGED);
})
.then((data: Image): void => {
if (frameData.number !== this.data.imageID) {
// already another image
return;
}
this.data.imageSize = {
height: frameData.height as number,
width: frameData.width as number,
};
this.data.imageIsDeleted = frameData.deleted;
this.data.image = data;
this.notify(UpdateReasons.IMAGE_CHANGED);
this.data.objects = objectStates;
this.notify(UpdateReasons.OBJECTS_UPDATED);
this.notify(UpdateReasons.IMAGE_CHANGED);
})
.catch((exception: any): void => {
this.data.exception = exception;
this.notify(UpdateReasons.DATA_FAILED);
this.data.isFrameUpdating = false;
throw exception;
});
}
@ -235,9 +250,13 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
return this.data.mode;
}
public get objects(): ObjectState[] {
return [...this.data.objects];
}
public isAbleToChangeFrame(): boolean {
const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT, Mode.BUSY].includes(this.data.mode) ||
(this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number');
const isUnable = [Mode.EDIT].includes(this.data.mode) ||
this.data.isFrameUpdating || (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number');
return !isUnable;
}
@ -288,11 +307,11 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
this.notify(UpdateReasons.DRAG_CANVAS);
}
public activate(clientID: string, attributeID: number | null): void {
public activate(clientID: string | null, attributeID: number | null): void {
if (this.data.activeElement.clientID === clientID && this.data.activeElement.attributeID === attributeID) {
return;
}
if (this.data.mode !== Mode.IDLE) {
if (this.data.mode !== Mode.IDLE && clientID !== null) {
throw Error(`Canvas is busy. Action: ${this.data.mode}`);
}
if (typeof clientID === 'number') {
@ -334,13 +353,27 @@ export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
}
public configureShapes(shapeProperties: ShapeProperties): void {
this.data.drawData.enabled = false;
this.data.mode = Mode.IDLE;
this.cancel();
this.data.shapeProperties = {
...shapeProperties,
};
this.notify(UpdateReasons.OBJECTS_UPDATED);
if (typeof shapeProperties.opacity === 'number') {
this.data.shapeProperties.opacity = Math.max(0, Math.min(shapeProperties.opacity, 100));
}
if (typeof shapeProperties.selectedOpacity === 'number') {
this.data.shapeProperties.selectedOpacity = Math.max(0, Math.min(shapeProperties.selectedOpacity, 100));
}
if (['Label', 'Instance', 'Group'].includes(shapeProperties.colorBy)) {
this.data.shapeProperties.colorBy = shapeProperties.colorBy;
}
if (typeof shapeProperties.outlined === 'boolean') {
this.data.shapeProperties.outlined = shapeProperties.outlined;
}
if (typeof shapeProperties.outlineColor === 'string') {
this.data.shapeProperties.outlineColor = shapeProperties.outlineColor;
}
this.notify(UpdateReasons.SHAPES_CONFIG_UPDATED);
}
public fit(): void {

File diff suppressed because it is too large Load Diff

@ -9,15 +9,20 @@ const DOLLY_FACTOR = 5;
const MAX_DISTANCE = 100;
const MIN_DISTANCE = 0.3;
const ZOOM_FACTOR = 7;
const ROTATION_HELPER_OFFSET = 0.1;
const ROTATION_HELPER_OFFSET = 0.75;
const CAMERA_REFERENCE = 'camRef';
const CUBOID_EDGE_NAME = 'edges';
const ROTATION_HELPER = 'rotationHelper';
const ROTATION_HELPER_NAME = '2DRotationHelper';
const PLANE_ROTATION_HELPER = 'planeRotationHelper';
const RESIZE_HELPER_NAME = '2DResizeHelper';
const ROTATION_SPEED = 80;
const FOV_DEFAULT = 1;
const FOV_MAX = 2;
const FOV_MIN = 0;
const FOV_INC = 0.08;
const DEFAULT_GROUP_COLOR = '#e0e0e0';
const DEFAULT_OUTLINE_COLOR = '#000000';
const GROUPING_COLOR = '#8b008b';
export default {
BASE_GRID_WIDTH,
@ -29,10 +34,15 @@ export default {
ROTATION_HELPER_OFFSET,
CAMERA_REFERENCE,
CUBOID_EDGE_NAME,
ROTATION_HELPER,
ROTATION_HELPER_NAME,
PLANE_ROTATION_HELPER,
RESIZE_HELPER_NAME,
ROTATION_SPEED,
FOV_DEFAULT,
FOV_MAX,
FOV_MIN,
FOV_INC,
DEFAULT_GROUP_COLOR,
DEFAULT_OUTLINE_COLOR,
GROUPING_COLOR,
};

@ -1,8 +1,9 @@
// Copyright (C) 2021-2022 Intel Corporation
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import * as THREE from 'three';
import { BufferGeometryUtils } from 'three/examples/jsm/utils/BufferGeometryUtils';
import { ViewType } from './canvas3dModel';
import constants from './consts';
@ -10,11 +11,25 @@ export interface Indexable {
[key: string]: any;
}
export function makeCornerPointsMatrix(x: number, y: number, z: number): number[][] {
return ([
[1 * x, 1 * y, 1 * z],
[1 * x, 1 * y, -1 * z],
[1 * x, -1 * y, 1 * z],
[1 * x, -1 * y, -1 * z],
[-1 * x, 1 * y, 1 * z],
[-1 * x, 1 * y, -1 * z],
[-1 * x, -1 * y, 1 * z],
[-1 * x, -1 * y, -1 * z],
]);
}
export class CuboidModel {
public perspective: THREE.Mesh;
public top: THREE.Mesh;
public side: THREE.Mesh;
public front: THREE.Mesh;
public wireframe: THREE.LineSegments;
public constructor(outline: string, outlineColor: string) {
const geometry = new THREE.BoxGeometry(1, 1, 1);
@ -26,24 +41,72 @@ export class CuboidModel {
});
this.perspective = new THREE.Mesh(geometry, material);
const geo = new THREE.EdgesGeometry(this.perspective.geometry);
const wireframe = new THREE.LineSegments(
this.wireframe = new THREE.LineSegments(
geo,
outline === 'line'
? new THREE.LineBasicMaterial({ color: outlineColor, linewidth: 4 })
: new THREE.LineDashedMaterial({
outline === 'line' ? new THREE.LineBasicMaterial({ color: outlineColor, linewidth: 4 }) :
new THREE.LineDashedMaterial({
color: outlineColor,
dashSize: 0.05,
gapSize: 0.05,
}),
);
wireframe.computeLineDistances();
wireframe.renderOrder = 1;
this.perspective.add(wireframe);
this.wireframe.computeLineDistances();
this.wireframe.renderOrder = 1;
this.perspective.add(this.wireframe);
this.top = new THREE.Mesh(geometry, material);
this.side = new THREE.Mesh(geometry, material);
this.front = new THREE.Mesh(geometry, material);
const planeTop = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0xff0000,
visible: false,
}),
);
const planeSide = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0xff0000,
visible: false,
}),
);
const planeFront = new THREE.Mesh(
new THREE.PlaneBufferGeometry(1, 1, 1, 1),
new THREE.MeshBasicMaterial({
color: 0xff0000,
visible: false,
}),
);
this.top.add(planeTop);
planeTop.rotation.set(0, 0, 0);
planeTop.position.set(0, 0, 0.5);
planeTop.name = constants.PLANE_ROTATION_HELPER;
this.side.add(planeSide);
planeSide.rotation.set(-Math.PI / 2, 0, Math.PI);
planeTop.position.set(0, 0.5, 0);
planeSide.name = constants.PLANE_ROTATION_HELPER;
this.front.add(planeFront);
planeFront.rotation.set(0, Math.PI / 2, 0);
planeTop.position.set(0.5, 0, 0);
planeFront.name = constants.PLANE_ROTATION_HELPER;
const cornerPoints = makeCornerPointsMatrix(0.5, 0.5, 0.5);
for (let i = 0; i < cornerPoints.length; i++) {
const point = new THREE.Vector3().fromArray(cornerPoints[i]);
const helper = new THREE.Mesh(new THREE.SphereGeometry(0.1));
helper.visible = false;
helper.name = `cuboidNodeHelper_${i}`;
this.perspective.add(helper);
helper.position.copy(point);
}
const camRotateHelper = new THREE.Object3D();
camRotateHelper.translateX(-2);
camRotateHelper.name = 'camRefRot';
@ -71,29 +134,25 @@ export class CuboidModel {
}
public attachCameraReference(): void {
// Attach Cam Reference
const topCameraReference = new THREE.Object3D();
topCameraReference.translateZ(2);
topCameraReference.name = constants.CAMERA_REFERENCE;
this.top.add(topCameraReference);
this.top.userData = { ...this.top.userData, camReference: topCameraReference };
const sideCameraReference = new THREE.Object3D();
sideCameraReference.translateY(2);
sideCameraReference.name = constants.CAMERA_REFERENCE;
this.side.add(sideCameraReference);
this.side.userData = { ...this.side.userData, camReference: sideCameraReference };
const frontCameraReference = new THREE.Object3D();
frontCameraReference.translateX(2);
frontCameraReference.name = constants.CAMERA_REFERENCE;
this.front.add(frontCameraReference);
this.front.userData = { ...this.front.userData, camReference: frontCameraReference };
}
public getReferenceCoordinates(viewType: string): THREE.Vector3 {
const { elements } = (this as Indexable)[viewType].getObjectByName(constants.CAMERA_REFERENCE).matrixWorld;
return new THREE.Vector3(elements[12], elements[13], elements[14]);
const camRef = (this as Indexable)[viewType].getObjectByName(constants.CAMERA_REFERENCE);
return camRef.getWorldPosition(new THREE.Vector3());
}
public setName(clientId: any): void {
@ -102,18 +161,17 @@ export class CuboidModel {
});
}
public setOriginalColor(color: string): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
((this as Indexable)[view] as any).originalColor = color;
});
}
public setColor(color: string): void {
this.setOutlineColor(color);
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
((this as Indexable)[view].material as THREE.MeshBasicMaterial).color.set(color);
});
}
public setOutlineColor(color: string): void {
(this.wireframe.material as THREE.MeshBasicMaterial).color.set(color);
}
public setOpacity(opacity: number): void {
[ViewType.PERSPECTIVE, ViewType.TOP, ViewType.SIDE, ViewType.FRONT].forEach((view): void => {
((this as Indexable)[view].material as THREE.MeshBasicMaterial).opacity = opacity / 100;
@ -121,69 +179,72 @@ export class CuboidModel {
}
}
export function setEdges(instance: THREE.Mesh): THREE.LineSegments {
const edges = new THREE.EdgesGeometry(instance.geometry);
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: '#ffffff', linewidth: 3 }));
line.name = constants.CUBOID_EDGE_NAME;
instance.add(line);
return line;
export function createCuboidEdges(instance: THREE.Mesh): THREE.LineSegments {
const geometry = new THREE.EdgesGeometry(instance.geometry);
const edges = new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({ color: '#ffffff', linewidth: 3 }));
edges.name = constants.CUBOID_EDGE_NAME;
instance.add(edges);
return edges;
}
export function setTranslationHelper(instance: THREE.Mesh): void {
export function removeCuboidEdges(instance: THREE.Mesh): void {
const edges = instance.getObjectByName(constants.CUBOID_EDGE_NAME);
instance.remove(edges);
}
export function createResizeHelper(instance: THREE.Mesh): void {
const sphereGeometry = new THREE.SphereGeometry(0.1);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 });
instance.geometry.deleteAttribute('normal');
instance.geometry.deleteAttribute('uv');
// eslint-disable-next-line no-param-reassign
instance.geometry = BufferGeometryUtils.mergeVertices(instance.geometry);
const vertices = [];
const positionAttribute = instance.geometry.getAttribute('position');
for (let i = 0; i < positionAttribute.count; i++) {
const vertex = new THREE.Vector3();
vertex.fromBufferAttribute(positionAttribute, i);
vertices.push(vertex);
}
const helpers = [];
for (let i = 0; i < vertices.length; i++) {
helpers[i] = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone());
helpers[i].position.set(vertices[i].x, vertices[i].y, vertices[i].z);
helpers[i].up.set(0, 0, 1);
helpers[i].name = 'resizeHelper';
instance.add(helpers[i]);
helpers[i].scale.set(1 / instance.scale.x, 1 / instance.scale.y, 1 / instance.scale.z);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ff0000', opacity: 1 });
const cornerPoints = makeCornerPointsMatrix(0.5, 0.5, 0.5);
for (let i = 0; i < cornerPoints.length; i++) {
const point = new THREE.Vector3().fromArray(cornerPoints[i]);
const tmpSphere = new THREE.Mesh(new THREE.SphereGeometry(0.1));
instance.add(tmpSphere);
tmpSphere.position.copy(point);
const globalPosition = tmpSphere.getWorldPosition(new THREE.Vector3());
instance.remove(tmpSphere);
const helper = new THREE.Mesh(sphereGeometry.clone(), sphereMaterial.clone());
helper.position.copy(globalPosition);
helper.name = `${constants.RESIZE_HELPER_NAME}_${i}`;
instance.parent.add(helper);
}
// eslint-disable-next-line no-param-reassign
instance.userData = { ...instance.userData, resizeHelpers: helpers };
}
export function removeResizeHelper(instance: THREE.Mesh): void {
instance.parent.children.filter((child: THREE.Object3D) => child.name.startsWith(constants.RESIZE_HELPER_NAME))
.forEach((helper) => {
instance.parent.remove(helper);
});
}
export function createRotationHelper(instance: THREE.Mesh, viewType: ViewType): void {
const sphereGeometry = new THREE.SphereGeometry(0.1);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#ffffff', opacity: 1 });
const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial);
rotationHelper.name = constants.ROTATION_HELPER;
switch (viewType) {
case ViewType.TOP:
rotationHelper.position.set(
(instance.geometry as THREE.BoxGeometry).parameters.height / 2 + constants.ROTATION_HELPER_OFFSET,
instance.position.y,
instance.position.z,
);
instance.add(rotationHelper.clone());
// eslint-disable-next-line no-param-reassign
instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() };
break;
case ViewType.SIDE:
case ViewType.FRONT:
rotationHelper.position.set(
instance.position.x,
instance.position.y,
(instance.geometry as THREE.BoxGeometry).parameters.depth / 2 + constants.ROTATION_HELPER_OFFSET,
);
instance.add(rotationHelper.clone());
// eslint-disable-next-line no-param-reassign
instance.userData = { ...instance.userData, rotationHelpers: rotationHelper.clone() };
break;
default:
break;
if ([ViewType.TOP, ViewType.SIDE, ViewType.FRONT].includes(viewType)) {
// Create a temporary element to get correct position
const tmpSphere = new THREE.Mesh(new THREE.SphereGeometry(0.1));
instance.add(tmpSphere);
if (viewType === ViewType.TOP) {
tmpSphere.translateY(constants.ROTATION_HELPER_OFFSET);
} else {
tmpSphere.translateZ(constants.ROTATION_HELPER_OFFSET);
}
const globalPosition = tmpSphere.getWorldPosition(new THREE.Vector3());
instance.remove(tmpSphere);
// Create rotation helper itself first
const sphereGeometry = new THREE.SphereGeometry(0.1);
const sphereMaterial = new THREE.MeshBasicMaterial({ color: '#33b864', opacity: 1 });
const rotationHelper = new THREE.Mesh(sphereGeometry, sphereMaterial);
rotationHelper.name = constants.ROTATION_HELPER_NAME;
instance.parent.add(rotationHelper);
rotationHelper.position.copy(globalPosition);
}
}
export function removeRotationHelper(instance: THREE.Mesh): void {
const helper = instance.parent.getObjectByName(constants.ROTATION_HELPER_NAME);
if (helper) {
instance.parent.remove(helper);
}
}

@ -0,0 +1,9 @@
// Copyright (C) 2022 CVAT.ai Corporation
//
// SPDX-License-Identifier: MIT
import ObjectState from 'cvat-core/src/object-state';
import { Label } from 'cvat-core/src/labels';
import { ShapeType } from 'cvat-core/src/enums';
export { ObjectState, Label, ShapeType };

@ -1496,13 +1496,13 @@ export function pasteShapeAsync(): ThunkAction {
if (initialState && canvasInstance) {
const activeControl = ShapeTypeToControl[initialState.shapeType as ShapeType] || ActiveControl.CURSOR;
canvasInstance.cancel();
dispatch({
type: AnnotationActionTypes.PASTE_SHAPE,
payload: {
activeControl,
},
});
canvasInstance.cancel();
if (initialState.objectType === ObjectType.TAG) {
const objectState = new cvat.classes.ObjectState({

@ -39,6 +39,7 @@ interface Props {
contextMenuVisibility: boolean;
activeLabelID: number;
activeObjectType: ObjectType;
activatedStateID: number | null;
onSetupCanvas: () => void;
onGroupObjects: (enabled: boolean) => void;
onResetCanvas(): void;
@ -182,6 +183,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
frame,
jobInstance,
activeLabelID,
activatedStateID,
resetZoom,
activeObjectType,
onShapeDrawn,
@ -336,6 +338,10 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
};
}, []);
useEffect(() => {
canvasInstance.activate(activatedStateID);
}, [activatedStateID]);
useEffect(() => {
canvasInstance.configure({ resetZoom });
}, [resetZoom]);
@ -522,16 +528,18 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
handle={<span className='cvat-resizable-handle-horizontal' />}
onResize={(e: SyntheticEvent) => setViewSize({ type: ViewType.PERSPECTIVE, e })}
>
{frameFetching ? (
<svg id='cvat_canvas_loading_animation'>
<circle id='cvat_canvas_loading_circle' r='30' cx='50%' cy='50%' />
</svg>
) : null}
<div className='cvat-canvas3d-perspective' id='cvat-canvas3d-perspective'>
<div className='cvat-canvas-container cvat-canvas-container-overflow' ref={perspectiveView} />
<ArrowGroup />
<ControlGroup />
</div>
<>
{frameFetching ? (
<svg id='cvat_canvas_loading_animation'>
<circle id='cvat_canvas_loading_circle' r='30' cx='50%' cy='50%' />
</svg>
) : null}
<div className='cvat-canvas3d-perspective' id='cvat-canvas3d-perspective'>
<div className='cvat-canvas-container cvat-canvas-container-overflow' ref={perspectiveView} />
<ArrowGroup />
<ControlGroup />
</div>
</>
</ResizableBox>
<div
className='cvat-canvas3d-orthographic-views'

@ -44,6 +44,7 @@ interface StateToProps {
annotations: any[];
contextMenuVisibility: boolean;
activeLabelID: number;
activatedStateID: number | null;
activeObjectType: ObjectType;
workspace: Workspace;
frame: number;
@ -78,6 +79,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
},
annotations: {
states: annotations,
activatedStateID,
},
workspace,
},
@ -105,6 +107,7 @@ function mapStateToProps(state: CombinedState): StateToProps {
outlined,
outlineColor,
activeLabelID,
activatedStateID,
activeObjectType,
resetZoom,
workspace,

@ -225,7 +225,7 @@ class ObjectItemContainer extends React.PureComponent<Props> {
private activate = (activeElementID?: number): void => {
const {
objectState, ready, activeControl, activateObject, canvasInstance,
objectState, ready, activeControl, activateObject,
} = this.props;
if (ready && activeControl === ActiveControl.CURSOR) {
@ -233,9 +233,6 @@ class ObjectItemContainer extends React.PureComponent<Props> {
objectState.clientID,
(Number.isInteger(activeElementID) ? activeElementID : null) as number | null,
);
if (canvasInstance instanceof Canvas3d) {
canvasInstance.activate(objectState.clientID);
}
}
};

Loading…
Cancel
Save