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

28/02/2020

Размещение собственных пометок (markups) по dbId в Forge Viewer

Размещение собственных пометок (markups) по dbId в Forge Viewer

Существуют разные подходы для реализации markups (пометок) в Forge Viewer. Например: 

В обеих статьях используются сходные подходы: поверх модели размещается DIV, расположение которого подстраивается при изменении позиции камеры. Умно ведь? Этот подход вполне рабочий, воспользуемся им. Но для него требуется знать точное расположение (XYZ) markup-а, что которое иногда достаточно сложно определить. Petr Broz реализовал это с использованием id и id фрагмента. Сами markup-ы он показывает с помощью изображений:

Petr практически реализовал именно то, что я хотел, но мне бы также хотелось упростить его подход для моего собственного примера, чтобы просто передать dbId, текст и картинку, которую нужно показать в markup-е, т.е. что-то вроде:

Код - JavaScript: [Выделить]
  1. viewer.loadExtension('IconMarkupExtension', {
  2.     icons: [
  3.         { dbId: 987,label: '300C',css:'fas fa-thermometer' }
  4.     ]
  5. });

Расширение использует dbId для того, чтобы найти центр 3D геометрии, т.е., например дверь из модели Revit может состоять из нескольких частей, поэтому мы используем метод THREE.Box3.union для объединения bounding box-ов и затем получаем центр. Свойство label - это просто текст. CSS содержит иконку markup-а и дополнительные стили, такие как фон и граница. Можно, например, использовать иконки из векторных шрифты, которые очень здорово смотрятся.

Ну и немножко ещё: нам нужна кнопка с иконкой на панели инструментов, возможность показать markup без текста, просто с иконкой.

Таким образом, чтобы получить что-то подобное GIF-ке в начале статьи нам  нужно загрузить расширение для Forge Viewer примерно следующим образом:

Код - JavaScript: [Выделить]
  1. viewer.loadExtension('IconMarkupExtension', {
  2.             button: {
  3.                 icon: 'fa-thermometer-half',
  4.                 tooltip: 'Show Temperature'
  5.             },
  6.             icons: [
  7.                 { dbId: 3944,   label: '300°C', css: 'fas fa-thermometer-full' },
  8.                 { dbId: 721,    label: '356°C', css: 'fas fa-thermometer-full' },
  9.                 { dbId: 10312,  label: '450°C', css: 'fas fa-thermometer-empty' },
  10.                 { dbId: 563,                         css: 'fas fa-exclamation-triangle' },
  11.             ],
  12.             onClick: (id) => {
  13.                 viewers.select(id);
  14.                 viewers.utilities.fitToView();
  15.                 switch (id){
  16.                     case 563:
  17.                         alert('Sensor offline');
  18.                 }
  19.             }
  20.         })

Ниже приведен код самого расширения.

Посмотреть online: https://forgeplant.herokuapp.com/

 

