Merge branch 'release-1.3.0'
commit
c7033a79ec
@ -0,0 +1,41 @@
|
||||
name: Linter
|
||||
on: pull_request
|
||||
jobs:
|
||||
Bandit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Run checks
|
||||
run: |
|
||||
URL="https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files"
|
||||
PR_FILES=$(curl -s -X GET -G $URL | jq -r '.[] | select(.status != "removed") | .filename')
|
||||
for files in $PR_FILES; do
|
||||
extension="${files##*.}"
|
||||
if [[ $extension == 'py' ]]; then
|
||||
changed_files_bandit+=" ${files}"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -z ${changed_files_bandit} ]]; then
|
||||
sudo apt-get --no-install-recommends install -y build-essential curl python3-dev python3-pip python3-venv
|
||||
python3 -m venv .env
|
||||
. .env/bin/activate
|
||||
pip install -U pip wheel setuptools
|
||||
pip install bandit
|
||||
mkdir -p bandit_report
|
||||
|
||||
echo "Bandit version: "`bandit --version | head -1`
|
||||
echo "The files will be checked: "`echo ${changed_files_bandit}`
|
||||
bandit ${changed_files_bandit} --exclude '**/tests/**' -a file --ini ./.bandit -f html -o ./bandit_report/bandit_checks.html
|
||||
deactivate
|
||||
else
|
||||
echo "No files with the \"py\" extension found"
|
||||
fi
|
||||
|
||||
- name: Upload artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: bandit_report
|
||||
path: bandit_report
|
||||
@ -0,0 +1,42 @@
|
||||
name: Linter
|
||||
on: pull_request
|
||||
jobs:
|
||||
ESLint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Run checks
|
||||
run: |
|
||||
URL="https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files"
|
||||
PR_FILES=$(curl -s -X GET -G $URL | jq -r '.[] | select(.status != "removed") | .filename')
|
||||
for files in $PR_FILES; do
|
||||
extension="${files##*.}"
|
||||
if [[ $extension == 'js' || $extension == 'ts' || $extension == 'jsx' || $extension == 'tsx' ]]; then
|
||||
changed_files_eslint+=" ${files}"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -z ${changed_files_eslint} ]]; then
|
||||
for package_files in `find -maxdepth 2 -name "package.json" -type f`; do
|
||||
cd $(dirname $package_files) && npm ci && cd ${{ github.workspace }}
|
||||
done
|
||||
npm install eslint-detailed-reporter --save-dev
|
||||
mkdir -p eslint_report
|
||||
|
||||
echo "ESLint version: "`npx eslint --version`
|
||||
echo "The files will be checked: "`echo ${changed_files_eslint}`
|
||||
npx eslint ${changed_files_eslint} -f node_modules/eslint-detailed-reporter/lib/detailed.js -o ./eslint_report/eslint_checks.html
|
||||
else
|
||||
echo "No files with the \"js|ts|jsx|tsx\" extension found"
|
||||
fi
|
||||
|
||||
- name: Upload artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: eslint_report
|
||||
path: eslint_report
|
||||
@ -0,0 +1,57 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'develop'
|
||||
pull_request:
|
||||
branches:
|
||||
- '*'
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Build CVAT
|
||||
env:
|
||||
HOST_COVERAGE_DATA_DIR: ${{ github.workspace }}
|
||||
CONTAINER_COVERAGE_DATA_DIR: '/coverage_data'
|
||||
DJANGO_SU_NAME: 'admin'
|
||||
DJANGO_SU_EMAIL: 'admin@localhost.company'
|
||||
DJANGO_SU_PASSWORD: '12qwaszx'
|
||||
run: |
|
||||
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml build
|
||||
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'coverage run -a manage.py test cvat/apps utils/cli && mv .coverage ${CONTAINER_COVERAGE_DATA_DIR}'
|
||||
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd cvat-data && npm ci && cd ../cvat-core && npm ci && npm run test && mv ./reports/coverage/lcov.info ${CONTAINER_COVERAGE_DATA_DIR} && chmod a+rwx ${CONTAINER_COVERAGE_DATA_DIR}/lcov.info'
|
||||
docker-compose up -d
|
||||
docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell"
|
||||
- name: Code instrumentation
|
||||
run: |
|
||||
npm ci
|
||||
npm run coverage
|
||||
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
|
||||
- name: End-to-end testing
|
||||
run: |
|
||||
cd ./tests
|
||||
npm ci
|
||||
npx cypress run --headless --browser chrome
|
||||
- name: Uploading cypress screenshots as an artifact
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: cypress_screenshots
|
||||
path: ${{ github.workspace }}/tests/cypress/screenshots
|
||||
- name: Collect coverage data
|
||||
env:
|
||||
HOST_COVERAGE_DATA_DIR: ${{ github.workspace }}
|
||||
CONTAINER_COVERAGE_DATA_DIR: "/coverage_data"
|
||||
COVERALLS_SERVICE_NAME: github
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mv ./tests/.nyc_output ./
|
||||
npx nyc report --reporter=text-lcov >> ${HOST_COVERAGE_DATA_DIR}/lcov.info
|
||||
docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd ${CONTAINER_COVERAGE_DATA_DIR} && coveralls-lcov -v -n lcov.info > ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json'
|
||||
docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'ln -s ${CONTAINER_COVERAGE_DATA_DIR}/.git . && ln -s ${CONTAINER_COVERAGE_DATA_DIR}/.coverage . && ln -s ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json . && coveralls --merge=coverage.json'
|
||||
@ -0,0 +1,61 @@
|
||||
name: Publish Docker images
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
build_and_push_to_registry:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Build images
|
||||
run: |
|
||||
CLAM_AV=yes INSTALL_SOURCES=yes docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml build
|
||||
|
||||
- name: Run unit tests
|
||||
env:
|
||||
HOST_COVERAGE_DATA_DIR: ${{ github.workspace }}
|
||||
CONTAINER_COVERAGE_DATA_DIR: '/coverage_data'
|
||||
DJANGO_SU_NAME: 'admin'
|
||||
DJANGO_SU_EMAIL: 'admin@localhost.company'
|
||||
DJANGO_SU_PASSWORD: '12qwaszx'
|
||||
run: |
|
||||
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'coverage run -a manage.py test cvat/apps utils/cli'
|
||||
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd cvat-data && npm ci && cd ../cvat-core && npm ci && npm run test'
|
||||
docker-compose up -d
|
||||
docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell"
|
||||
|
||||
- name: Run end-to-end tests
|
||||
run: |
|
||||
cd ./tests
|
||||
npm ci
|
||||
npm run cypress:run:chrome
|
||||
- name: Uploading cypress screenshots as an artifact
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: cypress_screenshots
|
||||
path: ${{ github.workspace }}/tests/cypress/screenshots
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Push to Docker Hub
|
||||
env:
|
||||
DOCKERHUB_WORKSPACE: 'openvino'
|
||||
SERVER_IMAGE_REPO: 'cvat_server'
|
||||
UI_IMAGE_REPO: 'cvat_ui'
|
||||
run: |
|
||||
docker tag "${DOCKERHUB_WORKSPACE}/${SERVER_IMAGE_REPO}:latest" "${DOCKERHUB_WORKSPACE}/${SERVER_IMAGE_REPO}:${{ github.event.release.tag_name }}"
|
||||
docker push "${DOCKERHUB_WORKSPACE}/${SERVER_IMAGE_REPO}:${{ github.event.release.tag_name }}"
|
||||
docker push "${DOCKERHUB_WORKSPACE}/${SERVER_IMAGE_REPO}:latest"
|
||||
|
||||
docker tag "${DOCKERHUB_WORKSPACE}/${UI_IMAGE_REPO}:latest" "${DOCKERHUB_WORKSPACE}/${UI_IMAGE_REPO}:${{ github.event.release.tag_name }}"
|
||||
docker push "${DOCKERHUB_WORKSPACE}/${UI_IMAGE_REPO}:${{ github.event.release.tag_name }}"
|
||||
docker push "${DOCKERHUB_WORKSPACE}/${UI_IMAGE_REPO}:latest"
|
||||
@ -0,0 +1,45 @@
|
||||
name: Linter
|
||||
on: pull_request
|
||||
jobs:
|
||||
PyLint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Run checks
|
||||
run: |
|
||||
URL="https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files"
|
||||
PR_FILES=$(curl -s -X GET -G $URL | jq -r '.[] | select(.status != "removed") | .filename')
|
||||
for files in $PR_FILES; do
|
||||
extension="${files##*.}"
|
||||
if [[ $extension == 'py' ]]; then
|
||||
changed_files_pylint+=" ${files}"
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ! -z ${changed_files_pylint} ]]; then
|
||||
sudo apt-get --no-install-recommends install -y build-essential curl python3-dev python3-pip python3-venv
|
||||
python3 -m venv .env
|
||||
. .env/bin/activate
|
||||
pip install -U pip wheel setuptools
|
||||
pip install pylint-json2html
|
||||
pip install $(egrep "pylint.*" ./cvat/requirements/development.txt)
|
||||
pip install $(egrep "Django.*" ./cvat/requirements/base.txt)
|
||||
mkdir -p pylint_report
|
||||
|
||||
echo "Pylint version: "`pylint --version | head -1`
|
||||
echo "The files will be checked: "`echo ${changed_files_pylint}`
|
||||
pylint ${changed_files_pylint} --output-format=json > ./pylint_report/pylint_checks.json || exit_code=`echo $?` || true
|
||||
pylint-json2html -o ./pylint_report/pylint_checks.html ./pylint_report/pylint_checks.json
|
||||
deactivate
|
||||
exit ${exit_code}
|
||||
else
|
||||
echo "No files with the \"py\" extension found"
|
||||
fi
|
||||
|
||||
- name: Upload artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: pylint_report
|
||||
path: pylint_report
|
||||
@ -0,0 +1,34 @@
|
||||
name: CI-nightly
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 22 * * *'
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 12
|
||||
- name: Build CVAT
|
||||
env:
|
||||
DJANGO_SU_NAME: "admin"
|
||||
DJANGO_SU_EMAIL: "admin@localhost.company"
|
||||
DJANGO_SU_PASSWORD: "12qwaszx"
|
||||
API_ABOUT_PAGE: "localhost:8080/api/v1/server/about"
|
||||
run: |
|
||||
docker-compose -f docker-compose.yml -f docker-compose.dev.yml -f ./tests/docker-compose.email.yml up -d --build
|
||||
/bin/bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${API_ABOUT_PAGE})" != "401" ]]; do sleep 5; done'
|
||||
docker exec -i cvat /bin/bash -c "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell"
|
||||
- name: End-to-end testing
|
||||
run: |
|
||||
cd ./tests
|
||||
npm ci
|
||||
npm run cypress:run:firefox
|
||||
- name: Uploading cypress screenshots as an artifact
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: cypress_screenshots
|
||||
path: ${{ github.workspace }}/tests/cypress/screenshots
|
||||
@ -1,57 +0,0 @@
|
||||
language: generic
|
||||
dist: focal
|
||||
|
||||
cache:
|
||||
npm: true
|
||||
directories:
|
||||
- ~/.cache
|
||||
|
||||
addons:
|
||||
firefox: 'latest'
|
||||
chrome: stable
|
||||
apt:
|
||||
packages:
|
||||
- libgconf-2-4
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
env:
|
||||
- CONTAINER_COVERAGE_DATA_DIR="/coverage_data"
|
||||
HOST_COVERAGE_DATA_DIR="${TRAVIS_BUILD_DIR}"
|
||||
DJANGO_SU_NAME="admin"
|
||||
DJANGO_SU_EMAIL="admin@localhost.company"
|
||||
DJANGO_SU_PASSWORD="12qwaszx"
|
||||
NODE_VERSION="12"
|
||||
API_ABOUT_PAGE="localhost:8080/api/v1/server/about"
|
||||
|
||||
before_install:
|
||||
- nvm install ${NODE_VERSION}
|
||||
|
||||
before_script:
|
||||
- chmod a+rwx ${HOST_COVERAGE_DATA_DIR}
|
||||
|
||||
script:
|
||||
- if [[ $TRAVIS_EVENT_TYPE == "cron" && $TRAVIS_BRANCH == "develop" ]];
|
||||
then
|
||||
docker-compose -f docker-compose.yml -f ./tests/docker-compose.email.yml up -d --build;
|
||||
bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' ${API_ABOUT_PAGE})" != "401" ]]; do sleep 5; done';
|
||||
docker exec -it cvat bash -ic "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell";
|
||||
cd ./tests && npm install && npm run cypress:run:firefox; exit $?;
|
||||
fi;
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml build
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'coverage run -a manage.py test cvat/apps utils/cli && mv .coverage ${CONTAINER_COVERAGE_DATA_DIR}'
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd cvat-data && npm install && cd ../cvat-core && npm install && npm run test && mv ./reports/coverage/lcov.info ${CONTAINER_COVERAGE_DATA_DIR} && chmod a+rwx ${CONTAINER_COVERAGE_DATA_DIR}/lcov.info'
|
||||
- docker-compose up -d
|
||||
- docker exec -it cvat bash -ic "echo \"from django.contrib.auth.models import User; User.objects.create_superuser('${DJANGO_SU_NAME}', '${DJANGO_SU_EMAIL}', '${DJANGO_SU_PASSWORD}')\" | python3 ~/manage.py shell"
|
||||
# End-to-end testing
|
||||
- npm install && npm run coverage
|
||||
- docker-compose up -d --build
|
||||
- cd ./tests && npm install && npx cypress run --headless --browser chrome
|
||||
- mv ./.nyc_output ../ && cd ..
|
||||
- npx nyc report --reporter=text-lcov >> ${HOST_COVERAGE_DATA_DIR}/lcov.info
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'cd ${CONTAINER_COVERAGE_DATA_DIR} && coveralls-lcov -v -n lcov.info > ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json'
|
||||
|
||||
after_success:
|
||||
# https://coveralls-python.readthedocs.io/en/latest/usage/multilang.html
|
||||
- docker-compose -f docker-compose.yml -f docker-compose.ci.yml run cvat_ci /bin/bash -c 'ln -s ${CONTAINER_COVERAGE_DATA_DIR}/.git . && ln -s ${CONTAINER_COVERAGE_DATA_DIR}/.coverage . && ln -s ${CONTAINER_COVERAGE_DATA_DIR}/coverage.json . && coveralls --merge=coverage.json'
|
||||
@ -0,0 +1,3 @@
|
||||
queue.type: persisted
|
||||
queue.max_bytes: 1gb
|
||||
queue.checkpoint.writes: 20
|
||||
@ -1,2 +1 @@
|
||||
src/*.js
|
||||
dist
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 303 KiB |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1 @@
|
||||
webpack.config.js
|
||||
@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
parserOptions: {
|
||||
parser: '@typescript-eslint/parser',
|
||||
ecmaVersion: 6,
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'import'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'airbnb-typescript/base',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/warnings',
|
||||
'plugin:import/typescript',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 0,
|
||||
'@typescript-eslint/indent': ['warn', 4],
|
||||
'no-plusplus': 0,
|
||||
'no-restricted-syntax': [
|
||||
0,
|
||||
{
|
||||
selector: 'ForOfStatement',
|
||||
},
|
||||
],
|
||||
'max-len': ['error', { code: 120 }],
|
||||
'no-continue': 0,
|
||||
'func-names': 0,
|
||||
'no-console': 0, // this rule deprecates console.log, console.warn etc. because 'it is not good in production code'
|
||||
'lines-between-class-members': 0,
|
||||
'import/prefer-default-export': 0, // works incorrect with interfaces
|
||||
'newline-per-chained-call': 0, // makes code uglier
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: {
|
||||
extensions: ['.ts', '.js', '.json'],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
dist
|
||||
@ -0,0 +1,48 @@
|
||||
# Module CVAT-CANVAS-3D
|
||||
|
||||
## Description
|
||||
|
||||
The CVAT module written in TypeScript language.
|
||||
It presents a canvas to viewing, drawing and editing of 3D annotations.
|
||||
|
||||
## Versioning
|
||||
|
||||
If you make changes in this package, please do following:
|
||||
|
||||
- After not important changes (typos, backward compatible bug fixes, refactoring) do: `npm version patch`
|
||||
- After changing API (backward compatible new features) do: `npm version minor`
|
||||
- After changing API (changes that break backward compatibility) do: `npm version major`
|
||||
|
||||
## Commands
|
||||
|
||||
- Building of the module from sources in the `dist` directory:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm run build -- --mode=development # without a minification
|
||||
```
|
||||
|
||||
### API Methods
|
||||
|
||||
```ts
|
||||
interface Canvas3d {
|
||||
html(): HTMLDivElement;
|
||||
setup(frameData: any): void;
|
||||
mode(): Mode;
|
||||
isAbleToChangeFrame(): boolean;
|
||||
render(): void;
|
||||
}
|
||||
```
|
||||
|
||||
### WEB
|
||||
|
||||
```js
|
||||
// Create an instance of a canvas
|
||||
const canvas = new window.canvas.Canvas3d();
|
||||
|
||||
console.log('Version ', window.canvas.CanvasVersion);
|
||||
console.log('Current mode is ', window.canvas.mode());
|
||||
|
||||
// Put canvas to a html container
|
||||
htmlContainer.appendChild(canvas.html());
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "cvat-canvas3d",
|
||||
"version": "0.0.1",
|
||||
"description": "Part of Computer Vision Annotation Tool which presents its canvas3D library",
|
||||
"main": "src/canvas3d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc && webpack --config ./webpack.config.js",
|
||||
"server": "nodemon --watch config --exec 'webpack-dev-server --config ./webpack.config.js --mode=development --open'"
|
||||
},
|
||||
"author": "Intel",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.5.5",
|
||||
"@babel/core": "^7.5.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.11.0",
|
||||
"@babel/preset-env": "^7.5.5",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@types/node": "^12.6.8",
|
||||
"@typescript-eslint/eslint-plugin": "^1.13.0",
|
||||
"@typescript-eslint/parser": "^1.13.0",
|
||||
"babel-loader": "^8.0.6",
|
||||
"css-loader": "^3.4.2",
|
||||
"dts-bundle-webpack": "^1.0.2",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint-config-airbnb-typescript": "^4.0.1",
|
||||
"eslint-config-typescript-recommended": "^1.4.17",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"node-sass": "^4.14.1",
|
||||
"nodemon": "^1.19.4",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"style-loader": "^1.0.0",
|
||||
"typescript": "^3.5.3",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-cli": "^3.3.6",
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/three": "^0.125.3",
|
||||
"camera-controls": "^1.25.3",
|
||||
"three": "^0.125.0"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
module.exports = {
|
||||
parser: false,
|
||||
plugins: {
|
||||
'postcss-preset-env': {
|
||||
browsers: '> 2.5%', // https://github.com/browserslist/browserslist
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -0,0 +1,79 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import pjson from '../../package.json';
|
||||
import { Canvas3dController, Canvas3dControllerImpl } from './canvas3dController';
|
||||
import {
|
||||
Canvas3dModel, Canvas3dModelImpl, Mode, DrawData, ViewType, MouseInteraction,
|
||||
} from './canvas3dModel';
|
||||
import {
|
||||
Canvas3dView, Canvas3dViewImpl, ViewsDOM, CAMERA_ACTION,
|
||||
} from './canvas3dView';
|
||||
import { Master } from './master';
|
||||
|
||||
const Canvas3dVersion = pjson.version;
|
||||
|
||||
interface Canvas3d {
|
||||
html(): ViewsDOM;
|
||||
setup(frameData: any): void;
|
||||
isAbleToChangeFrame(): boolean;
|
||||
mode(): Mode;
|
||||
render(): void;
|
||||
keyControls(keys: KeyboardEvent): void;
|
||||
mouseControls(type: string, event: MouseEvent): void;
|
||||
draw(drawData: DrawData): void;
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
class Canvas3dImpl implements Canvas3d {
|
||||
private model: Canvas3dModel & Master;
|
||||
private controller: Canvas3dController;
|
||||
private view: Canvas3dView;
|
||||
|
||||
public constructor() {
|
||||
this.model = new Canvas3dModelImpl();
|
||||
this.controller = new Canvas3dControllerImpl(this.model);
|
||||
this.view = new Canvas3dViewImpl(this.model, this.controller);
|
||||
}
|
||||
|
||||
public html(): ViewsDOM {
|
||||
return this.view.html();
|
||||
}
|
||||
|
||||
public keyControls(keys: KeyboardEvent): void {
|
||||
this.view.keyControls(keys);
|
||||
}
|
||||
|
||||
public mouseControls(type: MouseInteraction, event: MouseEvent): void {
|
||||
this.view.mouseControls(type, event);
|
||||
}
|
||||
|
||||
public render(): void {
|
||||
this.view.render();
|
||||
}
|
||||
|
||||
public draw(drawData: DrawData): void {
|
||||
this.model.draw(drawData);
|
||||
}
|
||||
|
||||
public setup(frameData: any): void {
|
||||
this.model.setup(frameData);
|
||||
}
|
||||
|
||||
public mode(): Mode {
|
||||
return this.model.mode;
|
||||
}
|
||||
|
||||
public isAbleToChangeFrame(): boolean {
|
||||
return this.model.isAbleToChangeFrame();
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.model.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
Canvas3dImpl as Canvas3d, Canvas3dVersion, ViewType, MouseInteraction, CAMERA_ACTION,
|
||||
};
|
||||
@ -0,0 +1,30 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { Canvas3dModel, Mode, DrawData } from './canvas3dModel';
|
||||
|
||||
export interface Canvas3dController {
|
||||
readonly drawData: DrawData;
|
||||
mode: Mode;
|
||||
}
|
||||
|
||||
export class Canvas3dControllerImpl implements Canvas3dController {
|
||||
private model: Canvas3dModel;
|
||||
|
||||
public constructor(model: Canvas3dModel) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
public set mode(value: Mode) {
|
||||
this.model.mode = value;
|
||||
}
|
||||
|
||||
public get mode(): Mode {
|
||||
return this.model.mode;
|
||||
}
|
||||
|
||||
public get drawData(): DrawData {
|
||||
return this.model.data.drawData;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,165 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { MasterImpl } from './master';
|
||||
|
||||
export interface Size {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Image {
|
||||
renderWidth: number;
|
||||
renderHeight: number;
|
||||
imageData: ImageData | CanvasImageSource;
|
||||
}
|
||||
|
||||
export interface DrawData {
|
||||
enabled: boolean;
|
||||
initialState?: any;
|
||||
redraw?: number;
|
||||
}
|
||||
|
||||
export enum FrameZoom {
|
||||
MIN = 0.1,
|
||||
MAX = 10,
|
||||
}
|
||||
|
||||
export enum ViewType {
|
||||
PERSPECTIVE = 'perspective',
|
||||
TOP = 'top',
|
||||
SIDE = 'side',
|
||||
FRONT = 'front',
|
||||
}
|
||||
|
||||
export enum MouseInteraction {
|
||||
CLICK = 'click',
|
||||
DOUBLE_CLICK = 'dblclick',
|
||||
HOVER = 'hover',
|
||||
}
|
||||
|
||||
export enum UpdateReasons {
|
||||
IMAGE_CHANGED = 'image_changed',
|
||||
OBJECTS_UPDATED = 'objects_updated',
|
||||
FITTED_CANVAS = 'fitted_canvas',
|
||||
DRAW = 'draw',
|
||||
SELECT = 'select',
|
||||
CANCEL = 'cancel',
|
||||
DATA_FAILED = 'data_failed',
|
||||
}
|
||||
|
||||
export enum Mode {
|
||||
IDLE = 'idle',
|
||||
DRAG = 'drag',
|
||||
RESIZE = 'resize',
|
||||
DRAW = 'draw',
|
||||
EDIT = 'edit',
|
||||
INTERACT = 'interact',
|
||||
}
|
||||
|
||||
export interface Canvas3dDataModel {
|
||||
canvasSize: Size;
|
||||
image: Image | null;
|
||||
imageID: number | null;
|
||||
imageOffset: number;
|
||||
imageSize: Size;
|
||||
drawData: DrawData;
|
||||
mode: Mode;
|
||||
exception: Error | null;
|
||||
}
|
||||
|
||||
export interface Canvas3dModel {
|
||||
mode: Mode;
|
||||
data: Canvas3dDataModel;
|
||||
setup(frameData: any): void;
|
||||
isAbleToChangeFrame(): boolean;
|
||||
draw(drawData: DrawData): void;
|
||||
cancel(): void;
|
||||
}
|
||||
|
||||
export class Canvas3dModelImpl extends MasterImpl implements Canvas3dModel {
|
||||
public data: Canvas3dDataModel;
|
||||
|
||||
public constructor() {
|
||||
super();
|
||||
this.data = {
|
||||
canvasSize: {
|
||||
height: 0,
|
||||
width: 0,
|
||||
},
|
||||
image: null,
|
||||
imageID: null,
|
||||
imageOffset: 0,
|
||||
imageSize: {
|
||||
height: 0,
|
||||
width: 0,
|
||||
},
|
||||
drawData: {
|
||||
enabled: false,
|
||||
initialState: null,
|
||||
},
|
||||
mode: Mode.IDLE,
|
||||
exception: null,
|
||||
};
|
||||
}
|
||||
|
||||
public setup(frameData: any): void {
|
||||
if (this.data.imageID !== frameData.number) {
|
||||
this.data.imageID = frameData.number;
|
||||
frameData
|
||||
.data((): void => {
|
||||
this.data.image = null;
|
||||
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.image = data;
|
||||
this.notify(UpdateReasons.IMAGE_CHANGED);
|
||||
})
|
||||
.catch((exception: any): void => {
|
||||
this.data.exception = exception;
|
||||
this.notify(UpdateReasons.DATA_FAILED);
|
||||
throw exception;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public set mode(value: Mode) {
|
||||
this.data.mode = value;
|
||||
}
|
||||
|
||||
public get mode(): Mode {
|
||||
return this.data.mode;
|
||||
}
|
||||
|
||||
public isAbleToChangeFrame(): boolean {
|
||||
const isUnable = [Mode.DRAG, Mode.EDIT, Mode.RESIZE, Mode.INTERACT].includes(this.data.mode)
|
||||
|| (this.data.mode === Mode.DRAW && typeof this.data.drawData.redraw === 'number');
|
||||
|
||||
return !isUnable;
|
||||
}
|
||||
|
||||
public draw(drawData: DrawData): void {
|
||||
if (drawData.enabled && this.data.drawData.enabled) {
|
||||
throw new Error('Drawing has been already started');
|
||||
}
|
||||
this.data.drawData.enabled = drawData.enabled;
|
||||
this.data.mode = Mode.DRAW;
|
||||
|
||||
this.notify(UpdateReasons.DRAW);
|
||||
}
|
||||
|
||||
public cancel(): void {
|
||||
this.notify(UpdateReasons.CANCEL);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,437 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import * as THREE from 'three';
|
||||
import { PCDLoader } from 'three/examples/jsm/loaders/PCDLoader';
|
||||
import CameraControls from 'camera-controls';
|
||||
import { Canvas3dController } from './canvas3dController';
|
||||
import { Listener, Master } from './master';
|
||||
import CONST from './consts';
|
||||
import {
|
||||
Canvas3dModel, UpdateReasons, Mode, DrawData, ViewType, MouseInteraction,
|
||||
} from './canvas3dModel';
|
||||
import { CuboidModel } from './cuboid';
|
||||
|
||||
export interface Canvas3dView {
|
||||
html(): ViewsDOM;
|
||||
render(): void;
|
||||
keyControls(keys: KeyboardEvent): void;
|
||||
mouseControls(type: MouseInteraction, event: MouseEvent): void;
|
||||
}
|
||||
|
||||
export enum CAMERA_ACTION {
|
||||
ZOOM_IN = 'KeyI',
|
||||
MOVE_UP = 'KeyU',
|
||||
MOVE_DOWN = 'KeyO',
|
||||
MOVE_LEFT = 'KeyJ',
|
||||
ZOOM_OUT = 'KeyK',
|
||||
MOVE_RIGHT = 'KeyL',
|
||||
TILT_UP = 'ArrowUp',
|
||||
TILT_DOWN = 'ArrowDown',
|
||||
ROTATE_RIGHT = 'ArrowRight',
|
||||
ROTATE_LEFT = 'ArrowLeft',
|
||||
}
|
||||
|
||||
export interface RayCast {
|
||||
renderer: THREE.Raycaster;
|
||||
mouseVector: THREE.Vector2;
|
||||
}
|
||||
|
||||
export interface Views {
|
||||
perspective: RenderView;
|
||||
top: RenderView;
|
||||
side: RenderView;
|
||||
front: RenderView;
|
||||
}
|
||||
|
||||
export interface CubeObject {
|
||||
perspective: THREE.Mesh;
|
||||
top: THREE.Mesh;
|
||||
side: THREE.Mesh;
|
||||
front: THREE.Mesh;
|
||||
}
|
||||
|
||||
export interface RenderView {
|
||||
renderer: THREE.WebGLRenderer;
|
||||
scene: THREE.Scene;
|
||||
camera?: THREE.PerspectiveCamera | THREE.OrthographicCamera;
|
||||
controls?: CameraControls;
|
||||
rayCaster?: RayCast;
|
||||
}
|
||||
|
||||
export interface ViewsDOM {
|
||||
perspective: HTMLCanvasElement;
|
||||
top: HTMLCanvasElement;
|
||||
side: HTMLCanvasElement;
|
||||
front: HTMLCanvasElement;
|
||||
}
|
||||
|
||||
export class Canvas3dViewImpl implements Canvas3dView, Listener {
|
||||
private controller: Canvas3dController;
|
||||
private views: Views;
|
||||
private clock: THREE.Clock;
|
||||
private speed: number;
|
||||
private cube: CuboidModel;
|
||||
private highlighted: boolean;
|
||||
private selected: CubeObject;
|
||||
|
||||
private set mode(value: Mode) {
|
||||
this.controller.mode = value;
|
||||
}
|
||||
|
||||
private get mode(): Mode {
|
||||
return this.controller.mode;
|
||||
}
|
||||
|
||||
public constructor(model: Canvas3dModel & Master, controller: Canvas3dController) {
|
||||
this.controller = controller;
|
||||
this.clock = new THREE.Clock();
|
||||
this.speed = CONST.MOVEMENT_FACTOR;
|
||||
this.cube = new CuboidModel();
|
||||
this.highlighted = false;
|
||||
this.selected = this.cube;
|
||||
|
||||
this.views = {
|
||||
perspective: {
|
||||
renderer: new THREE.WebGLRenderer({ antialias: true }),
|
||||
scene: new THREE.Scene(),
|
||||
rayCaster: {
|
||||
renderer: new THREE.Raycaster(),
|
||||
mouseVector: new THREE.Vector2(),
|
||||
},
|
||||
},
|
||||
top: {
|
||||
renderer: new THREE.WebGLRenderer({ antialias: true }),
|
||||
scene: new THREE.Scene(),
|
||||
},
|
||||
side: {
|
||||
renderer: new THREE.WebGLRenderer({ antialias: true }),
|
||||
scene: new THREE.Scene(),
|
||||
},
|
||||
front: {
|
||||
renderer: new THREE.WebGLRenderer({ antialias: true }),
|
||||
scene: new THREE.Scene(),
|
||||
},
|
||||
};
|
||||
CameraControls.install({ THREE });
|
||||
|
||||
this.mode = Mode.IDLE;
|
||||
|
||||
Object.keys(this.views).forEach((view: string): void => {
|
||||
this.views[view as keyof Views].scene.background = new THREE.Color(0x000000);
|
||||
});
|
||||
|
||||
const viewSize = CONST.ZOOM_FACTOR;
|
||||
const height = window.innerHeight;
|
||||
const width = window.innerWidth;
|
||||
const aspectRatio = window.innerWidth / window.innerHeight;
|
||||
|
||||
// setting up the camera and adding it in the scene
|
||||
this.views.perspective.camera = new THREE.PerspectiveCamera(50, aspectRatio, 1, 500);
|
||||
this.views.perspective.camera.position.set(-15, 0, 4);
|
||||
this.views.perspective.camera.up.set(0, 0, 1);
|
||||
this.views.perspective.camera.lookAt(10, 0, 0);
|
||||
|
||||
this.views.top.camera = new THREE.OrthographicCamera(
|
||||
(-aspectRatio * viewSize) / 2 - 2,
|
||||
(aspectRatio * viewSize) / 2 + 2,
|
||||
viewSize / 2 + 2,
|
||||
-viewSize / 2 - 2,
|
||||
-10,
|
||||
10,
|
||||
);
|
||||
|
||||
this.views.side.camera = new THREE.OrthographicCamera(
|
||||
(-aspectRatio * viewSize) / 2,
|
||||
(aspectRatio * viewSize) / 2,
|
||||
viewSize / 2,
|
||||
-viewSize / 2,
|
||||
-10,
|
||||
10,
|
||||
);
|
||||
this.views.side.camera.position.set(0, 5, 0);
|
||||
this.views.side.camera.lookAt(0, 0, 0);
|
||||
this.views.side.camera.up.set(0, 0, 1);
|
||||
|
||||
this.views.front.camera = new THREE.OrthographicCamera(
|
||||
(-aspectRatio * viewSize) / 2,
|
||||
(aspectRatio * viewSize) / 2,
|
||||
viewSize / 2,
|
||||
-viewSize / 2,
|
||||
-10,
|
||||
10,
|
||||
);
|
||||
this.views.front.camera.position.set(-7, 0, 0);
|
||||
this.views.front.camera.up.set(0, 0, 1);
|
||||
this.views.front.camera.lookAt(0, 0, 0);
|
||||
|
||||
Object.keys(this.views).forEach((view: string): void => {
|
||||
const viewType = this.views[view as keyof Views];
|
||||
viewType.renderer.setSize(width, height);
|
||||
if (view !== ViewType.PERSPECTIVE) {
|
||||
viewType.controls = new CameraControls(viewType.camera, viewType.renderer.domElement);
|
||||
viewType.controls.mouseButtons.left = CameraControls.ACTION.NONE;
|
||||
viewType.controls.mouseButtons.right = CameraControls.ACTION.NONE;
|
||||
} else {
|
||||
viewType.controls = new CameraControls(viewType.camera, viewType.renderer.domElement);
|
||||
}
|
||||
viewType.controls.minDistance = CONST.MIN_DISTANCE;
|
||||
viewType.controls.maxDistance = CONST.MAX_DISTANCE;
|
||||
});
|
||||
|
||||
model.subscribe(this);
|
||||
}
|
||||
|
||||
public notify(model: Canvas3dModel & Master, reason: UpdateReasons): void {
|
||||
if (reason === UpdateReasons.IMAGE_CHANGED) {
|
||||
const loader = new PCDLoader();
|
||||
this.clearScene();
|
||||
const objectURL = URL.createObjectURL(model.data.image.imageData);
|
||||
loader.load(objectURL, this.addScene.bind(this));
|
||||
URL.revokeObjectURL(objectURL);
|
||||
const event: CustomEvent = new CustomEvent('canvas.setup');
|
||||
this.views.perspective.renderer.domElement.dispatchEvent(event);
|
||||
} else if (reason === UpdateReasons.DRAW) {
|
||||
const data: DrawData = this.controller.drawData;
|
||||
if (data.enabled && this.mode === Mode.IDLE) {
|
||||
this.mode = Mode.DRAW;
|
||||
this.cube = new CuboidModel();
|
||||
} else if (this.mode !== Mode.IDLE) {
|
||||
this.cube = new CuboidModel();
|
||||
}
|
||||
} else if (reason === UpdateReasons.CANCEL) {
|
||||
if (this.mode === Mode.DRAW) {
|
||||
this.controller.drawData.enabled = false;
|
||||
Object.keys(this.views).forEach((view: string): void => {
|
||||
this.views[view as keyof Views].scene.children[0].remove(this.cube[view as keyof Views]);
|
||||
});
|
||||
}
|
||||
this.mode = Mode.IDLE;
|
||||
const event: CustomEvent = new CustomEvent('canvas.canceled');
|
||||
this.views.perspective.renderer.domElement.dispatchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private clearScene(): void {
|
||||
Object.keys(this.views).forEach((view: string): void => {
|
||||
this.views[view as keyof Views].scene.children = [];
|
||||
});
|
||||
}
|
||||
|
||||
private addScene(points: any): void {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
points.material.size = 0.08;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
points.material.color = new THREE.Color(0x0000ff);
|
||||
const sphereCenter = points.geometry.boundingSphere.center;
|
||||
const { radius } = points.geometry.boundingSphere;
|
||||
const xRange = -radius / 2 < this.views.perspective.camera.position.x - sphereCenter.x
|
||||
&& radius / 2 > this.views.perspective.camera.position.x - sphereCenter.x;
|
||||
const yRange = -radius / 2 < this.views.perspective.camera.position.y - sphereCenter.y
|
||||
&& radius / 2 > this.views.perspective.camera.position.y - sphereCenter.y;
|
||||
const zRange = -radius / 2 < this.views.perspective.camera.position.z - sphereCenter.z
|
||||
&& radius / 2 > this.views.perspective.camera.position.z - sphereCenter.z;
|
||||
let newX = 0;
|
||||
let newY = 0;
|
||||
let newZ = 0;
|
||||
if (!xRange) {
|
||||
newX = sphereCenter.x;
|
||||
}
|
||||
if (!yRange) {
|
||||
newY = sphereCenter.y;
|
||||
}
|
||||
if (!zRange) {
|
||||
newZ = sphereCenter.z;
|
||||
}
|
||||
if (newX || newY || newZ) {
|
||||
this.positionAllViews(newX, newY, newZ);
|
||||
}
|
||||
this.views.perspective.scene.add(points);
|
||||
this.views.top.scene.add(points.clone());
|
||||
this.views.side.scene.add(points.clone());
|
||||
this.views.front.scene.add(points.clone());
|
||||
}
|
||||
|
||||
private positionAllViews(x: number, y: number, z: number): void {
|
||||
this.views.perspective.controls.setLookAt(x - 8, y - 8, z + 3, x, y, z, false);
|
||||
this.views.top.controls.setLookAt(x, y, z + 8, x, y, z, false);
|
||||
this.views.side.controls.setLookAt(x, y + 8, z, x, y, z, false);
|
||||
this.views.front.controls.setLookAt(x + 8, y, z, x, y, z, false);
|
||||
}
|
||||
|
||||
private static resizeRendererToDisplaySize(viewName: string, view: RenderView): void {
|
||||
const { camera, renderer } = view;
|
||||
const canvas = renderer.domElement;
|
||||
const width = canvas.parentElement.clientWidth;
|
||||
const height = canvas.parentElement.clientHeight;
|
||||
const needResize = canvas.clientWidth !== width || canvas.clientHeight !== height;
|
||||
if (needResize) {
|
||||
if (camera instanceof THREE.PerspectiveCamera) {
|
||||
camera.aspect = width / height;
|
||||
} else {
|
||||
const topViewFactor = 0; // viewName === ViewType.TOP ? 2 : 0;
|
||||
const viewSize = CONST.ZOOM_FACTOR;
|
||||
const aspectRatio = width / height;
|
||||
if (!(camera instanceof THREE.PerspectiveCamera)) {
|
||||
camera.left = (-aspectRatio * viewSize) / 2 - topViewFactor;
|
||||
camera.right = (aspectRatio * viewSize) / 2 + topViewFactor;
|
||||
camera.top = viewSize / 2 + topViewFactor;
|
||||
camera.bottom = -viewSize / 2 - topViewFactor;
|
||||
}
|
||||
camera.near = -10;
|
||||
camera.far = 10;
|
||||
}
|
||||
view.renderer.setSize(width, height);
|
||||
view.camera.updateProjectionMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
private renderRayCaster = (viewType: RenderView): void => {
|
||||
viewType.rayCaster.renderer.setFromCamera(viewType.rayCaster.mouseVector, viewType.camera);
|
||||
if (this.mode === Mode.DRAW) {
|
||||
const intersects = this.views.perspective.rayCaster.renderer.intersectObjects(
|
||||
this.views.perspective.scene.children,
|
||||
false,
|
||||
);
|
||||
if (intersects.length > 0) {
|
||||
this.views.perspective.scene.children[0].add(this.cube.perspective);
|
||||
const newPoints = intersects[0].point;
|
||||
this.cube.perspective.position.copy(newPoints);
|
||||
}
|
||||
} else if (this.mode === Mode.IDLE) {
|
||||
const intersects = this.views.perspective.rayCaster.renderer.intersectObjects(
|
||||
this.views.perspective.scene.children[0].children,
|
||||
false,
|
||||
);
|
||||
if (intersects.length !== 0) {
|
||||
this.views.perspective.scene.children[0].children.forEach((sceneItem: THREE.Mesh): void => {
|
||||
if (this.selected.perspective !== sceneItem) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
sceneItem.material.color = new THREE.Color(0xff0000);
|
||||
}
|
||||
});
|
||||
const selectedObject = intersects[0].object as THREE.Mesh;
|
||||
if (this.selected.perspective !== selectedObject) {
|
||||
selectedObject.material.color = new THREE.Color(0xffff00);
|
||||
this.highlighted = true;
|
||||
}
|
||||
} else {
|
||||
if (this.highlighted) {
|
||||
this.views.perspective.scene.children[0].children.forEach((sceneItem: THREE.Mesh): void => {
|
||||
if (this.selected.perspective !== sceneItem) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
sceneItem.material.color = new THREE.Color(0xff0000);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.highlighted = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public render(): void {
|
||||
Object.keys(this.views).forEach((view: string): void => {
|
||||
const viewType = this.views[view as keyof Views];
|
||||
Canvas3dViewImpl.resizeRendererToDisplaySize(view, viewType);
|
||||
viewType.controls.update(this.clock.getDelta());
|
||||
viewType.renderer.render(viewType.scene, viewType.camera);
|
||||
if (view === ViewType.PERSPECTIVE && viewType.scene.children.length !== 0) {
|
||||
this.renderRayCaster(viewType);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public keyControls(key: any): void {
|
||||
const { controls } = this.views.perspective;
|
||||
switch (key.code) {
|
||||
case CAMERA_ACTION.ROTATE_RIGHT:
|
||||
controls.rotate(0.1 * THREE.MathUtils.DEG2RAD * this.speed, 0, true);
|
||||
break;
|
||||
case CAMERA_ACTION.ROTATE_LEFT:
|
||||
controls.rotate(-0.1 * THREE.MathUtils.DEG2RAD * this.speed, 0, true);
|
||||
break;
|
||||
case CAMERA_ACTION.TILT_UP:
|
||||
controls.rotate(0, -0.05 * THREE.MathUtils.DEG2RAD * this.speed, true);
|
||||
break;
|
||||
case CAMERA_ACTION.TILT_DOWN:
|
||||
controls.rotate(0, 0.05 * THREE.MathUtils.DEG2RAD * this.speed, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (key.altKey === true) {
|
||||
switch (key.code) {
|
||||
case CAMERA_ACTION.ZOOM_IN:
|
||||
controls.dolly(CONST.DOLLY_FACTOR, true);
|
||||
break;
|
||||
case CAMERA_ACTION.ZOOM_OUT:
|
||||
controls.dolly(-CONST.DOLLY_FACTOR, true);
|
||||
break;
|
||||
case CAMERA_ACTION.MOVE_LEFT:
|
||||
controls.truck(-0.01 * this.speed, 0, true);
|
||||
break;
|
||||
case CAMERA_ACTION.MOVE_RIGHT:
|
||||
controls.truck(0.01 * this.speed, 0, true);
|
||||
break;
|
||||
case CAMERA_ACTION.MOVE_DOWN:
|
||||
controls.truck(0, -0.01 * this.speed, true);
|
||||
break;
|
||||
case CAMERA_ACTION.MOVE_UP:
|
||||
controls.truck(0, 0.01 * this.speed, true);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public mouseControls(type: MouseInteraction, event: MouseEvent): void {
|
||||
event.preventDefault();
|
||||
if (type === MouseInteraction.DOUBLE_CLICK && this.mode === Mode.DRAW) {
|
||||
this.controller.drawData.enabled = false;
|
||||
this.mode = Mode.IDLE;
|
||||
const cancelEvent: CustomEvent = new CustomEvent('canvas.canceled');
|
||||
this.views.perspective.renderer.domElement.dispatchEvent(cancelEvent);
|
||||
} else {
|
||||
const canvas = this.views.perspective.renderer.domElement;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const { mouseVector } = this.views.perspective.rayCaster;
|
||||
mouseVector.x = ((event.clientX - (canvas.offsetLeft + rect.left)) / canvas.clientWidth) * 2 - 1;
|
||||
mouseVector.y = -((event.clientY - (canvas.offsetTop + rect.top)) / canvas.clientHeight) * 2 + 1;
|
||||
|
||||
if (type === MouseInteraction.CLICK && this.mode === Mode.IDLE) {
|
||||
const intersects = this.views.perspective.rayCaster.renderer.intersectObjects(
|
||||
this.views.perspective.scene.children[0].children,
|
||||
false,
|
||||
);
|
||||
if (intersects.length !== 0) {
|
||||
this.views.perspective.scene.children[0].children.forEach((sceneItem: THREE.Mesh): void => {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
sceneItem.material.color = new THREE.Color(0xff0000);
|
||||
});
|
||||
const selectedObject = intersects[0].object;
|
||||
selectedObject.material.color = new THREE.Color(0x00ffff);
|
||||
Object.keys(this.views).forEach((view: string): void => {
|
||||
if (view !== ViewType.PERSPECTIVE) {
|
||||
this.views[view as keyof Views].scene.children[0].children = [selectedObject.clone()];
|
||||
this.views[view as keyof Views].controls.fitToBox(selectedObject, false);
|
||||
this.views[view as keyof Views].controls.zoom(view === ViewType.TOP ? -5 : -5, false);
|
||||
}
|
||||
this.views[view as keyof Views].scene.background = new THREE.Color(0x000000);
|
||||
});
|
||||
this.selected.perspective = selectedObject as THREE.Mesh;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public html(): ViewsDOM {
|
||||
return {
|
||||
perspective: this.views.perspective.renderer.domElement,
|
||||
top: this.views.top.renderer.domElement,
|
||||
side: this.views.side.renderer.domElement,
|
||||
front: this.views.front.renderer.domElement,
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
const BASE_GRID_WIDTH = 2;
|
||||
const MOVEMENT_FACTOR = 200;
|
||||
const DOLLY_FACTOR = 5;
|
||||
const MAX_DISTANCE = 100;
|
||||
const MIN_DISTANCE = 0;
|
||||
const ZOOM_FACTOR = 7;
|
||||
|
||||
export default {
|
||||
BASE_GRID_WIDTH,
|
||||
MOVEMENT_FACTOR,
|
||||
DOLLY_FACTOR,
|
||||
MAX_DISTANCE,
|
||||
MIN_DISTANCE,
|
||||
ZOOM_FACTOR,
|
||||
};
|
||||
@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
import * as THREE from 'three';
|
||||
|
||||
export class CuboidModel {
|
||||
public perspective: THREE.Mesh;
|
||||
public top: THREE.Mesh;
|
||||
public side: THREE.Mesh;
|
||||
public front: THREE.Mesh;
|
||||
|
||||
public constructor() {
|
||||
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
||||
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });
|
||||
this.perspective = new THREE.Mesh(geometry, material);
|
||||
this.top = new THREE.Mesh(geometry, material);
|
||||
this.side = new THREE.Mesh(geometry, material);
|
||||
this.front = new THREE.Mesh(geometry, material);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
export interface Master {
|
||||
subscribe(listener: Listener): void;
|
||||
unsubscribe(listener: Listener): void;
|
||||
unsubscribeAll(): void;
|
||||
notify(reason: string): void;
|
||||
}
|
||||
|
||||
export interface Listener {
|
||||
notify(master: Master, reason: string): void;
|
||||
}
|
||||
|
||||
export class MasterImpl implements Master {
|
||||
private listeners: Listener[];
|
||||
|
||||
public constructor() {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
public subscribe(listener: Listener): void {
|
||||
this.listeners.push(listener);
|
||||
}
|
||||
|
||||
public unsubscribe(listener: Listener): void {
|
||||
for (let i = 0; i < this.listeners.length; i++) {
|
||||
if (this.listeners[i] === listener) {
|
||||
this.listeners.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public unsubscribeAll(): void {
|
||||
this.listeners = [];
|
||||
}
|
||||
|
||||
public notify(reason: string): void {
|
||||
for (const listener of this.listeners) {
|
||||
listener.notify(this, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"emitDeclarationOnly": true,
|
||||
"module": "es6",
|
||||
"target": "es6",
|
||||
"noImplicitAny": true,
|
||||
"preserveConstEnums": true,
|
||||
"declaration": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "node",
|
||||
"declarationDir": "dist/declaration",
|
||||
"paths": {
|
||||
"cvat-canvas.node": ["dist/cvat-canvas3d.node"]
|
||||
}
|
||||
},
|
||||
"include": ["src/typescript/*.ts"]
|
||||
}
|
||||
@ -0,0 +1,138 @@
|
||||
// Copyright (C) 2021 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const path = require('path');
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const DtsBundleWebpack = require('dts-bundle-webpack');
|
||||
|
||||
const nodeConfig = {
|
||||
target: 'node',
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
entry: './src/typescript/canvas3d.ts',
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: 'cvat-canvas3d.node.js',
|
||||
library: 'canvas3d',
|
||||
libraryTarget: 'commonjs',
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js', '.json'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
],
|
||||
presets: [['@babel/preset-env'], ['@babel/typescript']],
|
||||
sourceType: 'unambiguous',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(css|scss)$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 2,
|
||||
},
|
||||
},
|
||||
'postcss-loader',
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new DtsBundleWebpack({
|
||||
name: 'cvat-canvas3d.node',
|
||||
main: 'dist/declaration/src/typescript/canvas3d.d.ts',
|
||||
out: '../cvat-canvas3d.node.d.ts',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
const webConfig = {
|
||||
target: 'web',
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
entry: {
|
||||
'cvat-canvas3d': './src/typescript/canvas3d.ts',
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: '[name].[contenthash].js',
|
||||
library: 'canvas3d',
|
||||
libraryTarget: 'window',
|
||||
},
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'dist'),
|
||||
compress: false,
|
||||
inline: true,
|
||||
port: 3000,
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js', '.json'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
exclude: /node_modules/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
plugins: ['@babel/plugin-proposal-class-properties'],
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: '> 2.5%', // https://github.com/browserslist/browserslist
|
||||
},
|
||||
],
|
||||
['@babel/typescript'],
|
||||
],
|
||||
sourceType: 'unambiguous',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
importLoaders: 2,
|
||||
},
|
||||
},
|
||||
'postcss-loader',
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new DtsBundleWebpack({
|
||||
name: 'cvat-canvas3d',
|
||||
main: 'dist/declaration/src/typescript/canvas3d.d.ts',
|
||||
out: '../cvat-canvas3d.d.ts',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = [webConfig, nodeConfig];
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,121 +0,0 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Setup mock for a server
|
||||
jest.mock('../../src/server-proxy', () => {
|
||||
const mock = require('../mocks/server-proxy.mock');
|
||||
return mock;
|
||||
});
|
||||
|
||||
const AnnotationsFilter = require('../../src/annotations-filter');
|
||||
// Initialize api
|
||||
window.cvat = require('../../src/api');
|
||||
|
||||
// Test cases
|
||||
describe('Feature: toJSONQuery', () => {
|
||||
test('convert filters to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
const [groups, query] = annotationsFilter.toJSONQuery([]);
|
||||
expect(Array.isArray(groups)).toBeTruthy();
|
||||
expect(typeof query).toBe('string');
|
||||
});
|
||||
|
||||
test('convert empty fitlers to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
const [, query] = annotationsFilter.toJSONQuery([]);
|
||||
expect(query).toBe('$.objects[*].clientID');
|
||||
});
|
||||
|
||||
test('convert wrong fitlers (empty string) to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
expect(() => {
|
||||
annotationsFilter.toJSONQuery(['']);
|
||||
}).toThrow(window.cvat.exceptions.ArgumentError);
|
||||
});
|
||||
|
||||
test('convert wrong fitlers (wrong number argument) to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
expect(() => {
|
||||
annotationsFilter.toJSONQuery(1);
|
||||
}).toThrow(window.cvat.exceptions.ArgumentError);
|
||||
});
|
||||
|
||||
test('convert wrong fitlers (wrong array argument) to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
expect(() => {
|
||||
annotationsFilter.toJSONQuery(['clientID ==6', 1]);
|
||||
}).toThrow(window.cvat.exceptions.ArgumentError);
|
||||
});
|
||||
|
||||
test('convert wrong filters (wrong expression) to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
expect(() => {
|
||||
annotationsFilter.toJSONQuery(['clientID=5']);
|
||||
}).toThrow(window.cvat.exceptions.ArgumentError);
|
||||
});
|
||||
|
||||
test('convert filters to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
const [groups, query] = annotationsFilter.toJSONQuery(['clientID==5 & shape=="rectangle" & label==["car"]']);
|
||||
expect(groups).toEqual([['clientID==5', '&', 'shape=="rectangle"', '&', 'label==["car"]']]);
|
||||
expect(query).toBe('$.objects[?((@.clientID==5&@.shape=="rectangle"&@.label==["car"]))].clientID');
|
||||
});
|
||||
|
||||
test('convert filters to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
const [groups, query] = annotationsFilter.toJSONQuery(['label=="car" | width >= height & type=="track"']);
|
||||
expect(groups).toEqual([['label=="car"', '|', 'width >= height', '&', 'type=="track"']]);
|
||||
expect(query).toBe('$.objects[?((@.label=="car"|@.width>=@.height&@.type=="track"))].clientID');
|
||||
});
|
||||
|
||||
test('convert filters to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
const [groups, query] = annotationsFilter.toJSONQuery([
|
||||
'label=="person" & attr["Attribute 1"] ==attr["Attribute 2"]',
|
||||
]);
|
||||
expect(groups).toEqual([['label=="person"', '&', 'attr["Attribute 1"] ==attr["Attribute 2"]']]);
|
||||
expect(query).toBe('$.objects[?((@.label=="person"&@.attr["Attribute 1"]==@.attr["Attribute 2"]))].clientID');
|
||||
});
|
||||
|
||||
test('convert filters to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
const [groups, query] = annotationsFilter.toJSONQuery([
|
||||
'label=="car" & attr["parked"]==true',
|
||||
'label=="pedestrian" & width > 150',
|
||||
]);
|
||||
expect(groups).toEqual([
|
||||
['label=="car"', '&', 'attr["parked"]==true'],
|
||||
'|',
|
||||
['label=="pedestrian"', '&', 'width > 150'],
|
||||
]);
|
||||
expect(query).toBe(
|
||||
'$.objects[?((@.label=="car"&@.attr["parked"]==true)|(@.label=="pedestrian"&@.width>150))].clientID',
|
||||
);
|
||||
});
|
||||
|
||||
test('convert filters to a json query', () => {
|
||||
const annotationsFilter = new AnnotationsFilter();
|
||||
const [groups, query] = annotationsFilter.toJSONQuery([
|
||||
// eslint-disable-next-line
|
||||
'(( label==["car \\"mazda\\""]) & (attr["sunglass ( help ) es"]==true | (width > 150 | height > 150 & (clientID == serverID))))) ',
|
||||
]);
|
||||
expect(groups).toEqual([
|
||||
[
|
||||
[
|
||||
['label==["car `mazda`"]'],
|
||||
'&',
|
||||
[
|
||||
'attr["sunglass ( help ) es"]==true',
|
||||
'|',
|
||||
['width > 150', '|', 'height > 150', '&', ['clientID == serverID']],
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
expect(query).toBe(
|
||||
// eslint-disable-next-line
|
||||
'$.objects[?((((@.label==["car `mazda`"])&(@.attr["sunglass ( help ) es"]==true|(@.width>150|@.height>150&(@.clientID==serverID))))))].clientID',
|
||||
);
|
||||
});
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,10 @@
|
||||
<!--
|
||||
The file has been downloaded from: https://icon-icons.com/ru/%D0%B7%D0%BD%D0%B0%D1%87%D0%BE%D0%BA/%D0%92-%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82%D0%B5-OpenCV/132129
|
||||
License: Attribution 4.0 International (CC BY 4.0) https://creativecommons.org/licenses/by/4.0/
|
||||
The file has been modified
|
||||
-->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="40" height="40">
|
||||
<g style="transform: scale(0.078)">
|
||||
<path d="M148.6458282,81.0641403C191.8570709-0.3458452,307.612915-4.617764,356.5062561,73.3931732c37.8880615,60.4514771,13.7960815,135.4847717-41.8233948,167.7876129l-36.121521-62.5643005c22.1270447-12.8510284,31.7114563-42.7013397,16.6385498-66.750618c-19.4511414-31.034935-65.5021057-29.3354645-82.692749,3.0517044c-12.7206879,23.9658356-2.6391449,51.5502472,18.3088379,63.7294922l-36.1482544,62.6105804C142.0118256,210.643219,116.6704254,141.3057709,148.6458282,81.0641403z M167.9667206,374.4708557c-0.0435791,24.2778625-18.934967,46.8978271-46.092804,47.9000549c-36.6418304,1.3522339-61.0877724-37.6520386-43.8971252-70.0392151c13.2918015-25.0418091,43.8297424-31.7192383,65.9928284-19.1222839l36.2165222-62.7288513c-55.7241974-31.7991638-132.6246796-15.0146027-166.0706635,47.9976501c-43.2111893,81.4099731,18.2372913,179.4530945,110.3418884,176.0540161c68.1375427-2.5146179,115.5750122-59.1652527,115.8612366-120.0613708H167.9667206z M451.714386,270.7571411l-36.1215515,62.5642395c22.2027588,12.816864,31.8418274,42.7249451,16.744751,66.8127441c-19.4511414,31.0349426-65.5021057,29.3354797-82.692688-3.0516968c-12.742218-24.0063782-2.6048279-51.643219,18.4154358-63.7908325l-36.1482544-62.6105652c-52.7280579,30.5827942-78.1254272,99.9726562-46.128479,160.2548218c43.2111816,81.4099731,158.9670105,85.6818848,207.8603821,7.6710205C531.5561523,378.1168213,507.4096069,303.0259705,451.714386,270.7571411z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@ -1,170 +0,0 @@
|
||||
// Copyright (C) 2020 Intel Corporation
|
||||
//
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import Select, { SelectValue, LabeledValue } from 'antd/lib/select';
|
||||
import Title from 'antd/lib/typography/Title';
|
||||
import Text from 'antd/lib/typography/Text';
|
||||
import Paragraph from 'antd/lib/typography/Paragraph';
|
||||
import Tooltip from 'antd/lib/tooltip';
|
||||
import Modal from 'antd/lib/modal';
|
||||
import { FilterOutlined } from '@ant-design/icons';
|
||||
|
||||
import {
|
||||
changeAnnotationsFilters as changeAnnotationsFiltersAction,
|
||||
fetchAnnotationsAsync,
|
||||
} from 'actions/annotation-actions';
|
||||
import { CombinedState } from 'reducers/interfaces';
|
||||
|
||||
interface StateToProps {
|
||||
annotationsFilters: string[];
|
||||
annotationsFiltersHistory: string[];
|
||||
searchForwardShortcut: string;
|
||||
searchBackwardShortcut: string;
|
||||
}
|
||||
|
||||
interface DispatchToProps {
|
||||
changeAnnotationsFilters(value: SelectValue): void;
|
||||
}
|
||||
|
||||
function mapStateToProps(state: CombinedState): StateToProps {
|
||||
const {
|
||||
annotation: {
|
||||
annotations: { filters: annotationsFilters, filtersHistory: annotationsFiltersHistory },
|
||||
},
|
||||
shortcuts: { normalizedKeyMap },
|
||||
} = state;
|
||||
|
||||
return {
|
||||
annotationsFilters,
|
||||
annotationsFiltersHistory,
|
||||
searchForwardShortcut: normalizedKeyMap.SEARCH_FORWARD,
|
||||
searchBackwardShortcut: normalizedKeyMap.SEARCH_BACKWARD,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: any): DispatchToProps {
|
||||
return {
|
||||
changeAnnotationsFilters(value: SelectValue) {
|
||||
if (typeof value === 'string') {
|
||||
dispatch(changeAnnotationsFiltersAction([value]));
|
||||
dispatch(fetchAnnotationsAsync());
|
||||
} else if (
|
||||
Array.isArray(value) &&
|
||||
value.every((element: string | number | LabeledValue): boolean => typeof element === 'string')
|
||||
) {
|
||||
dispatch(changeAnnotationsFiltersAction(value as string[]));
|
||||
dispatch(fetchAnnotationsAsync());
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function filtersHelpModalContent(searchForwardShortcut: string, searchBackwardShortcut: string): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Paragraph>
|
||||
<Title level={3}>General</Title>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
You can use filters to display only subset of objects on a frame or to search objects that satisfy the
|
||||
filters using hotkeys
|
||||
<Text strong>{` ${searchForwardShortcut} `}</Text>
|
||||
and
|
||||
<Text strong>{` ${searchBackwardShortcut} `}</Text>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Text strong>Supported properties: </Text>
|
||||
width, height, label, serverID, clientID, type, shape, occluded
|
||||
<br />
|
||||
<Text strong>Supported operators: </Text>
|
||||
==, !=, >, >=, <, <=, (), & and |
|
||||
<br />
|
||||
<Text strong>
|
||||
If you have double quotes in your query string, please escape them using back slash: \" (see
|
||||
the latest example)
|
||||
</Text>
|
||||
<br />
|
||||
All properties and values are case-sensitive. CVAT uses json queries to perform search.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Title level={3}>Examples</Title>
|
||||
<ul>
|
||||
<li>label=="car" | label==["road sign"]</li>
|
||||
<li>shape == "polygon"</li>
|
||||
<li>width >= height</li>
|
||||
<li>attr["Attribute 1"] == attr["Attribute 2"]</li>
|
||||
<li>clientID == 50</li>
|
||||
<li>
|
||||
(label=="car" & attr["parked"]==true) | (label=="pedestrian"
|
||||
& width > 150)
|
||||
</li>
|
||||
<li>
|
||||
(( label==["car \"mazda\""]) & (attr["sunglasses"]==true |
|
||||
(width > 150 | height > 150 & (clientID == serverID)))))
|
||||
</li>
|
||||
</ul>
|
||||
</Paragraph>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function AnnotationsFiltersInput(props: StateToProps & DispatchToProps): JSX.Element {
|
||||
const {
|
||||
annotationsFilters,
|
||||
annotationsFiltersHistory,
|
||||
searchForwardShortcut,
|
||||
searchBackwardShortcut,
|
||||
changeAnnotationsFilters,
|
||||
} = props;
|
||||
|
||||
const [underCursor, setUnderCursor] = useState(false);
|
||||
|
||||
return (
|
||||
<Select
|
||||
className='cvat-annotations-filters-input'
|
||||
allowClear
|
||||
value={annotationsFilters}
|
||||
mode='tags'
|
||||
style={{ width: '100%' }}
|
||||
placeholder={
|
||||
underCursor ? (
|
||||
<>
|
||||
<Tooltip title='Click to open help' mouseLeaveDelay={0}>
|
||||
<FilterOutlined
|
||||
onClick={(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
Modal.info({
|
||||
width: 700,
|
||||
title: 'How to use filters?',
|
||||
content: filtersHelpModalContent(searchForwardShortcut, searchBackwardShortcut),
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FilterOutlined style={{ transform: 'scale(0.9)' }} />
|
||||
<span style={{ marginLeft: 5 }}>Annotations filters</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
onChange={changeAnnotationsFilters}
|
||||
onMouseEnter={() => setUnderCursor(true)}
|
||||
onMouseLeave={() => setUnderCursor(false)}
|
||||
>
|
||||
{annotationsFiltersHistory.map(
|
||||
(element: string): JSX.Element => (
|
||||
<Select.Option key={element} value={element} className='cvat-annotations-filters-input-history-element'>
|
||||
{element}
|
||||
</Select.Option>
|
||||
),
|
||||
)}
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AnnotationsFiltersInput);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue