├── 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 | 
4 | [](https://nodejs.org)
5 | [](https://www.npmjs.com/)
6 | [](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 | 
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 |
--------------------------------------------------------------------------------