ADN Open CIS
Сообщество программистов Autodesk в СНГ

29/04/2020

Пользовательские инструменты в Forge Viewer

 

Изучая API Forge Viewer-а, Вы, возможно, видели классы ToolController и ToolInterface в пространстве имен Viewing. Сегодня мы рассмотрим их подробнее.

 

Tool stack (стек инструментов)

Viewer одновременно использует множество инструментов, например, управление камерой, управление hotkey-ями или навигацию от первого лица. Они могут быть активны одновременно, обрабатывая входящие события и изменяя состояние и содержимое сцены viewer-а, в зависимости от приоритета каждого конкретного инструмента, формируя «стек инструментов». Класс ToolController управляет этим стеком и отправляет входящие события нужным инструментам. Он создается  Viewer3D и может быть доступен из свойства viewer.toolController. ToolInterface – это интерфейс, который Вы можете реализовать для своего собственного инструмента. Давайте посмотрим, как это можно сделать.

 

Tool interface

Перед тем, как писать свой собственный код, давайте быстренько глянем код ToolInterface:

Код - JavaScript: [Выделить]
  1. /**
  2.  * Базовый класс для реализации логики пользовательского взаимодействия
  3.  *
  4.  * Так же может быть использован как шаблон для создания нового инструмента
  5.  * @constructor
  6.  * @see Autodesk.Viewing.ToolController
  7.  * @alias Autodesk.Viewing.ToolInterface
  8.  */
  9. export function ToolInterface()
  10. {
  11.     this.names = [ "unnamed" ];
  12.  
  13.     /**
  14.      * Этот метод должен возвращать массив имен всех инструментов, реализованных в этом классе.
  15.      * Обычно, это единственное имя, но также возможно создание разных вариантов пользовательских взаимодействий с помощью одного инструмента.
  16.      * Когда инструмент регистрируется в ToolController, каждое имя регистрируется как доступный интерфейс.
  17.      * @returns {array} Массив строк. Не должен быть пустым.
  18.      */
  19.     this.getNames = function() {
  20.         return this.names;
  21.     };
  22.  
  23.     /**
  24.      * Опциональный метод для получения первого имени инструмента.
  25.      * @returns {string} Имя по умолчанию.
  26.      */
  27.     this.getName = function() {
  28.         return this.names[0];
  29.     };
  30.  
  31.     /**
  32.      * Этот метод должен возвращать приоритет инструмента в стеке.
  33.      * Инструмент с высшим приоритетом будет получать события раньше.
  34.      * @returns {number} Приоритет инструмента.
  35.      */
  36.     this.getPriority = function() {
  37.         return 0;
  38.     };
  39.  
  40.     /**
  41.      * Метод вызывается из {@link Autodesk.Viewing.ToolController#registerTool}.
  42.      * Используйте для инициализации.
  43.      */
  44.     this.register = function() {
  45.     };
  46.  
  47.     /**
  48.      * Метод вызывается из {@link Autodesk.Viewing.ToolController#deregisterTool}.
  49.      * Используйте для очистки.
  50.      */
  51.     this.deregister = function() {
  52.     };
  53.  
  54.     /**
  55.      * Метод вызывается ToolController-ом при добавлении инструмента в список инструментов,
  56.      * обрабатывающих события. После активации методы "handle*" можетбыть вызван,
  57.      * если событие не было обработано инструментом с более высоким приоритетом. Метод "update" каждого активного инструмента также вызывается
  58.      * в каждый цикл перерисовки.
  59.      * @param {string} name - Имя активируемого инструмента.
  60.      * @param {Autodesk.Viewing.Viewer3D} viewerApi – экземпляр Viewer-а.
  61.      */
  62.     this.activate = function(name, viewerApi) {
  63.     };
  64.  
  65.     /**
  66.      * Метод вызывается ToolController при удалении из списка активных инструментов
  67.      * После деактивацииметоды "handle*" и "update"
  68.      * более не будут вызываться
  69.      * @param {string} name – Имя инструмента.
  70.      */
  71.     this.deactivate = function(name) {
  72.     };
  73.  
  74.     /**
  75.      * Метод вызывается ToolController один раз на frame и предоставляет каждому инструменту
  76.      * возможность внести изменения в сцену или вид.
  77.      * @param {number} highResTimestamp – Метка времени из requestAnimationFrame браузера.
  78.      * @returns {boolean} Признак того, что в сцену или вид были внесены изменения
  79.      * и необходимо полное обновление.
  80.      */
  81.     this.update = function(highResTimestamp) {
  82.         return false;
  83.     };
  84.  
  85.     /**
  86.      * Метод вызывается при событии одинарного щелчка мышью.
  87.      * @param {MouseEvent} event – Объект вызываемого события
  88.      * @param {number} button – Номер нажатой кнопки (0 - левая, 1 - средняя, 2 - правая).
  89.      * Примечание: номер нажатой кнопки может отличаться от соответствующего параметра в event
  90.      * из-за переопределения кнопок в настройках и следует использовать именно это значение, а не свойство в объекте event.
  91.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  92.      */
  93.     this.handleSingleClick = function( event, button ) {
  94.         return false;
  95.     };
  96.  
  97.     /**
  98.      * Метод вызывается при событии двойного щелчка мышью.
  99.      * @param {MouseEvent} event - Объект вызываемого события
  100.      * @param {number} button - Номер нажатой кнопки (0 - левая, 1 - средняя, 2 - правая).
  101.      * Примечание: номер нажатой кнопки может отличаться от соответствующего параметра в event
  102.      * из-за переопределения кнопок в настройках и следует использовать именно это значение, а не свойство в объекте event.
  103.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  104.      */
  105.     this.handleDoubleClick = function( event, button ) {
  106.         return false;
  107.     };
  108.  
  109.     /**
  110.      * Метод вызывается при событии tap на устройствах, поддерживающих touch.
  111.      * @param {Event} event - Объект вызываемого события. Свойства canvasX, canvasY содержат относительные координаты,
  112.      * свойства normalizedX, normalizedY содержат
  113.      * нормализованные координаты в диапазоне [-1, 1]. Свойство event.pointers - это массив, содержащий
  114.      * один или два события, в зависимости от того, использованы при tap один или 2 пальца.
  115.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  116.      */
  117.     this.handleSingleTap = function( event ) {
  118.         return false;
  119.     };
  120.  
  121.     /**
  122.      * Метод вызывается при событии двойного tap на устройствах, поддерживающих touch.
  123.      * @param {Event} event - Объект вызываемого события. Свойства canvasX, canvasY содержат относительные координаты,
  124.      * свойства normalizedX, normalizedY содержат
  125.      * нормализованные координаты в диапазоне [-1, 1]. Свойство event.pointers - это массив, содержащий
  126.      * один или два события, в зависимости от того, использованы при tap один или 2 пальца.
  127.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  128.      */
  129.     this.handleDoubleTap = function( event ) {
  130.         return false;
  131.     };
  132.  
  133.     /**
  134.      * Метод вызывается при нажатии кнопки на клавиатуре.
  135.      * @param {KeyboardEvent} event - Объект вызываемого события.
  136.      * @param {number} keyCode - Числовой код нажатой кнопки.
  137.       * Примечание: номер нажатой кнопки keyCode может отличаться от соответствующего параметра в event
  138.      * из-за переопределения кнопок в настройках и следует использовать именно это значение, а не свойство в объекте event.
  139.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  140.      */
  141.     this.handleKeyDown = function( event, keyCode ) {
  142.         return false;
  143.     };
  144.  
  145.     /**
  146.      * Метод вызывается при отпускании кнопки на клавиатуре после нажатия.
  147.      * @param {KeyboardEvent} event - Объект вызываемого события.
  148.      * @param {number} keyCode - Числовой код нажатой кнопки.
  149.       * Примечание: номер нажатой кнопки keyCode может отличаться от соответствующего параметра в event
  150.      * из-за переопределения кнопок в настройках и следует использовать именно это значение, а не свойство в объекте event.
  151.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  152.      */
  153.     this.handleKeyUp = function( event, keyCode ) {
  154.         return false;
  155.     };
  156.  
  157.     /**
  158.      * Метод вызывается при событии прокрутки колеса мыши.
  159.      * @param {number} delta – Числовое значение, характеризующее насколько было повернуто колесо мыши.
  160.      * Примечание: значение может отличаться от оригинального в event для того, чтобы поведение инструмента было одинаковым в разных браузерах
  161.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  162.      */
  163.     this.handleWheelInput = function(delta) {
  164.         return false;
  165.     };
  166.  
  167.     /**
  168.      * Метод вызывается при событии нажатия кнопки мыши.
  169.      * @param {MouseEvent} event - Объект вызываемого события
  170.      * @param {number} button - Номер нажатой кнопки (0 - левая, 1 - средняя, 2 - правая).
  171.      * Примечание: номер нажатой кнопки может отличаться от соответствующего параметра в event
  172.      * из-за переопределения кнопок в настройках и следует использовать именно это значение, а не свойство в объекте event.
  173.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  174.      */
  175.     this.handleButtonDown = function(event, button) {
  176.         return false;
  177.     };
  178.  
  179.     /**
  180.      * Метод вызывается при событии отпускания кнопки мыши.
  181.      * @param {MouseEvent} event - Объект вызываемого события
  182.      * @param {number} button - Номер нажатой кнопки (0 - левая, 1 - средняя, 2 - правая).
  183.      * Примечание: номер нажатой кнопки может отличаться от соответствующего параметра в event
  184.      * из-за переопределения кнопок в настройках и следует использовать именно это значение, а не свойство в объекте event.
  185.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  186.      */
  187.     this.handleButtonUp = function(event, button) {
  188.         return false;
  189.     };
  190.  
  191.     /**
  192.      * Метод вызывается при событии движения мыши.
  193.      * @param {MouseEvent} event - Объект вызываемого события.
  194.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  195.      */
  196.     this.handleMouseMove = function(event) {
  197.         return false;
  198.     };
  199.  
  200.     /**
  201.      * Метод вызывается при событии touch жестов.
  202.      * @param {Event} event - Объект вызываемого события. Свойство event.type содержит
  203.      * тип жеста: dragstart, dragmove, dragend, panstart, panmove, panend,
  204.      * pinchstart, pinchmove, pinchend, rotatestart, rotatemove, rotateend, drag3start, drag3move, drag3end.
  205.      * Свойства event.canvas[XY] содержат соответствующую позицию жеста.
  206.      * Свойства event.scale и event.rotation содержат значения масштабирования и поворота
  207.      * соответственно. Свойства deltaX и deltaY содержат смещения.
  208.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  209.      */
  210.     this.handleGesture = function(event) {
  211.         return false;
  212.     };
  213.  
  214.     /**
  215.      * Метод вызывается при потере фокуса canvas-ом.
  216.      * @param {FocusEvent} event - Объект вызываемого события.
  217.      * @returns {boolean} False – если Вы хотите, чтобы инструменты с более низким приоритетом обработали это событие. True - чтобы не передавать событие далее по стеку
  218.      */
  219.     this.handleBlur = function(event) {
  220.         return false;
  221.     };
  222.  
  223.     /**
  224.      * Метод вызывается при изменении размера canvas-а.
  225.      * Новый размер canvas-а может быть получен из интерфейса Navigation с помощью метода getScreenViewport.
  226.      * @see Autodesk.Viewing.Navigation
  227.      */
  228.     this.handleResize = function() {
  229.     };
  230. }

Как Вы видите, интерфейс описывает несколько вещей. Есть методы для определения имён инструментов (поскольку класс может представлять сразу несколько инструментов) и приоритета. Инструменты с высшим приоритетом будут получать события раньше. Дальше идут несколько методов, определяющих жизненный цикл инструмента. Методы register/deregister вызываются когда ToolController запускает или останавливает инструмент. Методы activate/deactivate вызываются, когда инструмент становится активным или неактивным. Метод update вызывается многократно пока инструмент активен. Остальные методы интерфейса обрабатывают различные события.

Примечание: Вы, возможно, заметили, что методы интерфейса определены в конструкторе, что означает, что мы не можем просто переопределить их используя синтаксис классов. Существуют разные способы обхода данной проблемы. В нашем примере мы удалим методы объекта в нашем конструкторе класса и вместо них используем методы класса/прототипа.

 

Пользовательский инструмент

Давайте для примера создадим простой инструмент, позволяющий рисовать прямоугольные параллелепипеды и сферы. Вот как он будет работать:

-        при событии mouse button down инструмент начнёт «рисовать» геометрию в плоскости XY

-        при событии mouse up инструмент начнёт отслеживать события mouse move для контроля высоты геометрии

-        при событии mouse click построение геометрии будет завершено

Базовый каркас нашего инструмента:

