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

30/04/2021

Forge Viewer: Добавляем расширение Forge Viewer-а в пример использования Design Automation для Inventor (для замены Configurator 360)

Наша команда инженеров предоставила пример того, как создавать веб-приложения-конфигураторы на основе Design Automation для Inventor, например для замены Configurator 360. Об этом я уже писал вот в этой статье  (см. также перевод на нашем сайте).

Один из часто задаваемых вопросов - "а как же нам загрузить в Forge Viewer какое-нибудь расширение?" Например, вот это. Давайте посмотрим, как можно это сделать.

Часть приложения конфигуратора, ответственная за работу с Forge Viewer-ом содержится в файле forgeView.js:

В расширении TransformationExtension, которое я привёл в качестве примера, есть 3 файла: main.js, main.css и transformicon.png. Во избежание путаницы в дальнейшем, я переименовал JS и CSS файлы в transformtool.js и transformtool.css и разместил их в папке WebApplication >> ClientApp >> src >> components.

Файл transformicon.png я положил в папку WebApplication >> ClientApp >> public и поправил путь в transformtool.css:

Код - HTML: [Выделить]
  1. .transformextensionicon {
  2.     background-image: url(/transformicon.png);
  3.     background-size: 24px;
  4.     background-repeat: no-repeat;
  5.     background-position: center;
  6. }

Для того, чтобы наше расширение заработало, необходимо импортировать его в forgeView.js (ключевое слово import может быть только в начале файла), но тогда transformtool.js будет пытаться работать с ещё недоступным на текущий момент кодом самого Forge Viewer-а. JS код Forge Viewer-а загружается при необходимости, а не сразу с кодом самого приложения конфигуратора, поэтому при таком подходе мы увидим большое количество ошибок:

Поэтому я поместил исходный код из файла transformtool.js в функцию (экспортируемую), импортировал CSS файл. Ещё я объявил переменные Autodesk, AutodeskNamespace и THREE, они станут доступными после загрузки Forge Viewer-а.