Код - JavaScript: [Выделить]
  1. class IconMarkupExtension extends Autodesk.Viewing.Extension {
  2.     constructor(viewer, options) {
  3.         super(viewer, options);
  4.         this._group = null;
  5.         this._button = null;
  6.         this._icons = options.icons || [];
  7.     }
  8.  
  9.     load() {
  10.         const updateIconsCallback = () => {
  11.             if (this._enabled) {
  12.                 this.updateIcons();
  13.             }
  14.         };
  15.         this.viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, updateIconsCallback);
  16.         this.viewer.addEventListener(Autodesk.Viewing.ISOLATE_EVENT, updateIconsCallback);
  17.         this.viewer.addEventListener(Autodesk.Viewing.HIDE_EVENT, updateIconsCallback);
  18.         this.viewer.addEventListener(Autodesk.Viewing.SHOW_EVENT, updateIconsCallback);
  19.         return true;
  20.     }
  21.  
  22.     unload() {
  23.         // Clean our UI elements if we added any
  24.         if (this._group) {
  25.             this._group.removeControl(this._button);
  26.             if (this._group.getNumberOfControls() === 0) {
  27.                 this.viewer.toolbar.removeControl(this._group);
  28.             }
  29.         }
  30.  
  31.         return true;
  32.     }
  33.  
  34.     onToolbarCreated() {
  35.         // Create a new toolbar group if it doesn't exist
  36.         this._group = this.viewer.toolbar.getControl('customExtensions');
  37.         if (!this._group) {
  38.             this._group = new Autodesk.Viewing.UI.ControlGroup('customExtensions');
  39.             this.viewer.toolbar.addControl(this._group);
  40.         }
  41.  
  42.         // Add a new button to the toolbar group
  43.         this._button = new Autodesk.Viewing.UI.Button('IconExtension');
  44.         this._button.onClick = (ev) => {
  45.             this._enabled = !this._enabled;
  46.             this.showIcons(this._enabled);
  47.             this._button.setState(this._enabled ? 0 : 1);
  48.  
  49.         };
  50.         this._button.setToolTip(this.options.button.tooltip);
  51.         this._button.container.children[0].classList.add('fas', this.options.button.icon);
  52.         this._group.addControl(this._button);
  53.     }
  54.  
  55.     showIcons(show) {
  56.         const $viewer = $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer');
  57.  
  58.         // remove previous...
  59.         $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer label.markup').remove();
  60.         if (!show) return;
  61.  
  62.         // do we have anything to show?
  63.         if (this._icons === undefined || this.icons === null) return;
  64.  
  65.         // do we have access to the instance tree?
  66.         const tree = this.viewer.model.getInstanceTree();
  67.         if (tree === undefined) { console.log('Loading tree...'); return; }
  68.  
  69.         const onClick = (e) => {
  70.             if (this.options.onClick)
  71.                 this.options.onClick($(e.currentTarget).data('id'));
  72.         };
  73.  
  74.         this._frags = {}
  75.         for (var i = 0; i < this._icons.length; i++) {
  76.             // we need to collect all the fragIds for a given dbId
  77.             const icon = this._icons[i];
  78.             this._frags['dbId' + icon.dbId] = []
  79.  
  80.             // create the label for the dbId
  81.             const $label = $('
  82.             <label class="markup update" data-id="${icon.dbId}">
  83.                 <span class="${icon.css}"> ${icon.label || ''}</span>
  84.             </label>
  85.             ');
  86.             $label.css('display', this.viewer.isNodeVisible(icon.dbId) ? 'block' : 'none');
  87.             $label.on('click', onClick);
  88.             $viewer.append($label);
  89.  
  90.             // now collect the fragIds
  91.             const _this = this;
  92.             tree.enumNodeFragments(icon.dbId, function (fragId) {
  93.                 _this._frags['dbId' + icon.dbId].push(fragId);
  94.                 _this.updateIcons(); // re-position of each fragId found
  95.             });
  96.         }
  97.     }
  98.  
  99.     getModifiedWorldBoundingBox(dbId) {
  100.         var fragList = this.viewer.model.getFragmentList();
  101.         const nodebBox = new THREE.Box3()
  102.  
  103.         // for each fragId on the list, get the bounding box
  104.         for (const fragId of this._frags['dbId' + dbId]) {
  105.             const fragbBox = new THREE.Box3();
  106.             fragList.getWorldBounds(fragId, fragbBox);
  107.             nodebBox.union(fragbBox); // create a unifed bounding box
  108.         }
  109.  
  110.         return nodebBox
  111.     }
  112.  
  113.     updateIcons() {
  114.         for (const label of $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer .update')) {
  115.             const $label = $(label);
  116.             const id = $label.data('id');
  117.  
  118.             // get the center of the dbId (based on its fragIds bounding boxes)
  119.             const pos = this.viewer.worldToClient(this.getModifiedWorldBoundingBox(id).center());
  120.  
  121.             // position the label center to it
  122.             $label.css('left', Math.floor(pos.x - $label[0].offsetWidth / 2) + 'px');
  123.             $label.css('top', Math.floor(pos.y - $label[0].offsetHeight / 2) + 'px');
  124.             $label.css('display', this.viewer.isNodeVisible(id) ? 'block' : 'none');
  125.         }
  126.     }
  127. }
  128.  
  129. Autodesk.Viewing.theExtensionManager.registerExtension('IconMarkupExtension', IconMarkupExtension);

 

Источник: https://forge.autodesk.com/blog/placing-custom-markup-dbid

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