Код - JavaScript: [Выделить]
  1. class DrawTool extends Autodesk.Viewing.ToolInterface {
  2.     constructor() {
  3.         super();
  4.         this.names = ['box-drawing-tool', 'sphere-drawing-tool'];
  5.  
  6.         // Hack: удаляем функции, определенные в *экземпляре* инструмента.
  7.         // Мы хотим, чтобы tool controller вместо них вызывал методы нашего класса.
  8.         delete this.register;
  9.         delete this.deregister;
  10.         delete this.activate;
  11.         delete this.deactivate;
  12.         delete this.getPriority;
  13.         delete this.handleMouseMove;
  14.         delete this.handleButtonDown;
  15.         delete this.handleButtonUp;
  16.         delete this.handleSingleClick;
  17.     }
  18.  
  19.     register() {
  20.         console.log('DrawTool registered.');
  21.     }
  22.  
  23.     deregister() {
  24.         console.log('DrawTool unregistered.');
  25.     }
  26.  
  27.     activate(name, viewer) {
  28.         console.log('DrawTool activated.');
  29.     }
  30.  
  31.     deactivate(name) {
  32.         console.log('DrawTool deactivated.');
  33.     }
  34.  
  35.     getPriority() {
  36.         return 42; // Или используйте любое число больше 0 (приоритета инструментов viewer-апо умолчанию)
  37.     }
  38.  
  39.     update(highResTimestamp) {
  40.         return false;
  41.     }
  42.  
  43.     handleMouseMove(event) {
  44.         return false;
  45.     }
  46.  
  47.     handleButtonDown(event, button) {
  48.         return false;
  49.     }
  50.  
  51.     handleButtonUp(event, button) {
  52.         return false;
  53.     }
  54.  
  55.     handleSingleClick(event, button) {
  56.         return false;
  57.     }
  58.  
  59.     _update() {
  60.         // Здесь мы будем обновлять геометрию
  61.     }
  62. }

