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" (см. статью об этом расширении (её перевод есть на нашем сайте)):
- import { Component } from 'react';
- import './Viewer.css';
- class Viewer extends Component {
- embedURLfromA360: string;
- viewer?: Autodesk.Viewing.GuiViewer3D;
- constructor(props:any) {
- // Note: in strict mode this will be called twice
- // https://stackoverflow.com/questions/55119377/react-js-constructor-called-twice
- super(props);
- this.embedURLfromA360 = "https://myhub.autodesk360.com/ue29c89b7/shares/public/SH7f1edQT22b515c761e81af7c91890bcea5?mode=embed"; // Revit file (A360/Forge/Napa.rvt)
- }
- render() {
- return (
- <div className="Viewer" id="MyViewerDiv" />
- );
- }
- public componentDidMount() {
- if(!window.Autodesk) {
- this.loadCss('https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.min.css');
- this.loadScript('https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.min.js')
- .onload = () => {
- this.onScriptLoaded();
- };
- }
- }
- public loadCss(src: string): HTMLLinkElement {
- const link = document.createElement('link');
- link.rel="stylesheet";
- link.href=src;
- link.type="text/css";
- document.head.appendChild(link);
- return link;
- }
- private loadScript(src: string): HTMLScriptElement {
- const script = document.createElement('script');
- script.type = 'text/javascript';
- script.src = src;
- script.async = true;
- script.defer = true;
- document.body.appendChild(script);
- return script;
- }
- private onScriptLoaded() {
- let that: any = this;
- this.getURN(function (urn: string) {
- var options = {
- env: "AutodeskProduction",
- getAccessToken: that.getForgeToken.bind(that),
- };
- var documentId: string = "urn:" + urn;
- Autodesk.Viewing.Initializer(options, function onInitialized() {
- Autodesk.Viewing.Document.load(documentId, that.onDocumentLoadSuccess.bind(that), that.onDocumentLoadError);
- });
- });
- }
- getURN(onURNCallback: any) {
- $.get({
- url: this.embedURLfromA360
- .replace("public", "metadata")
- .replace("mode=embed", ""),
- dataType: "json",
- success: function (metadata) {
- if (onURNCallback) {
- let urn = btoa(metadata.success.body.urn)
- .replace("/", "_")
- .replace("=", "");
- onURNCallback(urn);
- }
- },
- });
- }
- getForgeToken(onTokenCallback: any) {
- $.post({
- url: this.embedURLfromA360
- .replace("public", "sign")
- .replace("mode=embed", "oauth2=true"),
- data: "{}",
- success: function (oauth) {
- if (onTokenCallback)
- onTokenCallback(oauth.accessToken, oauth.validitySeconds);
- },
- });
- }
- async onDocumentLoadSuccess(doc: Autodesk.Viewing.Document) {
- // doc содержит ссылки на 3D сцены и 2D-виды.
- var items = doc.getRoot().search({
- 'type': 'geometry',
- 'role': '2d'
- });
- if (items.length === 0) {
- console.error('Document contains no viewables.');
- return;
- }
- var viewerDiv: any = document.getElementById('MyViewerDiv');
- this.viewer = new Autodesk.Viewing.GuiViewer3D(viewerDiv);
- this.viewer.start();
- // динамически загружаем расширение
- const { MyExtension } = await import('./MyExtension');
- MyExtension.register();
- this.viewer.loadExtension('MyExtension');
- var options2 = {};
- let that: any = this;
- this.viewer.loadDocumentNode(doc, items[1], options2).then(function (model1: Autodesk.Viewing.Model) {
- var options1: any = {};
- options1.keepCurrentModels = true;
- that.viewer.loadDocumentNode(doc, items[0], options1).then(function (model2: Autodesk.Viewing.Model) {
- let extensionConfig: any = {}
- extensionConfig['mimeType'] ='application/vnd.autodesk.revit'
- extensionConfig['primaryModels'] = [model1]
- extensionConfig['diffModels'] = [model2]
- extensionConfig['diffMode'] = 'overlay'
- extensionConfig['versionA'] = '2'
- extensionConfig['versionB'] = '1'
- that.viewer.loadExtension('Autodesk.DiffTool', extensionConfig)
- .then((res: any)=> {
- console.log(res);
- })
- .catch(function(err: any) {
- console.log(err);
- })
- });
- });
- }
- onDocumentLoadError(errorCode: Autodesk.Viewing.ErrorCodes) {
- }
- }
- export default Viewer;
Я таже добавил файл Viewer.cssдля задания стилей div элемента с моим Forge Viewer-ом
- #MyViewerDiv {
- width: 100%;
- height: 100%;
- margin: 0;
- background-color: #f0f8ff;
- position: relative;
- }
Затем я написал очень простое расширение MyExtension.ts, просто чтобы убедиться, что всё работает:
- export class MyExtension extends Autodesk.Viewing.Extension {
- load() {
- console.log('MyExtension has been loaded');
- return true;
- }
- unload() {
- console.log('MyExtension has been unloaded');
- return true;
- }
- static register() {
- Autodesk.Viewing.theExtensionManager.registerExtension(
- "MyExtension",
- MyExtension
- );
- }
- };
Поскольку библиотека Viewer-а загружается динамически (см. Viewer.tsx -> loadScript()), то и импортировать моё расширение также приходится динамически, в противном случае оно будет загружено перед библиотекой Viewer-а, что приведет к ошибке:
- // динамически загружаем расширение
- const { MyExtension } = await import('./MyExtension');
- MyExtension.register();
- this.viewer.loadExtension('MyExtension');
Теперь осталось указать наш компонент Viewer в компоненте приложения App и всё готово!
- import { Component } from 'react';
- import Viewer from './Viewer'
- import './App.css';
- class App extends Component {
- render () {
- return (
- <div className="App">
- <Viewer />
- </div>
- );
- }
- }
- export default App;
Репозиторий приложения доступен по ссылке: https://github.com/adamenagy/viewer-react-typescript
Источник: https://forge.autodesk.com/blog/react-typescript-showing-shared-model
Опубликовано 28.02.2021