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

28/02/2021

Forge Viewer: React+TypeScript - Показываем общедоступные модели Autodesk A360 в Forge Viewer

У нас уже есть несколько примеров, в которых используются React и Forge Viewer:

- Как использовать React в Forge Viewer DockingPanel

- https://github.com/Autodesk-Forge/viewer-react-express-headless

- https://github.com/Autodesk-Forge/forge-react-boiler.nodejs

Как использовать TypeScript:

- TypeScript для Forge Viewer и Client SDK Node.js

- Полностью определены! Обновления TypeScript и пример React TypeScript  (React + TypeScript)

- https://github.com/Autodesk-Forge/viewer-nodejs-typeview.sample

- https://github.com/dukedhx/viewer-react-typescript-workbox (React + TypeScript)

Ну... Не так много о совместном использовании React и TypeScript и совсем ничего о написании собственного расширения для Forge Viewer.

Я не хотел писать серверный код для этой статьи (т.е. получения токена доступа для Forge Viewer-а). Один из возможных путей, это показывать общедоступные модели (с публичным доступом) в Forge Viewer описан в этой статье. Именно этим подходом мы и воспользуемся. Второй возможный вариант - это разместить содержимое SVF где-нибудь в общедоступном месте - также не раз показывался, например, вот здесь.

Итак, для начала воспользуемся npm пакетом create-react-app, который теперь также поддерживает создание TypeScript проекта: добавляем TypeScript.

Дальнейшие шаги для подключения TypeScript Forge Viewer-а описаны в этой статье.

Я также воспользуюсь jQuery, поэтому мне понадобится установить соответствующий пакет: npm install @types/jquery.

В зависимости от настроек файла tsconfig.json Вам может понадобиться добавить в него  "types": ["forge-viewer"]. Для примера, показанного в этой статье, мне это не понадобилось.

Я добавил файл Viewer.tsx в проект для того, чтобы разместить компонент моего Forge Viewer-а и перенести необходимый код из упомянутой выше статьи.

Для тестирования я открыл доступ к модели Revit во Fusion Team, и немного изменил код загрузки модели во Viewer так, чтобы он загружал первые два листа, которые затем сравниваются между собой с помощью расширения "Autodesk.DiffTool" (см. статью об этом расширении (её перевод есть на нашем сайте)):