Теперь, давайте создадим реализацию методов activate и deactivate. При активации инструмента мы сохраним ссылку на объект viewer-а для дальнейшего использования, режим рисования (прямоугольный параллелепипед или сферу) в зависимости от имени активируемого инструмента и этап редактирования (рисуем основу в плоскости XY или определяем высоту создаваемой геометрии)

Код - JavaScript: [Выделить]
  1. activate(name, viewer) {
  2.     this.viewer = viewer;
  3.     this.mode = (name === 'box-drawing-tool') ? 'box' : 'sphere';
  4.     this.state = ''; // может быть '' (не рисуем ничего), 'xy' (рисуем в плоскости XY), или 'z' (определяем высоту)
  5.     console.log('DrawTool', name, 'activated.');
  6. }
  7.  
  8. deactivate(name) {
  9.     this.viewer = null;
  10.     this.state = '';
  11.     console.log('DrawTool', name, 'deactivated.');
  12. }

Для реализации первого этапа редактирования — определения размеров геометрии в плоскости XY переопределим методы handleButtonDown и handleButtonUp:

Код - JavaScript: [Выделить]
  1. handleButtonDown(event, button) {
  2.     // Если нажата левая кнопка и мы пока ничего не редактируем
  3.     if (button === 0 && this.state === '') {
  4.         // Создаем новую геометрию и добавляем её на overlay
  5.         if (this.mode === 'box') {
  6.             const geometry = new THREE.BufferGeometry().fromGeometry(new THREE.BoxGeometry(1, 1, 1));
  7.             const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
  8.             this.mesh = new THREE.Mesh(geometry, material);
  9.         } else {
  10.             const geometry = new THREE.BufferGeometry().fromGeometry(new THREE.SphereGeometry(0.5, 16, 16));
  11.             const material = new THREE.MeshPhongMaterial({ color: 0x0000ff });
  12.             this.mesh = new THREE.Mesh(geometry, material);
  13.         }
  14.         this.viewer.impl.addOverlay('draw-tool-overlay', this.mesh);
  15.  
  16.         // Инициализируем 3 значения, контролирующие размер создаваемой геометрии (1ыйугол в плоскости XY, 2ойугол в плоскости XY и высоту)
  17.         this.corner1 = this.corner2 = this.viewer.impl.intersectGround(event.clientX, event.clientY);
  18.         this.height = 0.1;
  19.         this._update();
  20.         this.state = 'xy'; // Теперь рисуем в плоскости XY
  21.         return true; // Теперь инструменты в стеке с приоритетом ниже не получат сообщение
  22.     }
  23.     // Иначе позволяем обработать событие другим инструментам и устанавливаем значение поля bypassed, чтобы пропускать обработку части других событий.
  24.     this.bypassed = true;
  25.     return false;
  26. }
  27.  
  28. handleButtonUp(event, button) {
  29.     // Левая кнопка была отжата и находимся в режиме рисования в плоскости XY
  30.     if (button === 0 && this.state === 'xy') {
  31.         // Обновляем 2 угол на плоскости XY. Переключаемся в режим "рисования высоты"
  32.         this.corner2 = this.viewer.impl.intersectGround(event.clientX, event.clientY);
  33.         this._update();
  34.         this.state = 'z';
  35.         this.lastClientY = event.clientY; // Сохраняем значение координаты Y, чтобы потом на её основе вычислять высоту
  36.         return true; // Теперь инструменты в стеке с приоритетом ниже не получат сообщение
  37.     }
  38.     // Иначе позволяем обработать событие другим инструментам и устанавливаем значение поля bypassed, чтобы обрабатывать часть других событий
  39.     this.bypassed = false;
  40.     return false;
  41. }
  42.  

