├── thumbnail.png ├── src ├── components │ ├── App.css │ ├── App.jsx │ └── Viewer.jsx └── index.jsx ├── .gitignore ├── public └── index.html ├── package.json ├── LICENSE └── README.md /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/viewer-react-sample/HEAD/thumbnail.png -------------------------------------------------------------------------------- /src/components/App.css: -------------------------------------------------------------------------------- 1 | .app { 2 | display: flex; 3 | flex-flow: column; 4 | justify-content: center; 5 | align-items: center; 6 | } 7 | 8 | .app > * { 9 | margin: 0.5em; 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import App from './components/App'; 4 | 5 | const APS_ACCESS_TOKEN = ''; // Specify your access token 6 | const APS_MODEL_URN = ''; // Specify your model URN 7 | 8 | const root = ReactDOM.createRoot(document.getElementById('root')); 9 | if (!APS_ACCESS_TOKEN || !APS_MODEL_URN) { 10 | root.render(
Please specify APS_ACCESS_TOKEN and APS_MODEL_URN in the source code.
); 11 | } else { 12 | root.render(); 13 | } 14 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Viewer React Sample 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "viewer-react-sample", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "start": "react-scripts start", 6 | "build": "react-scripts build", 7 | "test": "react-scripts test", 8 | "eject": "react-scripts eject" 9 | }, 10 | "dependencies": { 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-scripts": "^5.0.1" 14 | }, 15 | "devDependencies": { 16 | "@types/forge-viewer": "^7.69.5", 17 | "prop-types": "^15.8.1" 18 | }, 19 | "browserslist": { 20 | "production": [ 21 | ">0.2%", 22 | "not dead", 23 | "not op_mini all" 24 | ], 25 | "development": [ 26 | "last 1 chrome version", 27 | "last 1 firefox version", 28 | "last 1 safari version" 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Autodesk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Viewer from './Viewer'; 3 | import './App.css'; 4 | 5 | class App extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.wrapper = null; 9 | this.state = { 10 | camera: null, 11 | selectedIds: [] 12 | }; 13 | } 14 | 15 | onInputChange = (ev) => { 16 | const val = ev.target.value.trim(); 17 | const ids = val.split(',').filter(e => e.length > 0).map(e => parseInt(e)).filter(e => Number.isInteger(e)); 18 | this.setState({ selectedIds: ids }); 19 | } 20 | 21 | render() { 22 | const { token, urn } = this.props; 23 | return ( 24 |
25 |
26 | this.setState({ camera: camera.getWorldPosition() })} 31 | onSelectionChange={({ viewer, ids }) => this.setState({ selectedIds: ids })} 32 | ref={ref => this.wrapper = ref} 33 | /> 34 |
35 |
36 | Camera Position: 37 | {this.state.camera && `${this.state.camera.x.toFixed(2)} ${this.state.camera.y.toFixed(2)} ${this.state.camera.z.toFixed(2)}`} 38 |
39 |
40 | Selected IDs: 41 | 42 |
43 | 44 |
45 | ); 46 | } 47 | } 48 | 49 | export default App; 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Viewer React Sample 2 | 3 | ![platforms](https://img.shields.io/badge/platform-windows%20%7C%20osx%20%7C%20linux-lightgray.svg) 4 | [![node.js](https://img.shields.io/badge/Node.js-16.17-blue.svg)](https://nodejs.org) 5 | [![npm](https://img.shields.io/badge/npm-8.15-blue.svg)](https://www.npmjs.com/) 6 | [![license](https://img.shields.io/:license-mit-green.svg)](https://opensource.org/licenses/MIT) 7 | 8 | > This code sample is referenced by the following blog post: https://aps.autodesk.com/blog/building-simple-react-wrapper-viewer 9 | 10 | Simple React application (bootstrapped using [Create React App](https://github.com/facebook/create-react-app)) with the viewer embedded into it using a custom wrapper component. 11 | 12 | ![thumbnail](thumbnail.png) 13 | 14 | ## Development 15 | 16 | ### Prerequisites 17 | 18 | - [Node.js](https://nodejs.org) 19 | - Terminal (for example, [Windows Command Prompt](https://en.wikipedia.org/wiki/Cmd.exe) or [macOS Terminal](https://support.apple.com/guide/terminal/welcome/mac)) 20 | - To keep things simple, this application does not use any server code - you'll need to hard-code an access token and a model URN directly into the client-side code 21 | - If you don't know how to get these values, try the following: 22 | - Go to https://aps-universal-test-app.autodesk.io/api/token and copy the value of the `access_token` property 23 | - Go to https://aps-universal-test-app.autodesk.io/api/models and copy the `urn` of one of the models 24 | 25 | ### Running 26 | 27 | - Clone this repository, and navigate to the repo folder in terminal 28 | - Install dependencies: `npm install` 29 | - Go to _src/index.jsx_, and update the `APS_ACCESS_TOKEN` and `APS_MODEL_URN` constants with your access token and model URN 30 | - Run `npm start` to open the application in development mode 31 | - The app should automatically open in your browser (if not, navigate to [http://localhost:3000](http://localhost:3000)) 32 | - When you make any changes to the code, the page will automatically reload 33 | 34 | ## Troubleshooting 35 | 36 | Please contact us via https://aps.autodesk.com/en/support/get-help. 37 | 38 | ## License 39 | 40 | This sample is licensed under the terms of the [MIT License](http://opensource.org/licenses/MIT). Please see the [LICENSE](LICENSE) file for more details. 41 | -------------------------------------------------------------------------------- /src/components/Viewer.jsx: -------------------------------------------------------------------------------- 1 | /// import * as Autodesk from "@types/forge-viewer"; 2 | 3 | import React from 'react'; 4 | import PropTypes from 'prop-types'; 5 | 6 | const { Autodesk } = window; 7 | 8 | const runtime = { 9 | /** @type {Autodesk.Viewing.InitializerOptions} */ 10 | options: null, 11 | /** @type {Promise} */ 12 | ready: null 13 | }; 14 | 15 | /** 16 | * Initializes global runtime for communicating with Autodesk Platform Services. 17 | * Calling this function repeatedly with different options is not allowed, and will result in an exception. 18 | * @async 19 | * @param {Autodesk.Viewing.InitializerOptions} options Runtime initialization options. 20 | * @returns {Promise} 21 | */ 22 | function initializeViewerRuntime(options) { 23 | if (!runtime.ready) { 24 | runtime.options = { ...options }; 25 | runtime.ready = new Promise((resolve) => Autodesk.Viewing.Initializer(runtime.options, resolve)); 26 | } else { 27 | if (['accessToken', 'getAccessToken', 'env', 'api', 'language'].some(prop => options[prop] !== runtime.options[prop])) { 28 | return Promise.reject('Cannot initialize another viewer runtime with different settings.') 29 | } 30 | } 31 | return runtime.ready; 32 | } 33 | 34 | /** 35 | * Wrapper for the Autodesk Platform Services viewer component. 36 | */ 37 | class Viewer extends React.Component { 38 | constructor(props) { 39 | super(props); 40 | /** @type {HTMLDivElement} */ 41 | this.container = null; 42 | /** @type {Autodesk.Viewing.GuiViewer3D} */ 43 | this.viewer = null; 44 | } 45 | 46 | componentDidMount() { 47 | initializeViewerRuntime(this.props.runtime || {}) 48 | .then(_ => { 49 | this.viewer = new Autodesk.Viewing.GuiViewer3D(this.container); 50 | this.viewer.start(); 51 | this.viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, this.onViewerCameraChange); 52 | this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onViewerSelectionChange); 53 | this.updateViewerState({}); 54 | }) 55 | .catch(err => console.error(err)); 56 | } 57 | 58 | componentWillUnmount() { 59 | if (this.viewer) { 60 | this.viewer.removeEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, this.onViewerCameraChange); 61 | this.viewer.removeEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.onViewerSelectionChange); 62 | this.viewer.finish(); 63 | this.viewer = null; 64 | } 65 | } 66 | 67 | componentDidUpdate(prevProps) { 68 | if (this.viewer) { 69 | this.updateViewerState(prevProps); 70 | } 71 | } 72 | 73 | updateViewerState(prevProps) { 74 | if (this.props.urn && this.props.urn !== prevProps.urn) { 75 | Autodesk.Viewing.Document.load( 76 | 'urn:' + this.props.urn, 77 | (doc) => this.viewer.loadDocumentNode(doc, doc.getRoot().getDefaultGeometry()), 78 | (code, message, errors) => console.error(code, message, errors) 79 | ); 80 | } else if (!this.props.urn && this.viewer.model) { 81 | this.viewer.unloadModel(this.viewer.model); 82 | } 83 | 84 | const selectedIds = this.viewer.getSelection(); 85 | if (JSON.stringify(this.props.selectedIds || []) !== JSON.stringify(selectedIds)) { 86 | this.viewer.select(this.props.selectedIds); 87 | } 88 | } 89 | 90 | onViewerCameraChange = () => { 91 | if (this.props.onCameraChange) { 92 | this.props.onCameraChange({ viewer: this.viewer, camera: this.viewer.getCamera() }); 93 | } 94 | } 95 | 96 | onViewerSelectionChange = () => { 97 | if (this.props.onSelectionChange) { 98 | this.props.onSelectionChange({ viewer: this.viewer, ids: this.viewer.getSelection() }); 99 | } 100 | } 101 | 102 | render() { 103 | return
this.container = ref}>
; 104 | } 105 | } 106 | 107 | Viewer.propTypes = { 108 | /** Viewer runtime initialization options. */ 109 | runtime: PropTypes.object, 110 | /** URN of model to be loaded. */ 111 | urn: PropTypes.string, 112 | /** List of selected object IDs. */ 113 | selectedIds: PropTypes.arrayOf(PropTypes.number), 114 | /** Callback for when the viewer camera changes. */ 115 | onCameraChange: PropTypes.func, 116 | /** Callback for when the viewer selectio changes. */ 117 | onSelectionChange: PropTypes.func 118 | }; 119 | 120 | export default Viewer; 121 | --------------------------------------------------------------------------------