Код - JavaScript: [Выделить]
  1. import { Component } from 'react';
  2. import './Viewer.css';
  3.  
  4. class Viewer extends Component {
  5.   embedURLfromA360: string;
  6.   viewer?: Autodesk.Viewing.GuiViewer3D;
  7.  
  8.   constructor(props:any) {
  9.     // Note: in strict mode this will be called twice
  10.     // https://stackoverflow.com/questions/55119377/react-js-constructor-called-twice
  11.     super(props); 
  12.     this.embedURLfromA360 = "https://myhub.autodesk360.com/ue29c89b7/shares/public/SH7f1edQT22b515c761e81af7c91890bcea5?mode=embed"; // Revit file (A360/Forge/Napa.rvt)   
  13.   }
  14.  
  15.   render() {
  16.     return (
  17.       <div className="Viewer" id="MyViewerDiv" />
  18.     );
  19.   }
  20.  
  21.   public componentDidMount() {
  22.     if(!window.Autodesk) {
  23.       this.loadCss('https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css');        
  24.  
  25.       this.loadScript('https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.min.js')
  26.         .onload = () => {     
  27.           this.onScriptLoaded(); 
  28.         };
  29.     }
  30.   }
  31.  
  32.   public loadCss(src: string): HTMLLinkElement {
  33.     const link = document.createElement('link');
  34.     link.rel="stylesheet";
  35.     link.href=src;
  36.     link.type="text/css";
  37.     document.head.appendChild(link);        
  38.     return link;
  39.   }
  40.  
  41.   private loadScript(src: string): HTMLScriptElement {
  42.     const script = document.createElement('script');
  43.     script.type = 'text/javascript';
  44.     script.src = src;
  45.     script.async = true;
  46.     script.defer = true;
  47.     document.body.appendChild(script);        
  48.     return script;
  49.   }
  50.  
  51.   private onScriptLoaded() {
  52.     let that: any = this;
  53.     this.getURN(function (urn: string) {
  54.       var options = {
  55.         env: "AutodeskProduction",
  56.         getAccessToken: that.getForgeToken.bind(that),
  57.       };
  58.       var documentId: string = "urn:" + urn;
  59.       Autodesk.Viewing.Initializer(options, function onInitialized() {
  60.         Autodesk.Viewing.Document.load(documentId, that.onDocumentLoadSuccess.bind(that), that.onDocumentLoadError);
  61.       });
  62.     });
  63.   }
  64.  
  65.   getURN(onURNCallback: any) {
  66.     $.get({
  67.       url: this.embedURLfromA360
  68.         .replace("public", "metadata")
  69.         .replace("mode=embed", ""),
  70.       dataType: "json",
  71.       success: function (metadata) {
  72.         if (onURNCallback) {
  73.           let urn = btoa(metadata.success.body.urn)
  74.             .replace("/", "_")
  75.             .replace("=", "");
  76.           onURNCallback(urn);
  77.         }
  78.       },
  79.     });
  80.   }
  81.  
  82.   getForgeToken(onTokenCallback: any) {
  83.     $.post({
  84.       url: this.embedURLfromA360
  85.         .replace("public", "sign")
  86.         .replace("mode=embed", "oauth2=true"),
  87.       data: "{}",
  88.       success: function (oauth) {
  89.         if (onTokenCallback)
  90.           onTokenCallback(oauth.accessToken, oauth.validitySeconds);
  91.       },
  92.     });
  93.   }
  94.  
  95.   async onDocumentLoadSuccess(doc: Autodesk.Viewing.Document) {
  96.     // doc содержит ссылки на 3D сцены и 2D-виды.
  97.     var items = doc.getRoot().search({
  98.       'type': 'geometry',
  99.       'role': '2d'
  100.     });
  101.     if (items.length === 0) {
  102.       console.error('Document contains no viewables.');
  103.       return;
  104.     }
  105.  
  106.     var viewerDiv: any = document.getElementById('MyViewerDiv');
  107.     this.viewer = new Autodesk.Viewing.GuiViewer3D(viewerDiv);
  108.     this.viewer.start();
  109.  
  110.     // динамически загружаем расширение
  111.     const { MyExtension } = await import('./MyExtension');
  112.     MyExtension.register();
  113.     this.viewer.loadExtension('MyExtension');
  114.  
  115.     var options2 = {};
  116.     let that: any = this;
  117.     this.viewer.loadDocumentNode(doc, items[1], options2).then(function (model1: Autodesk.Viewing.Model) {
  118.       var options1: any = {};
  119.       options1.keepCurrentModels = true;
  120.      
  121.       that.viewer.loadDocumentNode(doc, items[0], options1).then(function (model2: Autodesk.Viewing.Model) {
  122.      
  123.         let extensionConfig: any = {}
  124.         extensionConfig['mimeType'] ='application/vnd.autodesk.revit'
  125.         extensionConfig['primaryModels'] = [model1]
  126.         extensionConfig['diffModels'] = [model2]
  127.         extensionConfig['diffMode'] =  'overlay'
  128.         extensionConfig['versionA'] =  '2'
  129.         extensionConfig['versionB'] =  '1'
  130.                                    
  131.         that.viewer.loadExtension('Autodesk.DiffTool', extensionConfig)
  132.           .then((res: any)=> {
  133.               console.log(res);                             
  134.           })
  135.           .catch(function(err: any) {
  136.               console.log(err);
  137.           })
  138.       });
  139.     });
  140.   }
  141.  
  142.   onDocumentLoadError(errorCode: Autodesk.Viewing.ErrorCodes) {
  143.  
  144.   }
  145. }
  146.  
  147. export default Viewer;

Я таже добавил файл Viewer.cssдля задания стилей div элемента с моим Forge Viewer-ом

Код - HTML: [Выделить]
  1. #MyViewerDiv {
  2.   width: 100%;
  3.   height: 100%;
  4.   margin: 0;
  5.   background-color: #f0f8ff;
  6.   position: relative;
  7. }

Затем я написал очень простое расширение MyExtension.ts, просто чтобы убедиться, что всё работает:

Код - JavaScript: [Выделить]
  1. export class MyExtension extends Autodesk.Viewing.Extension {
  2.   load() {
  3.     console.log('MyExtension has been loaded');
  4.     return true;
  5.   }
  6.  
  7.   unload() {
  8.     console.log('MyExtension has been unloaded');
  9.     return true;
  10.   }
  11.  
  12.   static register() {
  13.     Autodesk.Viewing.theExtensionManager.registerExtension(
  14.       "MyExtension",
  15.       MyExtension
  16.     );
  17.   }
  18. };

Поскольку библиотека Viewer-а загружается динамически (см. Viewer.tsx -> loadScript()), то и импортировать моё расширение также приходится динамически, в противном случае оно будет загружено перед библиотекой Viewer-а, что приведет к ошибке:

Код - JavaScript: [Выделить]
  1. // динамически загружаем расширение
  2. const { MyExtension } = await import('./MyExtension');
  3. MyExtension.register();
  4. this.viewer.loadExtension('MyExtension');

Теперь осталось указать наш компонент Viewer в компоненте приложения App и всё готово!

Код - JavaScript: [Выделить]
  1. import { Component } from 'react';
  2. import Viewer from './Viewer'
  3. import './App.css';
  4.  
  5. class App extends Component {
  6.   render () {
  7.     return (
  8.       <div className="App">
  9.         <Viewer />
  10.       </div>
  11.     );
  12.   }
  13. }
  14.  
  15. export default App;

Репозиторий приложения доступен по ссылке: https://github.com/adamenagy/viewer-react-typescript

 

Источник: https://forge.autodesk.com/blog/react-typescript-showing-shared-model

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