Далее, переопределим метод handleMouseMove. В нашем случае, в зависимости от режима рисования, в котором находится инструмент, обновляем либо второй угол нашей геометрии в плоскости XY, либо её высоту:

Код - JavaScript: [Выделить]
  1. handleMouseMove(event) {
  2.     if (!this.bypassed && this.state === 'xy') {
  3.         // Мы в режиме "рисования в плоскости XY" и не обрабатываем событие другими методами
  4.         this.corner2 = this.viewer.impl.intersectGround(event.clientX, event.clientY);
  5.         this._update();
  6.         return true;
  7.     } else if (!this.bypassed && this.state === 'z') {
  8.         // Мы в режиме "рисования высоты" и не обрабатываем событие другими методами
  9.         this.height = this.lastClientY - event.clientY;
  10.         this._update();
  11.         return true;
  12.     }
  13.     // Иначе позволяем обработать событие другим инструментам
  14.     return false;
  15. }

Теперь определим событие mouse single click, чтобы завершить размещение создаваемой геометрии:

Код - JavaScript: [Выделить]
  1. handleSingleClick(event, button) {
  2.     // Нажата левая кнопка и мы в режиме "рисования высоты"
  3.     if (button === 0 && this.state === 'z') {
  4.         this.state = '';
  5.         return true; // Теперь инструменты в стеке с приоритетом ниже не получат сообщение
  6.     }
  7.     // Иначе позволяем обработать событие другим инструментам
  8.     return false;
  9. }

Теперь, когда мы закончили с методами обработки событий, осталось реализовать метод _update, который позиционирует и масштабирует текущую геометрию на основе значений corner1, corner2, и высоты:

Код - JavaScript: [Выделить]
  1. _update() {
  2.     const { corner1, corner2, height, mesh } = this;
  3.     const minX = Math.min(corner1.x, corner2.x), maxX = Math.max(corner1.x, corner2.x);
  4.     const minY = Math.min(corner1.y, corner2.y), maxY = Math.max(corner1.y, corner2.y);
  5.     mesh.position.x = minX + 0.5 * (maxX - minX);
  6.     mesh.position.y = minY + 0.5 * (maxY - minY);
  7.     mesh.position.z = 0.5 * height;
  8.     mesh.scale.x = maxX - minX;
  9.     mesh.scale.y = maxY - minY;
  10.     mesh.scale.z = height;
  11.     this.viewer.impl.invalidate(true, true, true);
  12. }

