@ -10,29 +10,55 @@ import {
ArrowDownOutlined , ArrowLeftOutlined , ArrowRightOutlined , ArrowUpOutlined ,
} from '@ant-design/icons' ;
import { ResizableBox } from 'react-resizable' ;
import { Workspace } from 'reducers/interfaces' ;
import {
CAMERA_ACTION , Canvas3d , MouseInteraction , ViewType ,
ColorBy , ContextMenuType , ObjectType , Workspace ,
} from 'reducers/interfaces' ;
import {
CameraAction , Canvas3d , ViewType , ViewsDOM ,
} from 'cvat-canvas3d-wrapper' ;
import { Canvas } from 'cvat-canvas-wrapper' ;
import ContextImage from 'components/annotation-page/standard-workspace/context-image/context-image' ;
import CVATTooltip from 'components/common/cvat-tooltip' ;
import { LogType } from 'cvat-logger' ;
import getCore from 'cvat-core-wrapper' ;
const cvat = getCore ( ) ;
interface Props {
canvasInstance : Canvas3d ;
opacity : number ;
selectedOpacity : number ;
outlined : boolean ;
outlineColor : string ;
colorBy : ColorBy ;
canvasInstance : Canvas3d | Canvas ;
jobInstance : any ;
frameData : any ;
curZLayer : number ;
annotations : any [ ] ;
contextMenuVisibility : boolean ;
activeLabelID : number ;
activatedStateID : number | null ;
activeObjectType : ObjectType ;
onSetupCanvas : ( ) = > void ;
onGroupObjects : ( enabled : boolean ) = > void ;
onResetCanvas ( ) : void ;
onCreateAnnotations ( sessionInstance : any , frame : number , states : any [ ] ) : void ;
onActivateObject ( activatedStateID : number | null ) : void ;
onUpdateAnnotations ( states : any [ ] ) : void ;
onUpdateContextMenu ( visible : boolean , left : number , top : number , type : ContextMenuType , pointID? : number ) : void ;
onGroupAnnotations ( sessionInstance : any , frame : number , states : any [ ] ) : void ;
onEditShape : ( enabled : boolean ) = > void ;
onDragCanvas : ( enabled : boolean ) = > void ;
onShapeDrawn : ( ) = > void ;
workspace : Workspace ;
animateID : any ;
automaticBordering : boolean ;
showObjectsTextAlways : boolean ;
frame : number ;
}
interface ViewSize {
fullHeight : number ;
fullWidth : number ;
vertical : number ;
top : number ;
side : number ;
@ -41,7 +67,7 @@ interface ViewSize {
function viewSizeReducer (
state : ViewSize ,
action : { type : ViewType | 'set' ; e? : SyntheticEvent ; data? : ViewSize } ,
action : { type : ViewType | 'set' | 'resize' ; e? : SyntheticEvent ; data? : ViewSize } ,
) : ViewSize {
const event = ( action . e as unknown ) as MouseEvent ;
const canvas3dContainer = document . getElementById ( 'canvas3d-container' ) ;
@ -94,6 +120,33 @@ function viewSizeReducer(
} ;
case 'set' :
return action . data as ViewSize ;
case 'resize' : {
const canvasPerspectiveContainer = document . getElementById ( 'cvat-canvas3d-perspective' ) ;
let midState = { . . . state } ;
if ( canvasPerspectiveContainer ) {
if ( state . fullHeight !== canvas3dContainer . clientHeight ) {
const diff = canvas3dContainer . clientHeight - state . fullHeight ;
midState = {
. . . midState ,
fullHeight : canvas3dContainer.clientHeight ,
vertical : state.vertical + diff ,
} ;
}
if ( state . fullWidth !== canvasPerspectiveContainer . clientWidth ) {
const oldWidth = state . fullWidth ;
const width = canvasPerspectiveContainer . clientWidth ;
midState = {
. . . midState ,
fullWidth : width ,
top : ( state . top / oldWidth ) * width ,
side : ( state . side / oldWidth ) * width ,
front : ( state . front / oldWidth ) * width ,
} ;
}
return midState ;
}
return state ;
}
default :
throw new Error ( ) ;
}
@ -105,6 +158,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
const animateId = useRef ( 0 ) ;
const [ viewSize , setViewSize ] = useReducer ( viewSizeReducer , {
fullHeight : 0 ,
fullWidth : 0 ,
vertical : 0 ,
top : 0 ,
side : 0 ,
@ -115,70 +169,122 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
const sideView = useRef < HTMLDivElement | null > ( null ) ;
const frontView = useRef < HTMLDivElement | null > ( null ) ;
const { frameData , annotations , curZLayer } = props ;
const {
opacity ,
outlined ,
outlineColor ,
selectedOpacity ,
colorBy ,
contextMenuVisibility ,
frameData ,
onResetCanvas ,
onSetupCanvas ,
annotations ,
frame ,
jobInstance ,
activeLabelID ,
activeObjectType ,
onShapeDrawn ,
onCreateAnnotations ,
} = props ;
const { canvasInstance } = props as { canvasInstance : Canvas3d } ;
const onCanvasSetup = ( ) : void = > {
const { onSetupCanvas } = props ;
onSetupCanvas ( ) ;
} ;
const animateCanvas = ( ) : void = > {
const { canvasInstance } = props ;
const onCanvasDragStart = ( ) : void = > {
const { onDragCanvas } = props ;
onDragCanvas ( true ) ;
} ;
const onCanvasDragDone = ( ) : void = > {
const { onDragCanvas } = props ;
onDragCanvas ( false ) ;
} ;
const animateCanvas = ( ) : void = > {
canvasInstance . render ( ) ;
animateId . current = requestAnimationFrame ( animateCanvas ) ;
} ;
const updateCanvas = ( ) : void = > {
const { canvasInstance } = props ;
if ( frameData !== null ) {
canvasInstance . setup ( frameData ) ;
canvasInstance . setup (
frameData ,
annotations . filter ( ( e ) = > e . objectType !== ObjectType . TAG ) ,
) ;
}
} ;
const onMouseClick = ( event : MouseEvent ) : void = > {
const { canvasInstance } = props ;
canvasInstance . mouseControls ( MouseInteraction . CLICK , event ) ;
const onCanvasCancel = ( ) : void = > {
onResetCanvas ( ) ;
} ;
const onMouseDoubleClick = ( event : MouseEvent ) : void = > {
const { canvasInstance } = props ;
canvasInstance . mouseControls ( MouseInteraction . DOUBLE_CLICK , event ) ;
} ;
const onCanvasShapeDrawn = ( event : any ) : void = > {
if ( ! event . detail . continue ) {
onShapeDrawn ( ) ;
}
const { state , duration } = event . detail ;
const isDrawnFromScratch = ! state . label ;
if ( isDrawnFromScratch ) {
jobInstance . logger . log ( LogType . drawObject , { count : 1 , duration } ) ;
} else {
jobInstance . logger . log ( LogType . pasteObject , { count : 1 , duration } ) ;
}
const onMouseHover = ( event : MouseEvent ) : void = > {
const { canvasInstance } = props ;
canvasInstance . mouseControls ( MouseInteraction . HOVER , event ) ;
state . objectType = state . objectType || activeObjectType ;
state . label = state . label || jobInstance . task . labels . filter ( ( label : any ) = > label . id === activeLabelID ) [ 0 ] ;
state . occluded = state . occluded || false ;
state . frame = frame ;
state . zOrder = 0 ;
const objectState = new cvat . classes . ObjectState ( state ) ;
onCreateAnnotations ( jobInstance , frame , [ objectState ] ) ;
} ;
const onCanvasCancel = ( ) : void = > {
const { onResetCanvas } = props ;
onResetCanvas ( ) ;
const onCanvasClick = ( e : MouseEvent ) : void = > {
const { onUpdateContextMenu } = props ;
if ( contextMenuVisibility ) {
onUpdateContextMenu ( false , e . clientX , e . clientY , ContextMenuType . CANVAS_SHAPE ) ;
}
} ;
const initialSetup = ( ) : void = > {
const { canvasInstance } = props ;
const canvasInstanceDOM = canvasInstance . html ( ) ;
// Events
const canvasInstanceDOM = canvasInstance . html ( ) as ViewsDOM ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.setup' , onCanvasSetup ) ;
canvasInstanceDOM . perspective . addEventListener ( 'mousemove' , onMouseHover ) ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.canceled' , onCanvasCancel ) ;
canvasInstanceDOM . perspective . addEventListener ( MouseInteraction . DOUBLE_CLICK , onMouseDoubleClick ) ;
canvasInstanceDOM . perspective . addEventListener ( MouseInteraction . CLICK , onMouseClick ) ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.dragstart' , onCanvasDragStart ) ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.dragstop' , onCanvasDragDone ) ;
} ;
const keyControls = ( key : KeyboardEvent ) : void = > {
const { canvasInstance } = props ;
const keyControlsKeyDown = ( key : KeyboardEvent ) : void = > {
canvasInstance . keyControls ( key ) ;
} ;
useEffect ( ( ) = > {
const { canvasInstance } = props ;
const keyControlsKeyUp = ( key : KeyboardEvent ) : void = > {
if ( key . code === 'ControlLeft' ) {
canvasInstance . keyControls ( key ) ;
}
} ;
const canvasInstanceDOM = canvasInstance . html ( ) ;
const onCanvasShapeSelected = ( event : any ) : void = > {
const { onActivateObject } = props ;
const { clientID } = event . detail ;
onActivateObject ( clientID ) ;
canvasInstance . activate ( clientID ) ;
} ;
const onCanvasEditDone = ( event : any ) : void = > {
const { onEditShape , onUpdateAnnotations } = props ;
onEditShape ( false ) ;
const { state , points } = event . detail ;
state . points = points ;
onUpdateAnnotations ( [ state ] ) ;
} ;
useEffect ( ( ) = > {
const canvasInstanceDOM = canvasInstance . html ( ) ;
if (
perspectiveView &&
perspectiveView . current &&
@ -200,6 +306,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
type : 'set' ,
data : {
fullHeight : canvas3dContainer.clientHeight ,
fullWidth : canvas3dContainer.clientWidth ,
vertical : canvas3dContainer.clientHeight / 2 ,
top : width ,
side : width ,
@ -209,7 +316,8 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
}
}
document . addEventListener ( 'keydown' , keyControls ) ;
document . addEventListener ( 'keydown' , keyControlsKeyDown ) ;
document . addEventListener ( 'keyup' , keyControlsKeyUp ) ;
initialSetup ( ) ;
updateCanvas ( ) ;
@ -217,21 +325,80 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
return ( ) = > {
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.setup' , onCanvasSetup ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'mousemove' , onMouseHover ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.canceled' , onCanvasCancel ) ;
canvasInstanceDOM . perspective . removeEventListener ( MouseInteraction . DOUBLE_CLICK , onMouseDoubleClick ) ;
canvasInstanceDOM . perspective . removeEventListener ( MouseInteraction . CLICK , onMouseClick ) ;
document . removeEventListener ( 'keydown' , keyControls ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.dragstart' , onCanvasDragStart ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.dragstop' , onCanvasDragDone ) ;
document . removeEventListener ( 'keydown' , keyControlsKeyDown ) ;
document . removeEventListener ( 'keyup' , keyControlsKeyUp ) ;
cancelAnimationFrame ( animateId . current ) ;
} ;
} , [ ] ) ;
const updateShapesView = ( ) : void = > {
( canvasInstance as Canvas3d ) . configureShapes ( {
opacity ,
outlined ,
outlineColor ,
selectedOpacity ,
colorBy ,
} ) ;
} ;
const onContextMenu = ( event : any ) : void = > {
const { onUpdateContextMenu , onActivateObject } = props ;
onActivateObject ( event . detail . clientID ) ;
onUpdateContextMenu (
event . detail . clientID !== null ,
event . detail . clientX ,
event . detail . clientY ,
ContextMenuType . CANVAS_SHAPE ,
) ;
} ;
const onResize = ( ) : void = > {
setViewSize ( {
type : 'resize' ,
} ) ;
} ;
const onCanvasObjectsGroupped = ( event : any ) : void = > {
const { onGroupAnnotations , onGroupObjects } = props ;
onGroupObjects ( false ) ;
const { states } = event . detail ;
onGroupAnnotations ( jobInstance , frame , states ) ;
} ;
useEffect ( ( ) = > {
updateShapesView ( ) ;
} , [ opacity , outlined , outlineColor , selectedOpacity , colorBy ] ) ;
useEffect ( ( ) = > {
const canvasInstanceDOM = canvasInstance . html ( ) as ViewsDOM ;
updateCanvas ( ) ;
} , [ frameData , annotations , curZLayer ] ) ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.drawn' , onCanvasShapeDrawn ) ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.selected' , onCanvasShapeSelected ) ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.edited' , onCanvasEditDone ) ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.contextmenu' , onContextMenu ) ;
canvasInstanceDOM . perspective . addEventListener ( 'click' , onCanvasClick ) ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.fit' , onResize ) ;
canvasInstanceDOM . perspective . addEventListener ( 'canvas.groupped' , onCanvasObjectsGroupped ) ;
window . addEventListener ( 'resize' , onResize ) ;
return ( ) = > {
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.drawn' , onCanvasShapeDrawn ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.selected' , onCanvasShapeSelected ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.edited' , onCanvasEditDone ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.contextmenu' , onContextMenu ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'click' , onCanvasClick ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.fit' , onResize ) ;
canvasInstanceDOM . perspective . removeEventListener ( 'canvas.groupped' , onCanvasObjectsGroupped ) ;
window . removeEventListener ( 'resize' , onResize ) ;
} ;
} , [ frameData , annotations , activeLabelID , contextMenuVisibility ] ) ;
const screenKeyControl = ( code : CAMERA_ACTION ) : void = > {
const { canvasInstance } = props ;
const screenKeyControl = ( code : CameraAction ) : void = > {
canvasInstance . keyControls ( new KeyboardEvent ( 'keydown' , { code , altKey : true } ) ) ;
} ;
@ -239,7 +406,8 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< span className = 'cvat-canvas3d-perspective-arrow-directions' >
< CVATTooltip title = 'Arrow Up' placement = 'topRight' >
< button
onClick = { ( ) = > screenKeyControl ( CAMERA_ACTION . TILT_UP ) }
data - cy = 'arrow-up'
onClick = { ( ) = > screenKeyControl ( CameraAction . TILT_UP ) }
type = 'button'
className = 'cvat-canvas3d-perspective-arrow-directions-icons-up'
>
@ -249,7 +417,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< br / >
< CVATTooltip title = 'Arrow Left' placement = 'topRight' >
< button
onClick = { ( ) = > screenKeyControl ( C AMERA_ACTION . ROTATE_LEFT ) }
onClick = { ( ) = > screenKeyControl ( C ameraAction . ROTATE_LEFT ) }
type = 'button'
className = 'cvat-canvas3d-perspective-arrow-directions-icons-bottom'
>
@ -258,7 +426,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< / CVATTooltip >
< CVATTooltip title = 'Arrow Bottom' placement = 'topRight' >
< button
onClick = { ( ) = > screenKeyControl ( C AMERA_ACTION . TILT_DOWN ) }
onClick = { ( ) = > screenKeyControl ( C ameraAction . TILT_DOWN ) }
type = 'button'
className = 'cvat-canvas3d-perspective-arrow-directions-icons-bottom'
>
@ -267,7 +435,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< / CVATTooltip >
< CVATTooltip title = 'Arrow Right' placement = 'topRight' >
< button
onClick = { ( ) = > screenKeyControl ( C AMERA_ACTION . ROTATE_RIGHT ) }
onClick = { ( ) = > screenKeyControl ( C ameraAction . ROTATE_RIGHT ) }
type = 'button'
className = 'cvat-canvas3d-perspective-arrow-directions-icons-bottom'
>
@ -281,7 +449,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< span className = 'cvat-canvas3d-perspective-directions' >
< CVATTooltip title = 'Alt+U' placement = 'topLeft' >
< button
onClick = { ( ) = > screenKeyControl ( C AMERA_ACTION . MOVE_UP ) }
onClick = { ( ) = > screenKeyControl ( C ameraAction . MOVE_UP ) }
type = 'button'
className = 'cvat-canvas3d-perspective-directions-icon'
>
@ -290,7 +458,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< / CVATTooltip >
< CVATTooltip title = 'Alt+I' placement = 'topLeft' >
< button
onClick = { ( ) = > screenKeyControl ( C AMERA_ACTION . ZOOM_IN ) }
onClick = { ( ) = > screenKeyControl ( C ameraAction . ZOOM_IN ) }
type = 'button'
className = 'cvat-canvas3d-perspective-directions-icon'
>
@ -299,7 +467,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< / CVATTooltip >
< CVATTooltip title = 'Alt+O' placement = 'topLeft' >
< button
onClick = { ( ) = > screenKeyControl ( C AMERA_ACTION . MOVE_DOWN ) }
onClick = { ( ) = > screenKeyControl ( C ameraAction . MOVE_DOWN ) }
type = 'button'
className = 'cvat-canvas3d-perspective-directions-icon'
>
@ -309,7 +477,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< br / >
< CVATTooltip title = 'Alt+J' placement = 'topLeft' >
< button
onClick = { ( ) = > screenKeyControl ( C AMERA_ACTION . MOVE_LEFT ) }
onClick = { ( ) = > screenKeyControl ( C ameraAction . MOVE_LEFT ) }
type = 'button'
className = 'cvat-canvas3d-perspective-directions-icon'
>
@ -318,7 +486,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< / CVATTooltip >
< CVATTooltip title = 'Alt+K' placement = 'topLeft' >
< button
onClick = { ( ) = > screenKeyControl ( C AMERA_ACTION . ZOOM_OUT ) }
onClick = { ( ) = > screenKeyControl ( C ameraAction . ZOOM_OUT ) }
type = 'button'
className = 'cvat-canvas3d-perspective-directions-icon'
>
@ -327,7 +495,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
< / CVATTooltip >
< CVATTooltip title = 'Alt+L' placement = 'topLeft' >
< button
onClick = { ( ) = > screenKeyControl ( C AMERA_ACTION . MOVE_RIGHT ) }
onClick = { ( ) = > screenKeyControl ( C ameraAction . MOVE_RIGHT ) }
type = 'button'
className = 'cvat-canvas3d-perspective-directions-icon'
>
@ -348,7 +516,7 @@ const CanvasWrapperComponent = (props: Props): ReactElement => {
handle = { < span className = 'cvat-resizable-handle-horizontal' / > }
onResize = { ( e : SyntheticEvent ) = > setViewSize ( { type : ViewType . PERSPECTIVE , e } ) }
>
< div className = 'cvat-canvas3d-perspective' >
< div className = 'cvat-canvas3d-perspective' id = 'cvat-canvas3d-perspective' >
< div className = 'cvat-canvas-container cvat-canvas-container-overflow' ref = { perspectiveView } / >
< ArrowGroup / >
< ControlGroup / >