Код - JavaScript: [Выделить]
  1. import './transformtool.css';
  2.  
  3. export default function RegisterTransformTool () {
  4.  
  5.     var Autodesk = window.Autodesk;
  6.     var AutodeskNamespace = window.AutodeskNamespace;
  7.     var THREE = window.THREE;
  8.  
  9.     AutodeskNamespace("Autodesk.ADN.Viewing.Extension");
  10.  
  11.     Autodesk.ADN.Viewing.Extension.TransformTool = function (viewer, options) {
  12.  
  13.         ///////////////////////////////////////////////////////////////////////////
  14.         //
  15.         //
  16.         ///////////////////////////////////////////////////////////////////////////
  17.         function TransformTool() {
  18.  
  19.             var _hitPoint = null;
  20.  
  21.             var _isDragging = false;
  22.  
  23.             var _transformMesh = null;
  24.  
  25.             var _modifiedFragIdMap = {};
  26.  
  27.             var _selectedFragProxyMap = {};
  28.  
  29.             var _transformControlTx = null;
  30.  
  31.             ///////////////////////////////////////////////////////////////////////////
  32.             // Creates a dummy mesh to attach control to
  33.             //
  34.             ///////////////////////////////////////////////////////////////////////////
  35.             function createTransformMesh() {
  36.  
  37.                 var material = new THREE.MeshPhongMaterial(
  38.                     { color: 0xff0000 });
  39.  
  40.                 viewer.impl.matman().addMaterial(
  41.                     guid(),
  42.                     material,
  43.                     true);
  44.  
  45.                 var sphere = new THREE.Mesh(
  46.                     new THREE.SphereGeometry(0.0001, 5),
  47.                     material);
  48.  
  49.                 sphere.position.set(0, 0, 0);
  50.  
  51.                 return sphere;
  52.             }
  53.  
  54.             ///////////////////////////////////////////////////////////////////////////
  55.             // on translation change
  56.             //
  57.             ///////////////////////////////////////////////////////////////////////////
  58.             function onTxChange() {
  59.  
  60.                 for (var fragId in _selectedFragProxyMap) {
  61.  
  62.                     var fragProxy = _selectedFragProxyMap[fragId];
  63.  
  64.                     var position = new THREE.Vector3(
  65.                         _transformMesh.position.x - fragProxy.offset.x,
  66.                         _transformMesh.position.y - fragProxy.offset.y,
  67.                         _transformMesh.position.z - fragProxy.offset.z);
  68.  
  69.                     fragProxy.position = position;
  70.  
  71.                     fragProxy.updateAnimTransform();
  72.                 }
  73.  
  74.                 viewer.impl.sceneUpdated(true);
  75.             }
  76.  
  77.             ///////////////////////////////////////////////////////////////////////////
  78.             // on camera changed
  79.             //
  80.             ///////////////////////////////////////////////////////////////////////////
  81.             function onCameraChanged() {
  82.  
  83.                 _transformControlTx.update();
  84.             }
  85.  
  86.             ///////////////////////////////////////////////////////////////////////////
  87.             // item selected callback
  88.             //
  89.             ///////////////////////////////////////////////////////////////////////////
  90.             function onItemSelected(event) {
  91.  
  92.                 _selectedFragProxyMap = {};
  93.  
  94.                 //component unselected
  95.  
  96.                 if (!event.fragIdsArray.length) {
  97.  
  98.                     _hitPoint = null;
  99.  
  100.                     _transformControlTx.visible = false;
  101.  
  102.                     _transformControlTx.removeEventListener(
  103.                         'change', onTxChange);
  104.  
  105.                     viewer.removeEventListener(
  106.                         Autodesk.Viewing.CAMERA_CHANGE_EVENT,
  107.                         onCameraChanged);
  108.  
  109.                     return;
  110.                 }
  111.  
  112.  
  113.                 if (_hitPoint) {
  114.  
  115.                     _transformControlTx.visible = true;
  116.  
  117.                     _transformControlTx.setPosition(_hitPoint);
  118.  
  119.                     _transformControlTx.addEventListener(
  120.                         'change', onTxChange);
  121.  
  122.                     viewer.addEventListener(
  123.                         Autodesk.Viewing.CAMERA_CHANGE_EVENT,
  124.                         onCameraChanged);
  125.  
  126.                     event.fragIdsArray.forEach(function (fragId) {
  127.  
  128.                         var fragProxy = viewer.impl.getFragmentProxy(
  129.                             viewer.model,
  130.                             fragId);
  131.  
  132.                         fragProxy.getAnimTransform();
  133.  
  134.                         var offset = {
  135.  
  136.                             x: _hitPoint.x - fragProxy.position.x,
  137.                             y: _hitPoint.y - fragProxy.position.y,
  138.                             z: _hitPoint.z - fragProxy.position.z
  139.                         };
  140.  
  141.                         fragProxy.offset = offset;
  142.  
  143.                         _selectedFragProxyMap[fragId] = fragProxy;
  144.  
  145.                         _modifiedFragIdMap[fragId] = {};
  146.                     });
  147.  
  148.                     _hitPoint = null;
  149.                 }
  150.                 else {
  151.  
  152.                     _transformControlTx.visible = false;
  153.                 }
  154.             }
  155.  
  156.             ///////////////////////////////////////////////////////////////////////////
  157.             // normalize screen coordinates
  158.             //
  159.             ///////////////////////////////////////////////////////////////////////////
  160.             function normalize(screenPoint) {
  161.  
  162.                 var viewport = viewer.navigation.getScreenViewport();
  163.  
  164.                 var n = {
  165.                     x: (screenPoint.x - viewport.left) / viewport.width,
  166.                     y: (screenPoint.y - viewport.top) / viewport.height
  167.                 };
  168.  
  169.                 return n;
  170.             }
  171.  
  172.             ///////////////////////////////////////////////////////////////////////////
  173.             // get 3d hit point on mesh
  174.             //
  175.             ///////////////////////////////////////////////////////////////////////////
  176.             function getHitPoint(event) {
  177.  
  178.                 var screenPoint = {
  179.                     x: event.clientX,
  180.                     y: event.clientY
  181.                 };
  182.  
  183.                 var n = normalize(screenPoint);
  184.  
  185.                 var hitPoint = viewer.utilities.getHitPoint(n.x, n.y);
  186.  
  187.                 return hitPoint;
  188.             }
  189.  
  190.             ///////////////////////////////////////////////////////////////////////////
  191.             // returns all transformed meshes
  192.             //
  193.             ///////////////////////////////////////////////////////////////////////////
  194.             this.getTransformMap = function () {
  195.  
  196.                 var transformMap = {};
  197.  
  198.                 for (var fragId in _modifiedFragIdMap) {
  199.  
  200.                     var fragProxy = viewer.impl.getFragmentProxy(
  201.                         viewer.model,
  202.                         fragId);
  203.  
  204.                     fragProxy.getAnimTransform();
  205.  
  206.                     transformMap[fragId] = {
  207.                         position: fragProxy.position
  208.                     };
  209.  
  210.                     fragProxy = null;
  211.                 }
  212.  
  213.                 return transformMap;
  214.             };
  215.  
  216.             ///////////////////////////////////////////////////////////////////////////
  217.             //
  218.             //
  219.             ///////////////////////////////////////////////////////////////////////////
  220.             this.getNames = function () {
  221.  
  222.                 return ['Dotty.Viewing.Tool.TransformTool'];
  223.             };
  224.  
  225.             this.getName = function () {
  226.  
  227.                 return 'Dotty.Viewing.Tool.TransformTool';
  228.             };
  229.  
  230.             ///////////////////////////////////////////////////////////////////////////
  231.             // activates tool
  232.             //
  233.             ///////////////////////////////////////////////////////////////////////////
  234.             this.activate = function () {
  235.  
  236.                 viewer.select([]);
  237.  
  238.                 var bbox = viewer.model.getBoundingBox();
  239.  
  240.                 viewer.impl.createOverlayScene(
  241.                     'Dotty.Viewing.Tool.TransformTool');
  242.  
  243.                 _transformControlTx = new THREE.TransformControls(
  244.                     viewer.impl.camera,
  245.                     viewer.impl.canvas,
  246.                     "translate");
  247.  
  248.                 _transformControlTx.setSize(
  249.                     bbox.getBoundingSphere().radius * 5);
  250.  
  251.                 _transformControlTx.visible = false;
  252.  
  253.                 viewer.impl.addOverlay(
  254.                     'Dotty.Viewing.Tool.TransformTool',
  255.                     _transformControlTx);
  256.  
  257.                 _transformMesh = createTransformMesh();
  258.  
  259.                 _transformControlTx.attach(_transformMesh);
  260.  
  261.                 viewer.addEventListener(
  262.                     Autodesk.Viewing.SELECTION_CHANGED_EVENT,
  263.                     onItemSelected);
  264.             };
  265.  
  266.             ///////////////////////////////////////////////////////////////////////////
  267.             // deactivate tool
  268.             //
  269.             ///////////////////////////////////////////////////////////////////////////
  270.             this.deactivate = function () {
  271.  
  272.                 viewer.impl.removeOverlay(
  273.                     'Dotty.Viewing.Tool.TransformTool',
  274.                     _transformControlTx);
  275.  
  276.                 _transformControlTx.removeEventListener(
  277.                     'change',
  278.                     onTxChange);
  279.  
  280.                 _transformControlTx = null;
  281.  
  282.                 viewer.impl.removeOverlayScene(
  283.                     'Dotty.Viewing.Tool.TransformTool');
  284.  
  285.                 viewer.removeEventListener(
  286.                     Autodesk.Viewing.CAMERA_CHANGE_EVENT,
  287.                     onCameraChanged);
  288.  
  289.                 viewer.removeEventListener(
  290.                     Autodesk.Viewing.SELECTION_CHANGED_EVENT,
  291.                     onItemSelected);
  292.             };
  293.  
  294.             ///////////////////////////////////////////////////////////////////////////
  295.             //
  296.             //
  297.             ///////////////////////////////////////////////////////////////////////////
  298.             this.update = function (t) {
  299.  
  300.                 return false;
  301.             };
  302.  
  303.             this.handleSingleClick = function (event, button) {
  304.  
  305.  
  306.                 return false;
  307.             };
  308.  
  309.             this.handleDoubleClick = function (event, button) {
  310.  
  311.                 return false;
  312.             };
  313.  
  314.  
  315.             this.handleSingleTap = function (event) {
  316.  
  317.                 return false;
  318.             };
  319.  
  320.  
  321.             this.handleDoubleTap = function (event) {
  322.  
  323.                 return false;
  324.             };
  325.  
  326.             this.handleKeyDown = function (event, keyCode) {
  327.  
  328.                 return false;
  329.             };
  330.  
  331.             this.handleKeyUp = function (event, keyCode) {
  332.  
  333.                 return false;
  334.             };
  335.  
  336.             this.handleWheelInput = function (delta) {
  337.  
  338.                 return false;
  339.             };
  340.  
  341.             ///////////////////////////////////////////////////////////////////////////
  342.             //
  343.             //
  344.             ///////////////////////////////////////////////////////////////////////////
  345.             this.handleButtonDown = function (event, button) {
  346.  
  347.                 _hitPoint = getHitPoint(event);
  348.  
  349.                 _isDragging = true;
  350.  
  351.                 if (_transformControlTx.onPointerDown(event))
  352.                     return true;
  353.  
  354.                 //return _transRotControl.onPointerDown(event);
  355.                 return false;
  356.             };
  357.  
  358.             ///////////////////////////////////////////////////////////////////////////
  359.             //
  360.             //
  361.             ///////////////////////////////////////////////////////////////////////////
  362.             this.handleButtonUp = function (event, button) {
  363.  
  364.                 _isDragging = false;
  365.  
  366.                 if (_transformControlTx.onPointerUp(event))
  367.                     return true;
  368.  
  369.                 //return _transRotControl.onPointerUp(event);
  370.                 return false;
  371.             };
  372.  
  373.             ///////////////////////////////////////////////////////////////////////////
  374.             //
  375.             //
  376.             ///////////////////////////////////////////////////////////////////////////
  377.             this.handleMouseMove = function (event) {
  378.  
  379.                 if (_isDragging) {
  380.  
  381.                     if (_transformControlTx.onPointerMove(event)) {
  382.  
  383.                         return true;
  384.                     }
  385.  
  386.                     return false;
  387.                 }
  388.  
  389.                 if (_transformControlTx.onPointerHover(event))
  390.                     return true;
  391.  
  392.                 //return _transRotControl.onPointerHover(event);
  393.                 return false;
  394.             };
  395.  
  396.             ///////////////////////////////////////////////////////////////////////////
  397.             //
  398.             //
  399.             ///////////////////////////////////////////////////////////////////////////
  400.             this.handleGesture = function (event) {
  401.  
  402.                 return false;
  403.             };
  404.  
  405.             this.handleBlur = function (event) {
  406.  
  407.                 return false;
  408.             };
  409.  
  410.             this.handleResize = function () {
  411.  
  412.             };
  413.         }
  414.  
  415.         Autodesk.Viewing.Extension.call(this, viewer, options);
  416.  
  417.         var _self = this;
  418.  
  419.         _self.tool = null;
  420.  
  421.         _self.toolactivated = false;
  422.  
  423.         ///////////////////////////////////////////////////////
  424.         // extension load callback
  425.         //
  426.         ///////////////////////////////////////////////////////
  427.         _self.load = function () {
  428.  
  429.             console.log('Autodesk.ADN.Viewing.Extension.TransformTool loaded');
  430.  
  431.             return true;
  432.         };
  433.  
  434.         _self.onToolbarCreated = function () {
  435.             // Create a new toolbar group if it doesn't exist
  436.             this._group = this.viewer.toolbar.getControl('transformExtensionsToolbar');
  437.             if (!this._group) {
  438.                 this._group = new Autodesk.Viewing.UI.ControlGroup('transformExtensionsToolbar');
  439.                 this.viewer.toolbar.addControl(this._group);
  440.             }
  441.  
  442.             // Add a new button to the toolbar group
  443.             this._button = new Autodesk.Viewing.UI.Button('transformExtensionButton');
  444.             this._button.onClick = (ev) => {
  445.                 // Execute an action here
  446.                 if (!_self.toolactivated) {
  447.                     _self.initialize();
  448.                     _self.toolactivated = true;
  449.                 } else {
  450.                     viewer.toolController.deactivateTool(_self.tool.getName());
  451.                     _self.toolactivated = false;
  452.                 }
  453.             };
  454.             this._button.setToolTip('Transform Extension');
  455.             this._button.addClass('transformextensionicon');
  456.             this._group.addControl(this._button);
  457.         };
  458.  
  459.         _self.initialize = function () {
  460.             _self.tool = new TransformTool();
  461.  
  462.             viewer.toolController.registerTool(_self.tool);
  463.  
  464.             if (this.viewer.model.getInstanceTree()) {
  465.                 _self.customize();
  466.             } else {
  467.                 this.viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, _self.customize());
  468.             }
  469.         };
  470.  
  471.         _self.customize = function () {
  472.             viewer.toolController.activateTool(_self.tool.getName());
  473.         };
  474.  
  475.         ///////////////////////////////////////////////////////
  476.         // extension unload callback
  477.         //
  478.         ///////////////////////////////////////////////////////
  479.         _self.unload = function () {
  480.  
  481.             if (_self.tool) viewer.toolController.deactivateTool(_self.tool.getName());
  482.             // Clean our UI elements if we added any
  483.             if (this._group) {
  484.                 this._group.removeControl(this._button);
  485.                 if (this._group.getNumberOfControls() === 0) {
  486.                     this.viewer.toolbar.removeControl(this._group);
  487.                 }
  488.             }
  489.             console.log('Autodesk.ADN.Viewing.Extension.TransformTool unloaded');
  490.  
  491.             return true;
  492.         };
  493.  
  494.         ///////////////////////////////////////////////////////
  495.         // new random guid
  496.         //
  497.         ///////////////////////////////////////////////////////
  498.         function guid() {
  499.  
  500.             var d = new Date().getTime();
  501.  
  502.             var guid = 'xxxx-xxxx-xxxx-xxxx-xxxx'.replace(
  503.                 /[xy]/g,
  504.                 function (c) {
  505.                     var r = (d + Math.random() * 16) % 16 | 0;
  506.                     d = Math.floor(d / 16);
  507.                     return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
  508.                 });
  509.  
  510.             return guid;
  511.         };
  512.     };
  513.  
  514.     Autodesk.ADN.Viewing.Extension.TransformTool.prototype =
  515.         Object.create(Autodesk.Viewing.Extension.prototype);
  516.  
  517.     Autodesk.ADN.Viewing.Extension.TransformTool.prototype.constructor =
  518.         Autodesk.ADN.Viewing.Extension.TransformTool;
  519.  
  520.     Autodesk.Viewing.theExtensionManager.registerExtension(
  521.         'TransformationExtension',
  522.         Autodesk.ADN.Viewing.Extension.TransformTool);
  523. }