Теперь наш инструмент готов. Вы можете зарегистрировать и активировать его прямо из Viewer-а:

Код - JavaScript: [Выделить]
  1. const drawTool = new DrawTool();
  2. viewer.toolController.registerTool(drawTool);
  3. viewer.toolController.activateTool('box-drawing-tool');

Создадим расширение Viewer-а для работы с нашим новым инструментом:

Код - JavaScript: [Выделить]
  1. const BoxDrawToolName = 'box-draw-tool';
  2. const SphereDrawToolName = 'sphere-draw-tool';
  3. const DrawToolOverlay = 'draw-tool-overlay';
  4.  
  5. class DrawToolExtension extends Autodesk.Viewing.Extension {
  6.     constructor(viewer, options) {
  7.         super(viewer, options);
  8.         this.tool = new DrawTool();
  9.     }
  10.  
  11.     load() {
  12.         this.viewer.toolController.registerTool(this.tool);
  13.         this.viewer.impl.createOverlayScene(DrawToolOverlay);
  14.         console.log('DrawToolExtension loaded.');
  15.         return true;
  16.     }
  17.  
  18.     unload() {
  19.         this.viewer.toolController.deregisterTool(this.tool);
  20.         this.viewer.impl.removeOverlayScene(DrawToolOverlay);
  21.         console.log('DrawToolExtension unloaded.');
  22.         return true;
  23.     }
  24.  
  25.     onToolbarCreated(toolbar) {
  26.         const controller = this.viewer.toolController;
  27.         this.button1 = new Autodesk.Viewing.UI.Button('box-draw-tool-button');
  28.         this.button1.onClick = (ev) => {
  29.             if (controller.isToolActivated(BoxDrawToolName)) {
  30.                 controller.deactivateTool(BoxDrawToolName);
  31.                 this.button1.setState(Autodesk.Viewing.UI.Button.State.INACTIVE);
  32.             } else {
  33.                 controller.deactivateTool(SphereDrawToolName);
  34.                 controller.activateTool(BoxDrawToolName);
  35.                 this.button2.setState(Autodesk.Viewing.UI.Button.State.INACTIVE);
  36.                 this.button1.setState(Autodesk.Viewing.UI.Button.State.ACTIVE);
  37.             }
  38.         };
  39.         this.button1.setToolTip('Box Draw Tool');
  40.  
  41.         this.button2 = new Autodesk.Viewing.UI.Button('sphere-draw-tool-button');
  42.         this.button2.onClick = (ev) => {
  43.             if (controller.isToolActivated(SphereDrawToolName)) {
  44.                 controller.deactivateTool(SphereDrawToolName);
  45.                 this.button2.setState(Autodesk.Viewing.UI.Button.State.INACTIVE);
  46.             } else {
  47.                 controller.deactivateTool(BoxDrawToolName);
  48.                 controller.activateTool(SphereDrawToolName);
  49.                 this.button1.setState(Autodesk.Viewing.UI.Button.State.INACTIVE);
  50.                 this.button2.setState(Autodesk.Viewing.UI.Button.State.ACTIVE);
  51.             }
  52.         };
  53.         this.button2.setToolTip('Sphere Draw Tool');
  54.  
  55.         this.group = new Autodesk.Viewing.UI.ControlGroup('draw-tool-group');
  56.         this.group.addControl(this.button1);
  57.         this.group.addControl(this.button2);
  58.         toolbar.addControl(this.group);
  59.     }
  60. }

При тестировании нового инструмента во Viewer-а, обратите внимание, как он взаимодействует с другими инструментами. Например, после завершения редактирования геометрии в плоскости XY перед установкой высоты с помощью клика мыши, вы можете использовать колесо мыши для масштабирования или нажать и удерживать левую кнопку мыши для поворота камеры. Магия стека инструментов!

 

Полный исходный код доступен здесь

 

Источник: https://forge.autodesk.com/blog/custom-tools-forge-viewer

Автор перевода: Александр Игнатович
Опубликовано 29.04.2020