Теперь я могу уже безопасно импортировать transformtool в forgeView.js и вызывать registerTransformTool(). После вызова этой функции я могу передавать в конструктор GuiViewer3D зарегистрированное расширение TransformationExtension:

Код - JavaScript: [Выделить]
  1. ...
  2. registerTransformTool();
  3. this.viewer = new Autodesk.Viewing.GuiViewer3D(container, { extensions: ["TransformationExtension"]});
  4. ...

Код, добавляющий расширение:

Код - JavaScript: [Выделить]
  1. import './forgeView.css';
  2. import Message from './message';
  3. import repo from '../Repository';
  4. import { viewerCss, viewerJs } from './shared';
  5. import registerTransformTool from './transformtool';
  6.  
  7. let Autodesk = null;
  8.  
  9. export class ForgeView extends Component {
  10.  
  11.     constructor(props){
  12.       super(props);
  13.  
  14.       this.viewerDiv = React.createRef();
  15.       this.viewer = null;
  16.     }
  17.  
  18.     handleScriptLoad() {
  19.  
  20.         const options = repo.hasAccessToken() ?
  21.                             { accessToken: repo.getAccessToken() } :
  22.                             { env: 'Local' };
  23.  
  24.         Autodesk = window.Autodesk;
  25.  
  26.         registerTransformTool();
  27.  
  28.         const container = this.viewerDiv.current;
  29.         this.viewer = new Autodesk.Viewing.GuiViewer3D(container, { extensions: ["TransformationExtension"]});
  30.  
  31.         // uncomment this for Viewer debugging
  32.         //this.viewer.debugEvents(true);
  33.  
  34.         Autodesk.Viewing.Initializer(options, this.handleViewerInit.bind(this));
  35.     }
  36. ...

Теперь всё заработало, в начале статьи я добавил картинку с иллюстрацией процесса.

Источник: https://forge.autodesk.com/blog/add-viewer-extensions-design-automation-inventor-sample

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