├── .gitignore ├── LICENSE ├── README.md ├── config.js ├── package.json ├── public ├── css │ ├── main.css │ └── w2ui-1.4.min.css ├── extensions │ ├── BasicSkeleton │ │ ├── README.md │ │ ├── config.json │ │ └── contents │ │ │ ├── img │ │ │ └── icon.png │ │ │ ├── main.css │ │ │ └── main.js │ ├── CameraRotation │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ ├── img │ │ │ │ └── emblem_camera.png │ │ │ ├── main.css │ │ │ └── main.js │ │ └── extension.gif │ ├── CustomPropertiesExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ └── main.js │ │ └── extension.gif │ ├── DrawToolExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ ├── img │ │ │ │ └── icon.png │ │ │ ├── main.css │ │ │ └── main.js │ │ └── extension.gif │ ├── Edit2dExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ └── main.js │ │ └── extension.gif │ ├── GoogleMapsLocator │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ ├── main.css │ │ │ └── main.js │ │ └── extension.gif │ ├── IconMarkupExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ ├── main.css │ │ │ └── main.js │ │ └── extension.gif │ ├── NestedViewerExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ ├── main.css │ │ │ └── main.js │ │ └── extension.gif │ ├── PhasingExtension │ │ ├── README.md │ │ ├── assets │ │ │ └── sample.csv │ │ ├── config.json │ │ ├── contents │ │ │ └── main.js │ │ └── extension.gif │ ├── PotreeExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ ├── PotreeExtension.js │ │ │ ├── potree.js │ │ │ └── workers │ │ │ │ ├── BinaryDecoderWorker.js │ │ │ │ ├── DEMWorker.js │ │ │ │ ├── EptBinaryDecoderWorker.js │ │ │ │ ├── EptLaszipDecoderWorker.js │ │ │ │ ├── GreyhoundBinaryDecoderWorker.js │ │ │ │ ├── LASDecoderWorker.js │ │ │ │ └── LASLAZWorker.js │ │ └── extension.gif │ ├── RoomLocatorExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ └── main.js │ │ ├── extension-1.jpeg │ │ └── extension-2.jpeg │ ├── TabSelectionExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ └── main.js │ │ └── extension.gif │ ├── TransformationExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ └── main.js │ │ └── extension.gif │ ├── XLSExtension │ │ ├── README.md │ │ ├── config.json │ │ ├── contents │ │ │ ├── ForgeXLS.js │ │ │ ├── img │ │ │ │ └── excel.png │ │ │ ├── libraries │ │ │ │ ├── Blob.js │ │ │ │ ├── FileSaver.min.js │ │ │ │ ├── clipboard.min.js │ │ │ │ └── notify.min.js │ │ │ ├── main.css │ │ │ ├── main.js │ │ │ └── modeldata.js │ │ └── extension.jpg │ ├── config.json │ ├── extensionloader.css │ └── extensionloader.js ├── img │ └── GitHub_Logo.png ├── index.html └── js │ ├── ApsTree.js │ └── ApsViewer.js ├── routes ├── auth.js └── models.js ├── services └── aps.js ├── start.js └── thumbnail.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # multer temp upload folder 9 | tmp/ 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | .vscode 17 | package-lock.json 18 | .DS_Store 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (http://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Typescript v1 declaration files 46 | typings/ 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # dotenv environment variables file 64 | .env 65 | 66 | # extension config 67 | public/extensions/config.json 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Autodesk Inc. 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aps-extensions 2 | 3 | [![Node.js](https://img.shields.io/badge/Node.js-4.4.3-blue.svg)](https://nodejs.org/) 4 | [![npm](https://img.shields.io/badge/npm-2.15.1-blue.svg)](https://www.npmjs.com/) 5 | ![Platforms](https://img.shields.io/badge/platform-windows%20%7C%20osx%20%7C%20linux-lightgray.svg) 6 | [![License](http://img.shields.io/:license-mit-blue.svg)](http://opensource.org/licenses/MIT) 7 | 8 | [![oAuth2](https://img.shields.io/badge/oAuth2-v1-green.svg)](http://forge.autodesk.com/) 9 | [![Data-Management](https://img.shields.io/badge/Data%20Management-v1-green.svg)](http://forge.autodesk.com/) 10 | [![Model-Derivative](https://img.shields.io/badge/Model%20Derivative-v2-green.svg)](http://forge.autodesk.com/) 11 | [![Viewer](https://img.shields.io/badge/Viewer-v7-green.svg)](http://forge.autodesk.com/) 12 | 13 | [![Level](https://img.shields.io/badge/Level-Basic-blue.svg)](http://forge.autodesk.com/) 14 | 15 | # Description 16 | 17 | This sample is a collection of extensions ready to be reused. Just add reference to the required files, load and use. Check each extension documentation for details. 18 | 19 | 1. [Camera Rotation](public/extensions/CameraRotation) 20 | 2. [Icon Markup](public/extensions/IconMarkupExtension) 21 | 3. [Nested Viewer](public/extensions/NestedViewerExtension) 22 | 4. [Transform](public/extensions/TransformationExtension) 23 | 5. [GoogleMapsLocator](public/extensions/GoogleMapsLocator) 24 | 6. [Draw Tool Extension](public/extensions/DrawToolExtension) 25 | 7. [Custom Properties Extension](public/extensions/CustomPropertiesExtension) 26 | 8. [XLS Extension](public/extensions/XLSExtension) 27 | 8. [Edit2D Extension](public/extensions/Edit2dExtension) 28 | 9. [Phasing Extension](public/extensions/PhasingExtension) 29 | 30 | Extensions were created using a [Basic Skeleton](public/extensions/BasicSkeleton). 31 | 32 | This sample is based on the [Learn APS](http://learnforge.autodesk.io) tutorials in the section *View modelss*. 33 | 34 | ## Thumbnail 35 | 36 | ![thumbnail](/thumbnail.png) 37 | 38 | ## Live Demo 39 | 40 | Extensions are dynamically loaded and unloaded for testing on the live version. 41 | 42 | [aps-extensions.autodesk.io](https://aps-extensions.autodesk.io) 43 | 44 | # Setup 45 | 46 | To use this sample, you will need Autodesk developer credentials. Visit the [APS Developer Portal](https://developer.autodesk.com), sign up for an account, then [create an app](https://developer.autodesk.com/myapps/create). For this new app, use `http://localhost:3000/api/aps/callback/oauth` as the Callback URL, although it is not used on a 2-legged flow. Finally, take note of the **Client ID** and **Client Secret**. 47 | 48 | ## Run locally 49 | 50 | Install [NodeJS](https://nodejs.org). 51 | 52 | Clone this project or download it. It's recommended to install [GitHub Desktop](https://desktop.github.com/). To clone it via command line, use the following (**Terminal** on MacOSX/Linux, **Git Shell** on Windows): 53 | 54 | git clone https://github.com/autodesk-platform-services/aps-extensions.git 55 | 56 | To run it, install the required packages, set the enviroment variables with your client ID & Secret and finally start it. Via command line, navigate to the folder where this repository was cloned to and use the following commands: 57 | 58 | Mac OSX/Linux (Terminal) 59 | 60 | npm install 61 | export APS_CLIENT_ID=<> 62 | export APS_CLIENT_SECRET=<> 63 | npm start 64 | 65 | Windows (use **Node.js command line** from the Start menu) 66 | 67 | npm install 68 | set APS_CLIENT_ID=<> 69 | set APS_CLIENT_SECRET=<> 70 | npm start 71 | 72 | Open the browser: [http://localhost:3000](http://localhost:3000). 73 | 74 | # Steps to plug in new extension 75 | 76 | 1) Create folder in public/extensions with same name as extension name. 77 | Structure of the extension folder is as shown below: 78 |
 79 | ExtensionName[Folder]
 80 |         | 
 81 |         |->contents
 82 |         |     |
 83 |         |     |->main.js
 84 |         |     |->main.css
 85 |         |     |->assets[folder]
 86 |         |->config.json
 87 | 
88 | Refer the [BasicSkeleton Extension](https://github.com/autodesk-platform-services/aps-extensions/tree/main/public/extensions/BasicSkeleton) for boilerplate code. 89 | 90 | 2) Each extension folder should be self-contained code, so that it's easily shareable between projects. 91 | Extension[Folder]/config.json is meant for keeping the config of an extension and for sharing. 92 | 93 | Extension config schema: 94 |
 95 | {
 96 |     "name":"extension name registered",
 97 |     "displayname": "display name for the extension in list",
 98 |     "description": "description for the extension",
 99 |     "options":{model specific information array to pass on to extension constructor},
100 |     "viewerversion":"viewer version",
101 |     "loadonstartup": "true or false",
102 |     "filestoload":{
103 |         "cssfiles":["css file(s)"],
104 |         "jsfiles":["js file(s)"]
105 |     },
106 |     "bloglink":"Blog link for working explanation of the extension (optional)",
107 |     "includeinlist":"true or false"
108 | }
109 | 
110 | Example: [IconMarkupExtension config.json](https://github.com/autodesk-platform-services/aps-extensions/blob/main/public/extensions/IconMarkupExtension/config.json) 111 | 112 | > Note: If your extension relies on event Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT to load, in load function check if the data is already loaded, if not only then add the event listener, below code shows the structure. 113 |
114 | class MyExtension extends Autodesk.Viewing.Extension {
115 |     ...
116 |     load() {
117 |         ...
118 |         if (this.viewer.model.getInstanceTree()) {
119 |             this.onTreeReady();
120 |         } else {
121 |             this.viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, this.onTreeReady.bind(this));
122 |         }
123 |         ...
124 |     }
125 |     ...
126 |     onTreeReady() {
127 |         const tree = this.viewer.model.getInstanceTree();
128 |         ...
129 |     }
130 |     ...
131 | }
132 | 
133 | Example: [IconMarkupExtension load function](https://github.com/autodesk-platform-services/aps-extensions/blob/main/public/extensions/IconMarkupExtension/contents/main.js#L26) 134 | 135 | > Note: After the new extension is added, please restart the server. 136 | 137 | # Understanding extensionloader and using it in aps app 138 | 139 | The way loose coupling between extensions and aps app is achived is with custom event, if you want to use extensionloader in your aps app, follow the three steps: 140 | 141 | 1) Copy paste the [extensions](https://github.com/autodesk-platform-services/aps-extensions/tree/main/public/extensions) in public folder of your app or in the folder where the index file resides. 142 | 143 | 2) Include below script in index.html file 144 |
145 | 
146 | 
147 | 148 | 3) Here's the linking part between the app and the extensionloader, in viewer [onDocumentLoadSuccess](https://github.com/autodesk-platform-services/aps-extensions/blob/main/public/js/ApsViewer.js#L35) function, emit an event to inform the extensionloader that viewer has loaded the model with the below [code](https://github.com/autodesk-platform-services/aps-extensions/blob/main/public/js/ApsViewer.js#L39): 149 |
150 | var ViewerInstance = new CustomEvent("viewerinstance", {detail: {viewer: viewer}});      
151 | document.dispatchEvent(ViewerInstance);
152 | 
153 | To load an extension programmatically, emit the below event. 154 |
155 |  var LoadExtensionEvent = new CustomEvent("loadextension", {
156 |               detail: {
157 |                 extension: "Extension1",
158 |                 viewer: viewer
159 |              }
160 |          });
161 |  document.dispatchEvent(LoadExtensionEvent);
162 |  
163 | 164 | To unload extension: 165 |
166 |  var UnloadExtensionEvent = new CustomEvent("unloadextension", {
167 |               detail: {
168 |                 extension: "Extension1",
169 |                 viewer: viewer
170 |              }
171 |          });
172 |  document.dispatchEvent(UnloadExtensionEvent);
173 | 
174 | >Note: If the extension needs additional UI elements, first option we suggest is use the viewer UI [Autodesk.Viewing.UI.DockingPanel](https://forge.autodesk.com/en/docs/viewer/v2/reference/javascript/dockingpanel) 175 | 176 | ## Packages used 177 | 178 | The [Autodesk Platform Services](https://www.npmjs.com/package/forge-apis) packages are included by default. Some other non-Autodesk packages are used, including [express](https://www.npmjs.com/package/express) and [multer](https://www.npmjs.com/package/multer) for upload. 179 | 180 | # Tips & tricks 181 | 182 | For local development/ testing, consider using the [nodemon](https://www.npmjs.com/package/nodemon) package, which auto-restarts your node application after any modification to your code. To install it, use: 183 | 184 | sudo npm install -g nodemon 185 | 186 | Then, instead of **npm run dev**, use the following: 187 | 188 | npm run nodemon 189 | 190 | Which executes **nodemon server.js --ignore www/**, where the **--ignore** parameter indicates that the app should not restart if files under the **www** folder are modified. 191 | 192 | ## Troubleshooting 193 | 194 | After installing GitHub Desktop for Windows, on the Git Shell, if you see the ***error setting certificate verify locations*** error, then use the following command: 195 | 196 | git config --global http.sslverify "false" 197 | 198 | # License 199 | 200 | This sample is licensed under the terms of the [MIT License](http://opensource.org/licenses/MIT). 201 | Please see the [LICENSE](LICENSE) file for full details. 202 | 203 | ## Written by 204 | 205 | The [APS Advocates](http://forge.autodesk.com) team: 206 | 207 | * Varun Patil [@VarunPatil578](https://twitter.com/VarunPatil578) 208 | * Petr Broz [@ipetrbroz](https://twitter.com/ipetrbroz) 209 | * Augusto Goncalves [@augustomaia](https://twitter.com/augustomaia) 210 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | // Autodesk Platform configuration 20 | let { APS_CLIENT_ID, APS_CLIENT_SECRET, PORT } = process.env; 21 | if (!APS_CLIENT_ID || !APS_CLIENT_SECRET) { 22 | console.warn('Missing some of the environment variables.'); 23 | process.exit(1); 24 | } 25 | PORT = PORT || 3000; 26 | 27 | module.exports = { 28 | APS_CLIENT_ID, 29 | APS_CLIENT_SECRET, 30 | PORT 31 | }; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "forgeextensions", 3 | "version": "1.0.0", 4 | "description": "Sample application for learnforge.autodesk.io.", 5 | "main": "start.js", 6 | "scripts": { 7 | "start": "node start.js" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "@aps_sdk/authentication": "^0.1.0-beta.1", 12 | "@aps_sdk/autodesk-sdkmanager": "^0.0.7-beta.1", 13 | "@aps_sdk/model-derivative": "^0.1.0-beta.1", 14 | "@aps_sdk/oss": "^0.1.0-beta.1", 15 | "express": "^4.18.1", 16 | "express-formidable": "^1.2.0", 17 | "formidable": "^2.0.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /public/css/main.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | min-height: 100%; 3 | height: 100%; 4 | } 5 | 6 | .fill{ 7 | height: calc(100vh - 100px); 8 | } 9 | 10 | body { 11 | padding-top: 60px; /* space for the top nav bar */ 12 | /* margin-right: 30px; */ 13 | } 14 | 15 | #appBuckets { 16 | overflow: auto; 17 | width: 100%; 18 | height: calc(100vh - 150px); 19 | } 20 | 21 | #apsViewer { 22 | width: 100%; 23 | } 24 | 25 | .nopadding{padding: 0;} 26 | 27 | .pl{padding-left: 10px;} 28 | 29 | .left_panel{padding: 0 10px;} 30 | 31 | .right_panel{padding: 0 10px;} -------------------------------------------------------------------------------- /public/extensions/BasicSkeleton/README.md: -------------------------------------------------------------------------------- 1 | # Basic Skeleton 2 | 3 | This basic skeleton extension can be used as boilerplate code for creating a custom extension. Please refer to [this tutorial](https://learnforge.autodesk.io/#/viewer/extensions/skeleton) for additional details. 4 | 5 | -------------------------------------------------------------------------------- /public/extensions/BasicSkeleton/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"BasicSkeleton", 3 | "displayname": "Basic Skeleton", 4 | "description": "Basic Skeleton of an extension, can be used as boilerplate code.", 5 | "options":{}, 6 | "viewerversion":"7.*", 7 | "loadonstartup": "false", 8 | "filestoload":{ 9 | "cssfiles":["main.css"], 10 | "jsfiles":["main.js"] 11 | }, 12 | "includeinlist":"false" 13 | } -------------------------------------------------------------------------------- /public/extensions/BasicSkeleton/contents/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/BasicSkeleton/contents/img/icon.png -------------------------------------------------------------------------------- /public/extensions/BasicSkeleton/contents/main.css: -------------------------------------------------------------------------------- 1 | .myAwesomeExtensionIcon { 2 | background-image: url(img/icon.png); 3 | background-size: 24px; 4 | background-repeat: no-repeat; 5 | background-position: center; 6 | } -------------------------------------------------------------------------------- /public/extensions/BasicSkeleton/contents/main.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | class BasicSkeleton extends Autodesk.Viewing.Extension { 19 | constructor(viewer, options) { 20 | super(viewer, options); 21 | this._group = null; 22 | this._button = null; 23 | } 24 | 25 | load() { 26 | console.log('MyAwesomeExtensions has been loaded'); 27 | return true; 28 | } 29 | 30 | unload() { 31 | // Clean our UI elements if we added any 32 | if (this._group) { 33 | this._group.removeControl(this._button); 34 | if (this._group.getNumberOfControls() === 0) { 35 | this.viewer.toolbar.removeControl(this._group); 36 | } 37 | } 38 | console.log('MyAwesomeExtensions has been unloaded'); 39 | return true; 40 | } 41 | 42 | onToolbarCreated() { 43 | // Create a new toolbar group if it doesn't exist 44 | this._group = this.viewer.toolbar.getControl('allMyAwesomeExtensionsToolbar'); 45 | if (!this._group) { 46 | this._group = new Autodesk.Viewing.UI.ControlGroup('allMyAwesomeExtensionsToolbar'); 47 | this.viewer.toolbar.addControl(this._group); 48 | } 49 | 50 | // Add a new button to the toolbar group 51 | this._button = new Autodesk.Viewing.UI.Button('myAwesomeExtensionButton'); 52 | this._button.onClick = (ev) => { 53 | // Execute an action here 54 | }; 55 | this._button.setToolTip('BasicSkeleton'); 56 | this._button.addClass('myAwesomeExtensionIcon'); 57 | this._group.addControl(this._button); 58 | } 59 | } 60 | 61 | Autodesk.Viewing.theExtensionManager.registerExtension('BasicSkeleton', BasicSkeleton); -------------------------------------------------------------------------------- /public/extensions/CameraRotation/README.md: -------------------------------------------------------------------------------- 1 | # Camera Rotation 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=CameraRotation) 4 | 5 | This extension can be used for model demo purpose, it just rotates the building or the model for look around. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Usage 10 | 11 | Click on the toolbar button to start rotating, click again to stop. 12 | 13 | ## Setup 14 | 15 | Include the CSS & JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 16 | 17 | ```xml 18 | 19 | 20 | ``` 21 | 22 | After Viewer is ready, preferable inside `onDocumentLoadSuccess`, load the extension 23 | 24 | ```javascript 25 | viewer.loadExtension("CameraRotation") 26 | ``` 27 | 28 | ## How it works 29 | -------------------------------------------------------------------------------- /public/extensions/CameraRotation/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"CameraRotation", 3 | "displayname": "Camera Rotation", 4 | "description":"Rotates the model continously untill stopped, can be used for demoing a large model.", 5 | "options":{}, 6 | "loadonstartup": "false", 7 | "filestoload":{ 8 | "cssfiles":["main.css"], 9 | "jsfiles":["main.js"] 10 | }, 11 | "gif": "extension.gif", 12 | "includeinlist":"true" 13 | } -------------------------------------------------------------------------------- /public/extensions/CameraRotation/contents/img/emblem_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/CameraRotation/contents/img/emblem_camera.png -------------------------------------------------------------------------------- /public/extensions/CameraRotation/contents/main.css: -------------------------------------------------------------------------------- 1 | .toolbarCameraRotation { 2 | background-image: url(./img/emblem_camera.png); 3 | background-size: 36px; 4 | background-repeat: no-repeat; 5 | background-position: center; 6 | } -------------------------------------------------------------------------------- /public/extensions/CameraRotation/contents/main.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | /////////////////////////////////////////////////////////////////////////////// 20 | // TurnTable extension illustrating camera rotation around the model 21 | // by Denis Grigor, November 2018 22 | // 23 | /////////////////////////////////////////////////////////////////////////////// 24 | 25 | class TurnTableExtension extends Autodesk.Viewing.Extension { 26 | constructor(viewer, options) { 27 | super(viewer, options); 28 | this.viewer = viewer; 29 | this._group = null; 30 | this._button = null; 31 | this.customize = this.customize.bind(this); 32 | } 33 | 34 | load() { 35 | if (this.viewer.model.getInstanceTree()) { 36 | this.customize(); 37 | } else { 38 | this.viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, this.customize()); 39 | } 40 | return true; 41 | } 42 | unload() { 43 | console.log('TurnTableExtension is now unloaded!'); 44 | // Clean our UI elements if we added any 45 | if (this._group) { 46 | this._group.removeControl(this._button); 47 | if (this._group.getNumberOfControls() === 0) { 48 | this.viewer.toolbar.removeControl(this._group); 49 | } 50 | } 51 | return true; 52 | } 53 | 54 | customize() { 55 | 56 | let viewer = this.viewer; 57 | 58 | this._button = new Autodesk.Viewing.UI.Button('turnTableButton'); 59 | this._button.addClass('toolbarCameraRotation'); 60 | this._button.setToolTip('Start/Stop Camera rotation'); 61 | 62 | // _group 63 | this._group = new Autodesk.Viewing.UI.ControlGroup('CameraRotateToolbar'); 64 | this._group.addControl(this._button); 65 | this.viewer.toolbar.addControl(this._group); 66 | 67 | let started = false; 68 | 69 | let rotateCamera = () => { 70 | if (started) { 71 | requestAnimationFrame(rotateCamera); 72 | } 73 | 74 | const nav = viewer.navigation; 75 | const up = nav.getCameraUpVector(); 76 | const axis = new THREE.Vector3(0, 0, 1); 77 | const speed = 10.0 * Math.PI / 180; 78 | const matrix = new THREE.Matrix4().makeRotationAxis(axis, speed * 0.1); 79 | 80 | let pos = nav.getPosition(); 81 | pos.applyMatrix4(matrix); 82 | up.applyMatrix4(matrix); 83 | nav.setView(pos, new THREE.Vector3(0, 0, 0)); 84 | nav.setCameraUpVector(up); 85 | var viewState = viewer.getState(); 86 | // viewer.restoreState(viewState); 87 | 88 | }; 89 | 90 | this._button.onClick = function (e) { 91 | started = !started; 92 | if (started) rotateCamera() 93 | }; 94 | 95 | } 96 | 97 | } 98 | 99 | Autodesk.Viewing.theExtensionManager.registerExtension('CameraRotation', 100 | TurnTableExtension); -------------------------------------------------------------------------------- /public/extensions/CameraRotation/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/CameraRotation/extension.gif -------------------------------------------------------------------------------- /public/extensions/CustomPropertiesExtension/README.md: -------------------------------------------------------------------------------- 1 | # Custom Properties Extension 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=CustomPropertiesExtension) 4 | 5 | Extension showing custom properties in the Properties palette, based on their `dbId`. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Setup 10 | 11 | Include the JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 12 | 13 | ```xml 14 | 15 | ``` 16 | 17 | Load the extension passing the properties you want to add to various objects based on their `dbId`. 18 | 19 | ```javascript 20 | viewer.loadExtension('CustomPropertiesExtension', { 21 | "properties": { 22 | "1": { 23 | "My Group": { 24 | "My Property": "My Property Value" 25 | } 26 | }, 27 | "3": { 28 | "My Other Group": { 29 | "My Other Property": "My Other Property Value" 30 | } 31 | } 32 | } 33 | }) 34 | ``` 35 | 36 | ## Configuration 37 | 38 | The `properties` contains the dbId's you assign custom properties to. 39 | 40 | ## How it works 41 | 42 | The custom properties will appear in the Properties palette. 43 | 44 | ## Futher reading 45 | 46 | The sample is based on [this blog post](https://forge.autodesk.com/blog/adding-custom-properties-property-panel). 47 | -------------------------------------------------------------------------------- /public/extensions/CustomPropertiesExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"CustomPropertiesExtension", 3 | "displayname": "Custom Properties", 4 | "description": "Custom Properties example, info provided in config is showed in Properties palette based on selected object's dbId.", 5 | "options":{ 6 | "properties": { 7 | "1": { 8 | "My Group": { 9 | "My Property": "My Property Value", 10 | "My Second Property": "My Second Property Value" 11 | } 12 | }, 13 | "3": { 14 | "My Other Group": { 15 | "My Other Property": "My Other Property Value" 16 | } 17 | } 18 | } 19 | }, 20 | "editoptions":"true", 21 | "viewerversion":"7.*", 22 | "loadonstartup": "false", 23 | "filestoload":{ 24 | "cssfiles":[], 25 | "jsfiles":["main.js"] 26 | }, 27 | "bloglink":"https://github.com/autodesk-platform-services/aps-extensions/tree/main/public/extensions/CustomPropertiesExtension", 28 | "includeinlist":"true", 29 | "gif": "extension.gif", 30 | "externaldependencies":[] 31 | } -------------------------------------------------------------------------------- /public/extensions/CustomPropertiesExtension/contents/main.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | // ******************************************* 20 | // Custom Property Panel 21 | // ******************************************* 22 | class CustomPropertyPanel extends Autodesk.Viewing.Extensions.ViewerPropertyPanel { 23 | constructor (viewer, options) { 24 | super(viewer, options); 25 | this.properties = options.properties || {}; 26 | } 27 | 28 | setAggregatedProperties(propertySet) { 29 | Autodesk.Viewing.Extensions.ViewerPropertyPanel.prototype.setAggregatedProperties.call(this, propertySet); 30 | 31 | // add your custom properties here 32 | const dbids = propertySet.getDbIds(); 33 | dbids.forEach(id => { 34 | var propsForObject = this.properties[id.toString()]; 35 | if (propsForObject) { 36 | for (const groupName in propsForObject) { 37 | const group = propsForObject[groupName]; 38 | for (const propName in group) { 39 | const prop = group[propName]; 40 | this.addProperty(propName, prop, groupName); 41 | } 42 | } 43 | } 44 | }); 45 | } 46 | }; 47 | 48 | // ******************************************* 49 | // Custom Properties Extension 50 | // ******************************************* 51 | class CustomPropertiesExtension extends Autodesk.Viewing.Extension { 52 | constructor(viewer, options) { 53 | super(viewer, options); 54 | 55 | this.panel = new CustomPropertyPanel(viewer, options); 56 | } 57 | 58 | async load() { 59 | var ext = await this.viewer.getExtension('Autodesk.PropertiesManager'); 60 | ext.setPanel(this.panel); 61 | 62 | return true; 63 | } 64 | 65 | async unload() { 66 | var ext = await this.viewer.getExtension('Autodesk.PropertiesManager'); 67 | ext.setDefaultPanel(); 68 | 69 | return true; 70 | } 71 | } 72 | 73 | Autodesk.Viewing.theExtensionManager.registerExtension('CustomPropertiesExtension', CustomPropertiesExtension); -------------------------------------------------------------------------------- /public/extensions/CustomPropertiesExtension/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/CustomPropertiesExtension/extension.gif -------------------------------------------------------------------------------- /public/extensions/DrawToolExtension/README.md: -------------------------------------------------------------------------------- 1 | # Draw Tool Extension 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=DrawToolExtension) 4 | 5 | A simple tool that will allow us to draw boxes and spheres. Please use the Draw Tool in only full screen mode. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Usage 10 | 11 | Load the extension and enter fullscreen. And here's how the drawing works: 12 | 13 | . on mouse button down event the tool will start "drawing" the geometry in the XY plane 14 | . on mouse button up event, the tool will start monitoring mouse move events to control the height of the geometry 15 | . on mouse click event, the geometry will be finalized 16 | 17 | ## Setup 18 | 19 | Include the CSS & JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 20 | 21 | ```xml 22 | 23 | 24 | ``` 25 | 26 | ## Futher reading 27 | 28 | Detailed technical explaination can be found in [this blog post](https://forge.autodesk.com/blog/custom-tools-forge-viewer). 29 | 30 | ## Author 31 | [Petr Broz](https://twitter.com/ipetrbroz) 32 | 33 | -------------------------------------------------------------------------------- /public/extensions/DrawToolExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"DrawToolExtension", 3 | "displayname": "Draw Tool", 4 | "description": "A simple tool that will allow us to draw boxes and spheres.", 5 | "options":{}, 6 | "editoptions":"true", 7 | "viewerversion":"7.*", 8 | "loadonstartup": "false", 9 | "filestoload":{ 10 | "cssfiles":["main.css"], 11 | "jsfiles":["main.js"] 12 | }, 13 | "bloglink":"https://forge.autodesk.com/blog/custom-tools-forge-viewer", 14 | "includeinlist":"true", 15 | "gif": "extension.gif" 16 | } -------------------------------------------------------------------------------- /public/extensions/DrawToolExtension/contents/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/DrawToolExtension/contents/img/icon.png -------------------------------------------------------------------------------- /public/extensions/DrawToolExtension/contents/main.css: -------------------------------------------------------------------------------- 1 | #box-draw-tool-button { 2 | background-image: url(https://img.icons8.com/color/64/000000/left-view.png); 3 | background-size: 24px; 4 | background-repeat: no-repeat; 5 | background-position: center; 6 | } 7 | 8 | #sphere-draw-tool-button { 9 | background-image: url(https://img.icons8.com/color/24/000000/sphere.png); 10 | background-size: 24px; 11 | background-repeat: no-repeat; 12 | background-position: center; 13 | } -------------------------------------------------------------------------------- /public/extensions/DrawToolExtension/contents/main.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | const BoxDrawToolName = 'box-draw-tool'; 19 | const SphereDrawToolName = 'sphere-draw-tool'; 20 | const DrawToolOverlay = 'draw-tool-overlay'; 21 | 22 | // Simple viewer tool for drawing boxes and spheres 23 | class DrawTool extends Autodesk.Viewing.ToolInterface { 24 | constructor() { 25 | super(); 26 | 27 | // Hack: delete functions defined *on the instance* of the tool. 28 | // We want the tool controller to call our class methods instead. 29 | delete this.register; 30 | delete this.deregister; 31 | delete this.activate; 32 | delete this.deactivate; 33 | delete this.getPriority; 34 | delete this.handleMouseMove; 35 | delete this.handleButtonDown; 36 | delete this.handleButtonUp; 37 | delete this.handleSingleClick; 38 | 39 | this.state = ''; // '' (inactive), 'xy' (specifying extent in the XY plane), or 'z' (specifying height) 40 | this.names = [BoxDrawToolName, SphereDrawToolName]; 41 | } 42 | 43 | register() { 44 | console.log('DrawTool registered.'); 45 | } 46 | 47 | deregister() { 48 | console.log('DrawTool unregistered.'); 49 | } 50 | 51 | activate(name, viewer) { 52 | this.viewer = viewer; 53 | this.state = ''; 54 | this.mode = (name === BoxDrawToolName) ? 'box' : 'sphere'; 55 | console.log('DrawTool', name, 'activated.'); 56 | } 57 | 58 | deactivate(name) { 59 | this.viewer = null; 60 | this.state = ''; 61 | console.log('DrawTool', name, 'deactivated.'); 62 | } 63 | 64 | getPriority() { 65 | return 1; // Use any number higher than 0 (the priority of all default tools) 66 | } 67 | 68 | handleButtonDown(event, button) { 69 | // If left button is pressed and we're not drawing already 70 | if (button === 0 && this.state === '') { 71 | let boxRectangle = event.target.getBoundingClientRect(); 72 | let clientX = event.clientX - boxRectangle.left; 73 | let clientY = event.clientY - boxRectangle.top; 74 | 75 | // Create new geometry and add it to an overlay 76 | if (this.mode === 'box') { 77 | const geometry = new THREE.BufferGeometry().fromGeometry(new THREE.BoxGeometry(1, 1, 1)); 78 | const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 }); 79 | this.mesh = new THREE.Mesh(geometry, material); 80 | } else { 81 | const geometry = new THREE.BufferGeometry().fromGeometry(new THREE.SphereGeometry(0.5, 16, 16)); 82 | const material = new THREE.MeshPhongMaterial({ color: 0x0000ff }); 83 | this.mesh = new THREE.Mesh(geometry, material); 84 | } 85 | this.viewer.impl.addOverlay(DrawToolOverlay, this.mesh); 86 | 87 | // Initialize the 3 values that will control the geometry's size (1st corner in the XY plane, 2nd corner in the XY plane, and height) 88 | this.corner1 = this.corner2 = this._intersect(clientX, clientY); 89 | this.height = 0.1; 90 | this._update(); 91 | this.state = 'xy'; // Now we're drawing in the XY plane 92 | return true; // Stop the event from going to other tools in the stack 93 | } 94 | // Otherwise let another tool handle the event, and make note that our tool is now bypassed 95 | this.bypassed = true; 96 | return false; 97 | } 98 | 99 | handleButtonUp(event, button) { 100 | // If left button is released and we're drawing in the XY plane 101 | if (button === 0 && this.state === 'xy') { 102 | let boxRectangle = event.target.getBoundingClientRect(); 103 | let clientX = event.clientX - boxRectangle.left; 104 | let clientY = event.clientY - boxRectangle.top; 105 | 106 | // Update the 2nd corner in the XY plane and switch to the 'z' state 107 | this.corner2 = this._intersect(clientX, clientY); 108 | this._update(); 109 | this.state = 'z'; 110 | this.lastClientY = clientY; // Store the current mouse Y coordinate to compute height later on 111 | return true; // Stop the event from going to other tools in the stack 112 | } 113 | // Otherwise let another tool handle the event, and make note that our tool is no longer bypassed 114 | this.bypassed = false; 115 | return false; 116 | } 117 | 118 | handleMouseMove(event) { 119 | let boxRectangle = event.target.getBoundingClientRect(); 120 | let clientX = event.clientX - boxRectangle.left; 121 | let clientY = event.clientY - boxRectangle.top; 122 | 123 | if (!this.bypassed && this.state === 'xy') { 124 | // If we're in the "XY plane drawing" state, and not bypassed by another tool 125 | this.corner2 = this._intersect(clientX, clientY); 126 | this._update(); 127 | return true; 128 | } else if (!this.bypassed && this.state === 'z') { 129 | // If we're in the "height drawing" state, and not bypassed by another tool 130 | this.height = this.lastClientY - clientY; 131 | this._update(); 132 | return true; 133 | } 134 | // Otherwise let another tool handle the event 135 | return false; 136 | } 137 | 138 | handleSingleClick(event, button) { 139 | // If left button is clicked and we're currently in the "height drawing" state 140 | if (button === 0 && this.state === 'z') { 141 | this.state = ''; 142 | return true; // Stop the event from going to other tools in the stack 143 | } 144 | // Otherwise let another tool handle the event 145 | return false; 146 | } 147 | 148 | _intersect(clientX, clientY) { 149 | return this.viewer.impl.intersectGround(clientX, clientY); 150 | } 151 | 152 | _update() { 153 | const { corner1, corner2, height, mesh } = this; 154 | const minX = Math.min(corner1.x, corner2.x), maxX = Math.max(corner1.x, corner2.x); 155 | const minY = Math.min(corner1.y, corner2.y), maxY = Math.max(corner1.y, corner2.y); 156 | mesh.position.x = minX + 0.5 * (maxX - minX); 157 | mesh.position.y = minY + 0.5 * (maxY - minY); 158 | mesh.position.z = corner1.z + 0.5 * height; 159 | mesh.scale.x = maxX - minX; 160 | mesh.scale.y = maxY - minY; 161 | mesh.scale.z = height; 162 | this.viewer.impl.invalidate(true, true, true); 163 | } 164 | } 165 | 166 | class DrawToolExtension extends Autodesk.Viewing.Extension { 167 | constructor(viewer, options) { 168 | super(viewer, options); 169 | this.tool = new DrawTool(); 170 | } 171 | 172 | load() { 173 | this.viewer.toolController.registerTool(this.tool); 174 | this.viewer.impl.createOverlayScene(DrawToolOverlay); 175 | this._createUI(); 176 | console.log('DrawToolExtension loaded.'); 177 | return true; 178 | } 179 | 180 | unload() { 181 | this.viewer.toolController.deregisterTool(this.tool); 182 | this.viewer.impl.removeOverlayScene(DrawToolOverlay); 183 | this._removeUI(); 184 | console.log('DrawToolExtension unloaded.'); 185 | return true; 186 | } 187 | 188 | onToolbarCreated() { 189 | this._createUI(); 190 | } 191 | 192 | _createUI() { 193 | const toolbar = this.viewer.toolbar; 194 | if (toolbar && !this.group) { 195 | const controller = this.viewer.toolController; 196 | this.button1 = new Autodesk.Viewing.UI.Button('box-draw-tool-button'); 197 | this.button1.onClick = (ev) => { 198 | if (controller.isToolActivated(BoxDrawToolName)) { 199 | controller.deactivateTool(BoxDrawToolName); 200 | this.button1.setState(Autodesk.Viewing.UI.Button.State.INACTIVE); 201 | } else { 202 | controller.deactivateTool(SphereDrawToolName); 203 | controller.activateTool(BoxDrawToolName); 204 | this.button2.setState(Autodesk.Viewing.UI.Button.State.INACTIVE); 205 | this.button1.setState(Autodesk.Viewing.UI.Button.State.ACTIVE); 206 | } 207 | }; 208 | this.button1.setToolTip('Box Draw Tool'); 209 | 210 | this.button2 = new Autodesk.Viewing.UI.Button('sphere-draw-tool-button'); 211 | this.button2.onClick = (ev) => { 212 | if (controller.isToolActivated(SphereDrawToolName)) { 213 | controller.deactivateTool(SphereDrawToolName); 214 | this.button2.setState(Autodesk.Viewing.UI.Button.State.INACTIVE); 215 | } else { 216 | controller.deactivateTool(BoxDrawToolName); 217 | controller.activateTool(SphereDrawToolName); 218 | this.button1.setState(Autodesk.Viewing.UI.Button.State.INACTIVE); 219 | this.button2.setState(Autodesk.Viewing.UI.Button.State.ACTIVE); 220 | } 221 | }; 222 | this.button2.setToolTip('Sphere Draw Tool'); 223 | 224 | this.group = new Autodesk.Viewing.UI.ControlGroup('draw-tool-group'); 225 | this.group.addControl(this.button1); 226 | this.group.addControl(this.button2); 227 | toolbar.addControl(this.group); 228 | } 229 | } 230 | 231 | _removeUI() { 232 | if (this.group) { 233 | this.viewer.toolbar.removeControl(this.group); 234 | this.group = null; 235 | } 236 | } 237 | } 238 | 239 | Autodesk.Viewing.theExtensionManager.registerExtension('DrawToolExtension', DrawToolExtension); -------------------------------------------------------------------------------- /public/extensions/DrawToolExtension/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/DrawToolExtension/extension.gif -------------------------------------------------------------------------------- /public/extensions/Edit2dExtension/README.md: -------------------------------------------------------------------------------- 1 | # Edit2D 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=Edit2dExtension) 4 | 5 | Shows some Edit2D extension functionality. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Usage 10 | 11 | Enable the extension, click on the model element, use the `gizmo` to move the element. 12 | 13 | ## Setup 14 | 15 | Include the CSS & JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 16 | 17 | ```xml 18 | 19 | ``` 20 | 21 | After Viewer is ready, preferably inside `onDocumentLoadSuccess`, load the extension 22 | 23 | ```javascript 24 | viewer.loadExtension("Edit2dExtension") 25 | ``` 26 | 27 | ## How it works 28 | 29 | It uses the code introduced in [Edit2D Setup](https://aps.autodesk.com/en/docs/viewer/v7/developers_guide/advanced_options/edit2d-setup/) available in the Viewer online documentation 30 | 31 | -------------------------------------------------------------------------------- /public/extensions/Edit2dExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"Edit2dExtension", 3 | "displayname": "Edit2D Extension", 4 | "description":"Purpose of this extension is to show some Edit2D functionality.", 5 | "options":{}, 6 | "loadonstartup": "false", 7 | "filestoload":{ 8 | "jsfiles":["main.js"], 9 | "cssfiles": [] 10 | }, 11 | "gif": "extension.gif", 12 | "includeinlist":"true", 13 | "externaldependencies":[ 14 | {"type": "css", "link": "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css"} 15 | ] 16 | } -------------------------------------------------------------------------------- /public/extensions/Edit2dExtension/contents/main.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | // ******************************************* 20 | // Edit2D Extension 21 | // ******************************************* 22 | class Edit2dExtension extends Autodesk.Viewing.Extension { 23 | constructor(viewer, options) { 24 | super(viewer, options); 25 | } 26 | 27 | async load() { 28 | this._edit2D = await this.viewer.loadExtension('Autodesk.Edit2D'); 29 | this._edit2D.registerDefaultTools(); 30 | 31 | return true; 32 | } 33 | 34 | unload() { 35 | this.stopTool(); 36 | 37 | if (this._group) { 38 | this.viewer.toolbar.removeControl(this._group); 39 | } 40 | 41 | return true; 42 | } 43 | 44 | onToolbarCreated() { 45 | // Create a new toolbar group if it doesn't exist 46 | this._group = this.viewer.toolbar.getControl('Edit2dExtensionsToolbar'); 47 | if (!this._group) { 48 | this._group = new Autodesk.Viewing.UI.ControlGroup('Edit2dExtensionsToolbar'); 49 | this.viewer.toolbar.addControl(this._group); 50 | } 51 | 52 | // Polygon Button 53 | let polygonButton = new Autodesk.Viewing.UI.Button('PolygonButton'); 54 | polygonButton.onClick = (ev) => { 55 | if (polygonButton.getState() === Autodesk.Viewing.UI.Button.State.ACTIVE) { 56 | this.stopTool(); 57 | } else { 58 | this.startTool("polygonTool"); 59 | polygonButton.setState(Autodesk.Viewing.UI.Button.State.ACTIVE) 60 | } 61 | }; 62 | polygonButton.setToolTip('Draw Polygon'); 63 | polygonButton.icon.classList.add("fas", "fa-draw-polygon"); 64 | this._group.addControl(polygonButton); 65 | 66 | // Edit Button 67 | let editButton = new Autodesk.Viewing.UI.Button('EditButton'); 68 | editButton.onClick = (ev) => { 69 | if (editButton.getState() === Autodesk.Viewing.UI.Button.State.ACTIVE) { 70 | this.stopTool(); 71 | } else { 72 | this.startTool("polygonEditTool"); 73 | editButton.setState(Autodesk.Viewing.UI.Button.State.ACTIVE) 74 | } 75 | }; 76 | editButton.setToolTip('Edit Polygon'); 77 | editButton.icon.classList.add("fas", "fa-edit"); 78 | this._group.addControl(editButton); 79 | 80 | // Move Button 81 | let moveButton = new Autodesk.Viewing.UI.Button('MoveButton'); 82 | moveButton.onClick = (ev) => { 83 | if (moveButton.getState() === Autodesk.Viewing.UI.Button.State.ACTIVE) { 84 | this.stopTool(); 85 | } else { 86 | this.startTool("moveTool"); 87 | moveButton.setState(Autodesk.Viewing.UI.Button.State.ACTIVE) 88 | } 89 | }; 90 | moveButton.setToolTip('Move Polygon'); 91 | moveButton.icon.classList.add("fas", "fa-arrows-alt"); 92 | this._group.addControl(moveButton); 93 | } 94 | 95 | startTool(toolName) { 96 | this.stopTool(); 97 | 98 | let controller = this.viewer.toolController; 99 | controller.activateTool(this._edit2D.defaultTools[toolName].getName()); 100 | } 101 | 102 | stopTool() { 103 | for (let button of this._group._controls) { 104 | button.setState(Autodesk.Viewing.UI.Button.State.INACTIVE); 105 | } 106 | 107 | let activeTool = this.viewer.toolController.getActiveTool(); 108 | var isEdit2DTool = activeTool && activeTool.getName().startsWith("Edit2"); 109 | if (isEdit2DTool) { 110 | activeTool.selection?.clear(); 111 | this.viewer.toolController.deactivateTool(activeTool.getName()); 112 | } 113 | } 114 | } 115 | 116 | Autodesk.Viewing.theExtensionManager.registerExtension('Edit2dExtension', Edit2dExtension); -------------------------------------------------------------------------------- /public/extensions/Edit2dExtension/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/Edit2dExtension/extension.gif -------------------------------------------------------------------------------- /public/extensions/GoogleMapsLocator/README.md: -------------------------------------------------------------------------------- 1 | # Google Maps Locator 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=GoogleMapsLocator) 4 | 5 | APS Viewer extension to show location of model on google maps. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Usage 10 | 11 | - Load the extension in the viewer 12 | - Click the new maps icon in the toolbar 13 | - New docking panel opens showing google maps and the model location mentioned in extension config 14 | 15 | ## Setup 16 | 17 | Include the CSS & JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 18 | 19 | ```xml 20 | 21 | 22 | ``` 23 | 24 | The following sample uses [font-awesome](https://fontawesome.com) icons, but any CSS icon library can be used and google maps api script file. 25 | Get your Google Maps API Key [here](https://developers.google.com/maps/documentation/javascript/get-api-key) 26 | 27 | ```xml 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/extensions/GoogleMapsLocator/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"GoogleMapsLocator", 3 | "displayname": "GoogleMapsLocator", 4 | "description": "Show location of model on google maps.", 5 | "options":{"lattitude":0,"longitude":0}, 6 | "editoptions":"true", 7 | "viewerversion":"7.*", 8 | "loadonstartup": "false", 9 | "filestoload":{ 10 | "cssfiles":["main.css"], 11 | "jsfiles":["main.js"] 12 | }, 13 | "includeinlist":"true", 14 | "gif": "extension.gif", 15 | "externaldependencies":[ 16 | {"type": "css", "link": "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css"}, 17 | {"type": "js", "link": "https://maps.googleapis.com/maps/api/js?key="} 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /public/extensions/GoogleMapsLocator/contents/main.css: -------------------------------------------------------------------------------- 1 | #googlemap { 2 | width: 100%; 3 | height: calc(100% - 70px); 4 | } -------------------------------------------------------------------------------- /public/extensions/GoogleMapsLocator/contents/main.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | // ******************************************* 20 | // MiniMap Extension 21 | // ******************************************* 22 | function GoogleMapsLocator(viewer, options) { 23 | Autodesk.Viewing.Extension.call(this, viewer, options); 24 | this.viewer = viewer; 25 | this.panel = null; // dock panel 26 | this.map = null; // Google Map 27 | this.geoExtension = null; // Autodesk.Geolocation extension 28 | this.options = options; 29 | 30 | var _this = this; 31 | // load extension... 32 | viewer.loadExtension('Autodesk.Geolocation').then(function (ext) { _this.geoExtension = ext }); 33 | } 34 | 35 | GoogleMapsLocator.prototype = Object.create(Autodesk.Viewing.Extension.prototype); 36 | GoogleMapsLocator.prototype.constructor = GoogleMapsLocator; 37 | 38 | GoogleMapsLocator.prototype.load = function () { 39 | return true; 40 | }; 41 | 42 | GoogleMapsLocator.prototype.onToolbarCreated = function () { 43 | // Create a new toolbar group if it doesn't exist 44 | this._group = this.viewer.toolbar.getControl('customExtensions'); 45 | if (!this._group) { 46 | this._group = new Autodesk.Viewing.UI.ControlGroup('customExtensions'); 47 | this.viewer.toolbar.addControl(this._group); 48 | } 49 | 50 | // Add a new button to the toolbar group 51 | this._button = new Autodesk.Viewing.UI.Button('showMiniMap'); 52 | this._button.onClick = (ev) => { 53 | if (this.geoExtension === null || !this.geoExtension.hasGeolocationData()) { 54 | alert('Model does not contain geo location information'); 55 | return; 56 | } 57 | 58 | this._enabled = !this._enabled; 59 | this._button.setState(this._enabled ? 0 : 1); 60 | 61 | // if null, create it 62 | if (this.panel == null) { 63 | this.panel = new MiniMapPanel(this.viewer, this.viewer.container, 'miniMapPanel', 'Mini Map'); 64 | } 65 | 66 | // show/hide docking panel 67 | this.panel.setVisible(!this.panel.isVisible()); 68 | 69 | if (!this._enabled) return; 70 | 71 | // initialize the map 72 | if (this.map == null) { 73 | this.map = new google.maps.Map(document.getElementById('googlemap'), { 74 | zoom: 16, 75 | center: { lat: this.options.lattitude, lng: this.options.longitude }, 76 | mapTypeId: 'satellite', 77 | rotateControl: false, 78 | streetViewControl: false, 79 | tilt: 0 80 | }); 81 | } 82 | 83 | // draw model boundoung box & center 84 | var bb = this.viewer.model.getBoundingBox(); 85 | this.drawBoundingBox(bb.min, bb.max); 86 | this.cameraChanged(this.viewer.autocam); // first run (center of the model) 87 | 88 | }; 89 | this._button.setToolTip('Show map location'); 90 | this._button.container.children[0].classList.add('fas', 'fa-map-marked'); 91 | this._group.addControl(this._button); 92 | 93 | // listen to camera changes 94 | this.viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, (e) => { this.cameraChanged(e.target.autocam) }); 95 | }; 96 | 97 | GoogleMapsLocator.prototype.drawBoundingBox = function (min, max) { 98 | // basic check... 99 | if (this.map == null) return; 100 | if (this.geoExtension == null) return; 101 | 102 | // prepare a polygon with the bounding box information 103 | var polygon = []; 104 | polygon.push({ x: min.x, y: min.y }); 105 | polygon.push({ x: min.x, y: max.y }); 106 | polygon.push({ x: max.x, y: max.y }); 107 | polygon.push({ x: max.x, y: min.y }); 108 | 109 | this.drawPolygon(polygon); 110 | } 111 | 112 | GoogleMapsLocator.prototype.drawPolygon = function (polygon) { 113 | // basic check... 114 | var _this = this; 115 | if (_this.map == null) return; 116 | if (_this.geoExtension == null) return; 117 | 118 | // prepare the polygon coordinate to draw it 119 | var coords = []; 120 | polygon.forEach(function (point) { 121 | var geoLoc = _this.geoExtension.lmvToLonLat(point); 122 | coords.push({ lat: geoLoc.y, lng: geoLoc.x }); 123 | }); 124 | var polyOptions = { 125 | path: coords, 126 | strokeColor: '#FF0000', 127 | strokeOpacity: 0.8, 128 | strokeWeight: 2, 129 | fillColor: '#FF0000', 130 | fillOpacity: 0.1, 131 | }; 132 | var polygon = new google.maps.Polygon(polyOptions); 133 | polygon.setMap(_this.map); 134 | } 135 | 136 | GoogleMapsLocator.prototype.cameraChanged = function (camera) { 137 | // basic check... 138 | if (this.map == null) return; 139 | if (this.geoExtension == null) return; 140 | 141 | // adjust the center of the map 142 | var geoLoc = this.geoExtension.lmvToLonLat(camera.center); 143 | this.map.setCenter({ lat: geoLoc.y, lng: geoLoc.x }); 144 | } 145 | 146 | 147 | GoogleMapsLocator.prototype.unload = function () { 148 | if (this.viewer.toolbar !== null) this.viewer.toolbar.removeControl(this.subToolbar); 149 | if (this.panel !== null) this.panel.setVisible(false); 150 | if (this._group) { 151 | this._group.removeControl(this._button); 152 | if (this._group.getNumberOfControls() === 0) { 153 | this.viewer.toolbar.removeControl(this._group); 154 | } 155 | } 156 | return true; 157 | }; 158 | 159 | Autodesk.Viewing.theExtensionManager.registerExtension('GoogleMapsLocator', GoogleMapsLocator); 160 | 161 | // ******************************************* 162 | // MiniMap Panel 163 | // ******************************************* 164 | function MiniMapPanel(viewer, container, id, title, options) { 165 | this.viewer = viewer; 166 | Autodesk.Viewing.UI.DockingPanel.call(this, container, id, title, options); 167 | 168 | // the style of the docking panel 169 | // use this built-in style to support Themes on Viewer 4+ 170 | this.container.classList.add('docking-panel-container-solid-color-a'); 171 | this.container.style.top = "10px"; 172 | this.container.style.left = "10px"; 173 | this.container.style.width = "300px"; 174 | this.container.style.height = "300px"; 175 | this.container.style.resize = "auto"; 176 | 177 | // here we check the existence of a googlemap div 178 | // and remove it if it does 179 | var googleMap = document.getElementById('googlemap'); 180 | if(!!googleMap){ 181 | googleMap.parentElement.removeChild(googleMap) 182 | } 183 | 184 | // this is where we should place the content of our panel 185 | var div = document.createElement('div'); 186 | div.id = 'googlemap'; 187 | this.container.appendChild(div); 188 | } 189 | MiniMapPanel.prototype = Object.create(Autodesk.Viewing.UI.DockingPanel.prototype); 190 | MiniMapPanel.prototype.constructor = MiniMapPanel; 191 | -------------------------------------------------------------------------------- /public/extensions/GoogleMapsLocator/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/GoogleMapsLocator/extension.gif -------------------------------------------------------------------------------- /public/extensions/IconMarkupExtension/README.md: -------------------------------------------------------------------------------- 1 | # Icon Markup Extension 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=IconMarkupExtension) 4 | 5 | Extension showing labels on top of elements, based on their `dbId`. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Setup 10 | 11 | Include the CSS & JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 12 | 13 | ```xml 14 | 15 | 16 | ``` 17 | 18 | The following sample uses [font-awesome](https://fontawesome.com) icons, but any CSS icon library can be used. 19 | 20 | ```xml 21 | 22 | ``` 23 | Load the extension passing the `dbId`, `label` and `css` for the label. Toolbar button and onClick event can also be configured. 24 | 25 | ```javascript 26 | viewer.loadExtension('IconMarkupExtension', { 27 | button: { 28 | icon: 'fa-thermometer-half', 29 | tooltip: 'Show Temperature' 30 | }, 31 | icons: [ 32 | { dbId: 3944, label: '300°C', css: 'fas fa-thermometer-full' }, 33 | { dbId: 721, label: '356°C', css: 'fas fa-thermometer-full' }, 34 | { dbId: 10312, label: '450°C', css: 'fas fa-thermometer-empty' }, 35 | { dbId: 563, css: 'fas fa-exclamation-triangle' }, 36 | ], 37 | onClick: (id) => { 38 | viewers.select(id); 39 | viewers.utilities.fitToView(); 40 | switch (id){ 41 | case 563: 42 | alert('Sensor offline'); 43 | } 44 | } 45 | }) 46 | ``` 47 | 48 | ## Configuration 49 | 50 | The `button` attribute defines the toolbar button. The `icons` contains the labels shwon on the model. Last, `onClick` is triggered when the user clicks on the label. 51 | 52 | ## How it works 53 | 54 | Labels are positioned over the Viewer canvas and repositioned when the camera changes (e.g. pan, zoom or rotate). 55 | 56 | ## Futher reading 57 | 58 | Detailed technical explaination can be found at [this blog post](https://forge.autodesk.com/blog/placing-custom-markup-dbid). This [live sample](http://forgeplant.herokuapp.com) shows an sample application. 59 | -------------------------------------------------------------------------------- /public/extensions/IconMarkupExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"IconMarkupExtension", 3 | "displayname": "Icon Markups", 4 | "description": "Markups example, based on options provided in config, info is showed on particular object referring dbid.", 5 | "options":{ 6 | "icons": [ 7 | { "dbId": 9256, "label": "30°C", "css": "fas fa-thermometer-full" }, 8 | { "dbId": 920, "label": "35°C", "css": "fas fa-thermometer-full" }, 9 | { "dbId": 1626, "label": "45°C", "css": "fas fa-thermometer-empty" } 10 | ], 11 | "button": { 12 | "icon": "fa-thermometer-half", 13 | "tooltip": "Show Temperature" 14 | } 15 | }, 16 | "editoptions":"true", 17 | "viewerversion":"7.*", 18 | "loadonstartup": "false", 19 | "filestoload":{ 20 | "cssfiles":["main.css"], 21 | "jsfiles":["main.js"] 22 | }, 23 | "bloglink":"https://github.com/autodesk-forge/forge-extensions/tree/master/public/extensions/IconMarkupExtension", 24 | "includeinlist":"true", 25 | "gif": "extension.gif", 26 | "externaldependencies":[ 27 | {"type": "css", "link": "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css"} 28 | ] 29 | 30 | } -------------------------------------------------------------------------------- /public/extensions/IconMarkupExtension/contents/main.css: -------------------------------------------------------------------------------- 1 | 2 | #layout { 3 | width: 100%; 4 | height: calc(100vh); 5 | } 6 | 7 | #sidebar { 8 | size: 35px; 9 | } 10 | 11 | label.markup { 12 | position: absolute; 13 | white-space: nowrap; 14 | cursor: pointer; 15 | } 16 | 17 | label.update { 18 | z-index: 1; 19 | } 20 | 21 | .temperatureBorder{ 22 | background-color: rgba(256, 256, 256, 0.5); 23 | border: 1px solid black; 24 | padding: 5px; 25 | } 26 | 27 | .temperatureHigh { 28 | font-size: 25px; 29 | color: red; 30 | } 31 | 32 | .temperatureOk { 33 | font-size: 20px; 34 | color: blue; 35 | } 36 | 37 | .temperatureYellow { 38 | font-size: 30px; 39 | color: yellow; 40 | } 41 | 42 | .maintenance{ 43 | font-size: 40px; 44 | color: green; 45 | } -------------------------------------------------------------------------------- /public/extensions/IconMarkupExtension/contents/main.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | class IconMarkupExtension extends Autodesk.Viewing.Extension { 19 | constructor(viewer, options) { 20 | super(viewer, options); 21 | this._group = null; 22 | this._button = null; 23 | this._icons = options.icons || []; 24 | } 25 | 26 | load() { 27 | if (this.viewer.model.getInstanceTree()) { 28 | this.customize(); 29 | } else { 30 | this.viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, this.customize()); 31 | } 32 | return true; 33 | } 34 | 35 | unload() { 36 | // Clean our UI elements if we added any 37 | if (this._group) { 38 | this._group.removeControl(this._button); 39 | if (this._group.getNumberOfControls() === 0) { 40 | this.viewer.toolbar.removeControl(this._group); 41 | } 42 | } 43 | $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer label.markup').remove(); 44 | return true; 45 | } 46 | 47 | customize(){ 48 | const updateIconsCallback = () => { 49 | if (this._enabled) { 50 | this.updateIcons(); 51 | } 52 | }; 53 | this.viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, updateIconsCallback); 54 | this.viewer.addEventListener(Autodesk.Viewing.ISOLATE_EVENT, updateIconsCallback); 55 | this.viewer.addEventListener(Autodesk.Viewing.HIDE_EVENT, updateIconsCallback); 56 | this.viewer.addEventListener(Autodesk.Viewing.SHOW_EVENT, updateIconsCallback); 57 | } 58 | 59 | onToolbarCreated() { 60 | // Create a new toolbar group if it doesn't exist 61 | this._group = this.viewer.toolbar.getControl('customExtensions'); 62 | if (!this._group) { 63 | this._group = new Autodesk.Viewing.UI.ControlGroup('customExtensions'); 64 | this.viewer.toolbar.addControl(this._group); 65 | } 66 | 67 | // Add a new button to the toolbar group 68 | this._button = new Autodesk.Viewing.UI.Button('IconExtension'); 69 | this._button.onClick = (ev) => { 70 | this._enabled = !this._enabled; 71 | this.showIcons(this._enabled); 72 | this._button.setState(this._enabled ? 0 : 1); 73 | 74 | }; 75 | this._button.setToolTip(this.options.button.tooltip); 76 | this._button.icon.classList.add('fas', this.options.button.icon); 77 | this._group.addControl(this._button); 78 | } 79 | 80 | showIcons(show) { 81 | const $viewer = $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer'); 82 | 83 | // remove previous... 84 | $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer label.markup').remove(); 85 | if (!show) return; 86 | 87 | // do we have anything to show? 88 | if (this._icons === undefined || this.icons === null) return; 89 | 90 | // do we have access to the instance tree? 91 | const tree = this.viewer.model.getInstanceTree(); 92 | if (tree === undefined) { console.log('Loading tree...'); return; } 93 | 94 | const onClick = (e) => { 95 | this.viewer.select($(e.currentTarget).data('id')); 96 | this.viewer.utilities.fitToView(); 97 | }; 98 | 99 | this._frags = {} 100 | for (var i = 0; i < this._icons.length; i++) { 101 | // we need to collect all the fragIds for a given dbId 102 | const icon = this._icons[i]; 103 | this._frags['dbId' + icon.dbId] = [] 104 | 105 | // create the label for the dbId 106 | const $label = $(` 107 | 110 | `); 111 | $label.css('display', this.viewer.isNodeVisible(icon.dbId) ? 'block' : 'none'); 112 | $label.on('click', this.options.onClick || onClick); 113 | $viewer.append($label); 114 | 115 | // now collect the fragIds 116 | const getChildren = (topParentId, dbId) => { 117 | if (tree.getChildCount(dbId) === 0) 118 | getFrags(topParentId, dbId); // get frags for this leaf child 119 | tree.enumNodeChildren(dbId, (childId) => { 120 | getChildren(topParentId, childId); 121 | }) 122 | } 123 | const getFrags = (topParentId, dbId) => { 124 | tree.enumNodeFragments(dbId, (fragId) => { 125 | this._frags['dbId' + topParentId].push(fragId); 126 | this.updateIcons(); // re-position for each fragId found 127 | }); 128 | } 129 | getChildren(icon.dbId, icon.dbId); 130 | } 131 | } 132 | 133 | getModifiedWorldBoundingBox(dbId) { 134 | var fragList = this.viewer.model.getFragmentList(); 135 | const nodebBox = new THREE.Box3() 136 | 137 | // for each fragId on the list, get the bounding box 138 | for (const fragId of this._frags['dbId' + dbId]) { 139 | const fragbBox = new THREE.Box3(); 140 | fragList.getWorldBounds(fragId, fragbBox); 141 | nodebBox.union(fragbBox); // create a unifed bounding box 142 | } 143 | 144 | return nodebBox 145 | } 146 | 147 | updateIcons() { 148 | for (const label of $('#' + this.viewer.clientContainer.id + ' div.adsk-viewing-viewer .update')) { 149 | const $label = $(label); 150 | const id = $label.data('id'); 151 | 152 | // get the center of the dbId (based on its fragIds bounding boxes) 153 | const pos = this.viewer.worldToClient(this.getModifiedWorldBoundingBox(id).center()); 154 | 155 | // position the label center to it 156 | $label.css('left', Math.floor(pos.x - $label[0].offsetWidth / 2) + 'px'); 157 | $label.css('top', Math.floor(pos.y - $label[0].offsetHeight / 2) + 'px'); 158 | $label.css('display', this.viewer.isNodeVisible(id) ? 'block' : 'none'); 159 | } 160 | } 161 | } 162 | 163 | Autodesk.Viewing.theExtensionManager.registerExtension('IconMarkupExtension', IconMarkupExtension); -------------------------------------------------------------------------------- /public/extensions/IconMarkupExtension/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/IconMarkupExtension/extension.gif -------------------------------------------------------------------------------- /public/extensions/NestedViewerExtension/README.md: -------------------------------------------------------------------------------- 1 | # NestedViewerExtension 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=NestedViewerExtension) 4 | 5 | APS Viewer extension showing viewables related to the currently loaded model in another instance of the viewer. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Usage 10 | 11 | - Load the extension in the viewer 12 | - Click the icon in the toolbar 13 | - New dialog will open, showing a dropdown list of all viewables available for the currently loaded model 14 | 15 | ## Setup 16 | 17 | Include the CSS & JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 18 | 19 | ```xml 20 | 21 | 22 | ``` 23 | 24 | After Viewer is ready, preferable inside `onDocumentLoadSuccess`, load the extension 25 | 26 | ```javascript 27 | viewer.loadExtension("NestedViewerExtension", { filter: ["2d", "3d"], crossSelection: true }) 28 | ``` 29 | 30 | ## Configuration 31 | 32 | The extension accepts an optional _options_ object with the following properties: 33 | - `filter` - array of allowed viewable "roles" ("2d", "3d", or both), by default: `["2d", "3d"]` 34 | - `crossSelection` - boolean flag for cross-selecting elements after they're selected in the main or the nested viewer, `false` by default 35 | 36 | ## How it works 37 | 38 | The extension uses another instance of [GuiViewer3D](https://forge.autodesk.com/en/docs/viewer/v7/reference/Viewing/GuiViewer3D/) 39 | and places it in a custom [DockingPanel](https://forge.autodesk.com/en/docs/viewer/v7/reference/UI/DockingPanel/). 40 | Whenever the "main" viewer loads a different model (observed via the `Autodesk.Viewing.MODEL_ROOT_LOADED_EVENT` event), 41 | the extension collects all viewables available in this model (using `doc.getRoot().search({ type: 'geometry' })`), 42 | and makes them available in the docking panel's dropdown. 43 | -------------------------------------------------------------------------------- /public/extensions/NestedViewerExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "NestedViewerExtension", 3 | "displayname": "Viewer in Viewer", 4 | "description": "Nested viewer where viewables related to the currently loaded model can be viewed.", 5 | "options": { 6 | "filter": ["2d", "3d"], 7 | "crossSelection": true 8 | }, 9 | "editoptions":"true", 10 | "viewerversion": "7.*", 11 | "loadonstartup": "false", 12 | "filestoload": { 13 | "cssfiles": ["main.css"], 14 | "jsfiles": ["main.js"] 15 | }, 16 | "gif": "extension.gif", 17 | "includeinlist": "true", 18 | "bloglink": "https://github.com/autodesk-forge/forge-extensions/tree/master/public/extensions/NestedViewerExtension/README.md" 19 | } 20 | -------------------------------------------------------------------------------- /public/extensions/NestedViewerExtension/contents/main.css: -------------------------------------------------------------------------------- 1 | #nestedViewerExtensionButton { 2 | background-image: url(https://img.icons8.com/color/64/000000/picture-in-picture.png); 3 | background-size: 24px; 4 | background-repeat: no-repeat; 5 | background-position: center; 6 | filter: grayscale(100%); 7 | } 8 | 9 | #nestedViewerExtensionButton.active { 10 | filter: none; 11 | } 12 | 13 | #nestedViewerExtensionDropdown { 14 | background: #eee; 15 | color: #333; 16 | } 17 | -------------------------------------------------------------------------------- /public/extensions/NestedViewerExtension/contents/main.js: -------------------------------------------------------------------------------- 1 | class NestedViewerExtension extends Autodesk.Viewing.Extension { 2 | constructor(viewer, options) { 3 | super(viewer, options); 4 | options = options || {}; 5 | this._filter = options.filter || ['2d', '3d']; 6 | this._crossSelection = !!options.crossSelection; 7 | this._group = null; 8 | this._button = null; 9 | this._panel = null; 10 | this._onModelLoaded = this.onModelLoaded.bind(this); 11 | this._onSelectionChanged = this.onSelectionChanged.bind(this); 12 | } 13 | 14 | load() { 15 | this.viewer.addEventListener(Autodesk.Viewing.MODEL_ROOT_LOADED_EVENT, this._onModelLoaded); 16 | if (this._crossSelection) { 17 | this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this._onSelectionChanged); 18 | } 19 | console.log('NestedViewerExtension has been loaded.'); 20 | return true; 21 | } 22 | 23 | unload() { 24 | this.viewer.removeEventListener(Autodesk.Viewing.MODEL_ROOT_LOADED_EVENT, this._onModelLoaded); 25 | if (this._crossSelection) { 26 | this.viewer.removeEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this._onSelectionChanged); 27 | } 28 | if (this._panel) { 29 | this._panel.uninitialize(); 30 | } 31 | // Clean our UI elements if we added any 32 | if (this._group) { 33 | this._group.removeControl(this._button); 34 | if (this._group.getNumberOfControls() === 0) { 35 | this.viewer.toolbar.removeControl(this._group); 36 | } 37 | } 38 | console.log('NestedViewerExtension has been unloaded.'); 39 | return true; 40 | } 41 | 42 | onModelLoaded() { 43 | if (this._panel) { 44 | this._panel.urn = this.viewer.model.getData().urn; 45 | } 46 | } 47 | 48 | onSelectionChanged() { 49 | if (this._panel) { 50 | // Avoid endless loop between main viewer and 51 | // the nested viewer calling each other's select() method 52 | let vs = this.viewer.getSelection(); 53 | let ps = this._panel._viewer.getSelection(); 54 | if (!this.isSelectionSame(vs, ps)) { 55 | this._panel.select(vs); 56 | } 57 | } 58 | } 59 | 60 | isSelectionSame(sel1, sel2) { 61 | if (sel1.length !== sel2.length) 62 | return false; 63 | 64 | for (let i = 0; i < sel1.length; i++) { 65 | if (sel1[i] !== sel2[i]) 66 | return false; 67 | } 68 | 69 | return true; 70 | } 71 | 72 | onToolbarCreated() { 73 | this._group = this.viewer.toolbar.getControl('nestedViewerExtensionToolbar'); 74 | if (!this._group) { 75 | this._group = new Autodesk.Viewing.UI.ControlGroup('nestedViewerExtensionToolbar'); 76 | this.viewer.toolbar.addControl(this._group); 77 | } 78 | this._button = new Autodesk.Viewing.UI.Button('nestedViewerExtensionButton'); 79 | this._button.onClick = (ev) => { 80 | if (!this._panel) { 81 | this._panel = new NestedViewerPanel(this.viewer, this._filter, this._crossSelection); 82 | this._panel.urn = this.viewer.model.getData().urn; 83 | } 84 | if (this._panel.isVisible()) { 85 | this._panel.setVisible(false); 86 | this._button.removeClass('active'); 87 | } else { 88 | this._panel.setVisible(true); 89 | this._button.addClass('active'); 90 | } 91 | }; 92 | this._button.setToolTip('Nested Viewer'); 93 | this._button.addClass('nestedViewerExtensionIcon'); 94 | this._group.addControl(this._button); 95 | } 96 | } 97 | 98 | class NestedViewerPanel extends Autodesk.Viewing.UI.DockingPanel { 99 | constructor(viewer, filter, crossSelection) { 100 | super(viewer.container, 'nested-viewer-panel', 'Nested Viewer'); 101 | this._urn = ''; 102 | this._parentViewer = viewer; 103 | this._filter = filter; 104 | this._crossSelection = crossSelection; 105 | } 106 | 107 | get urn() { 108 | return this._urn; 109 | } 110 | 111 | set urn(value) { 112 | if (this._urn !== value) { 113 | this._urn = value; 114 | this._updateDropdown(); 115 | } 116 | } 117 | 118 | initialize() { 119 | this.container.style.top = '5em'; 120 | this.container.style.right = '5em'; 121 | this.container.style.width = '500px'; 122 | this.container.style.height = '400px'; 123 | 124 | this.title = this.createTitleBar(this.titleLabel || this.container.id); 125 | this.container.appendChild(this.title); 126 | 127 | this._container = document.createElement('div'); 128 | this._container.style.position = 'absolute'; 129 | this._container.style.left = '0'; 130 | this._container.style.top = '50px'; 131 | this._container.style.width = '100%'; 132 | this._container.style.height = '330px'; // 400px - 50px (title bar) - 20px (footer) 133 | this.container.appendChild(this._container); 134 | 135 | this._overlay = document.createElement('div'); 136 | this._overlay.style.width = '100%'; 137 | this._overlay.style.height = '100%'; 138 | this._overlay.style.display = 'none'; 139 | this._overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)'; 140 | this._overlay.style.color = 'white'; 141 | this._overlay.style.zIndex = '101'; 142 | this._overlay.style.justifyContent = 'center'; 143 | this._overlay.style.alignItems = 'center'; 144 | this._container.appendChild(this._overlay); 145 | 146 | this._dropdown = document.createElement('select'); 147 | this._dropdown.style.position = 'absolute'; 148 | this._dropdown.style.left = '1em'; 149 | this._dropdown.style.top = '1em'; 150 | this._dropdown.style.setProperty('z-index', '100'); 151 | this._dropdown.setAttribute('id', 'nestedViewerExtensionDropdown'); 152 | this._dropdown.addEventListener('change', this._onDropdownChanged.bind(this)) 153 | this._dropdown.addEventListener('mousedown', function (ev) { ev.stopPropagation(); }); // prevent DockingPanel from kidnapping clicks on the dropdown 154 | this._container.appendChild(this._dropdown); 155 | 156 | this.initializeMoveHandlers(this.container); 157 | this._footer = this.createFooter(); 158 | this.footerInstance.resizeCallback = (width, height) => { 159 | this._container.style.height = `${height - 50 /* title bar */ - 20 /* footer */}px`; 160 | if (this._viewer) { 161 | this._viewer.resize(); 162 | } 163 | }; 164 | this.container.appendChild(this._footer); 165 | } 166 | 167 | setVisible(show) { 168 | super.setVisible(show); 169 | if (show && !this._viewer) { 170 | this._viewer = new Autodesk.Viewing.GuiViewer3D(this._container); 171 | this._viewer.start(); 172 | this._onDropdownChanged(); 173 | if (this._crossSelection) { 174 | this._viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, () => { 175 | this._parentViewer.select(this._viewer.getSelection()); 176 | }); 177 | } 178 | } 179 | } 180 | 181 | select(dbids) { 182 | if (this._viewer) { 183 | this._viewer.select(dbids); 184 | } 185 | } 186 | 187 | _updateDropdown() { 188 | const onDocumentLoadSuccess = (doc) => { 189 | this._manifest = doc; 190 | const filterGeom = (geom) => this._filter.indexOf(geom.data.role) !== -1; 191 | const geometries = doc.getRoot().search({ type: 'geometry' }).filter(filterGeom); 192 | if (geometries.length > 0) { 193 | this._overlay.style.display = 'none'; 194 | this._dropdown.innerHTML = geometries.map(function (geom) { 195 | return ``; 196 | }).join('\n'); 197 | } else { 198 | this._overlay.style.display = 'flex'; 199 | this._overlay.innerText = 'No viewables found'; 200 | this._dropdown.innerHTML = ''; 201 | } 202 | this._onDropdownChanged(); 203 | }; 204 | const onDocumentLoadFailure = () => { 205 | console.error('Could not load document.'); 206 | }; 207 | this._dropdown.innerHTML = ''; 208 | Autodesk.Viewing.Document.load('urn:' + this._urn, onDocumentLoadSuccess, onDocumentLoadFailure); 209 | } 210 | 211 | _onDropdownChanged() { 212 | const guid = this._dropdown.value; 213 | if (guid) { 214 | this._viewer.loadDocumentNode(this._manifest, this._manifest.getRoot().findByGuid(guid)); 215 | } 216 | } 217 | } 218 | 219 | Autodesk.Viewing.theExtensionManager.registerExtension('NestedViewerExtension', NestedViewerExtension); 220 | -------------------------------------------------------------------------------- /public/extensions/NestedViewerExtension/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/NestedViewerExtension/extension.gif -------------------------------------------------------------------------------- /public/extensions/PhasingExtension/README.md: -------------------------------------------------------------------------------- 1 | # Phasing Extension 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=PhasingExtension) 4 | 5 | APS Viewer extension to add Gantt chart connected with the model. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Usage 10 | 11 | - Load the extension in the viewer 12 | - Import one CSV with the configuration 13 | - New docking panel opens showing Gantt chart with the tasks connected to elements of the scene 14 | 15 | ## Setup 16 | 17 | Include the JS file on your page. 18 | 19 | Everything is inside `main.js` file (extension and panel). 20 | in panel class there's also the reference for the external libraries used by this extension: 21 | 22 | [Sweetalert2](https://sweetalert2.github.io) 23 | 24 | [Frappe Gantt](https://frappe.io/gantt) 25 | 26 | ## How it works 27 | 28 | This extension build the tasks based on the csv imported. 29 | The Gantt chart generated is connected with the model in a way that by double clicking in a task, the correlated elements get isolated. 30 | There are also options to control UI and override elements colors based on the status of each task. 31 | 32 | To help you quickly take advantage of it, you can find a [sample.csv](./assets/sample.csv) compatible with the Office.rvt model available at [extensions sample](https://aps-extensions.autodesk.io) 33 | -------------------------------------------------------------------------------- /public/extensions/PhasingExtension/assets/sample.csv: -------------------------------------------------------------------------------- 1 | ID,NAME,START,END,PROGRESS,Type Name,DEPENDENCIES 2 | 123,Basement Floor,2022-12-10,2023-01-04,100,Generic - 12", 3 | 124,Wood Floor,2023-01-12,2023-01-22,100,Wood Floor - 12",123 4 | 125,Concrete Walls,2023-01-22,2023-02-22,100,Cast Concrete Wall 12",124 5 | 126,Generic Walls - 300,2023-02-25,2023-03-13,100,Generic - 8",125 6 | 127,Roof,2023-03-22,2023-04-05,50,Wood Frame - 10",126 -------------------------------------------------------------------------------- /public/extensions/PhasingExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PhasingExtension", 3 | "displayname": "Phasing Extension", 4 | "description": "APS extension to generate Gantt chart connected with objects from the model", 5 | "options": {}, 6 | "viewerversion": "7.*", 7 | "loadonstartup": "false", 8 | "filestoload": { 9 | "cssfiles": [], 10 | "jsfiles": [ 11 | "main.js" 12 | ] 13 | }, 14 | "bloglink": "TBD", 15 | "includeinlist": "true", 16 | "gif": "extension.gif" 17 | } -------------------------------------------------------------------------------- /public/extensions/PhasingExtension/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/PhasingExtension/extension.gif -------------------------------------------------------------------------------- /public/extensions/PotreeExtension/README.md: -------------------------------------------------------------------------------- 1 | # PotreeExtension 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=PotreeExtension) 4 | 5 | ![thumbnail](extension.gif) 6 | 7 | APS Viewer extension for loading and rendering Potree models. 8 | 9 | The extension uses [potree-core](https://github.com/tentone/potree-core) library that has been back-ported to three.js version 71 (the one used by the APS Viewer). The experimental back-ported version of the library can be found in https://github.com/petrbroz/potree-core/tree/experiment/three71. 10 | 11 | Apart from the library itself (available as either _potree.js_, _potree.min.js_, or _potree.module.js_), this folder also includes various decoders in the _workers_ subfolder, and a sample dataset from https://github.com/tentone/potree-core. 12 | 13 | ## Usage 14 | 15 | - copy this _contents_ folder to your APS application (the _data_ subfolder is not necessary) 16 | - include the Potree library and this extension in your HTML, after the APS Viewer script: 17 | 18 | ```html 19 | 20 | 21 | 22 | ``` 23 | 24 | - include `PotreeExtension` when initializing the viewer, for example: 25 | 26 | ```js 27 | Autodesk.Viewing.Initializer(options, () => { 28 | const config = { 29 | extensions: ['PotreeExtension'] 30 | }; 31 | viewer = new Autodesk.Viewing.GuiViewer3D(container, config); 32 | }); 33 | ``` 34 | 35 | - in your JavaScript code, use the extension's `loadPointCloud` method, for example: 36 | 37 | ```js 38 | const potreeExtension = viewer.getExtension('PotreeExtension'); 39 | let position = new THREE.Vector3(0, 0, -25); 40 | let scale = new THREE.Vector3(5, 5, 5); 41 | const pointcloud = await potreeExtension.loadPointCloud('my-pointcloud', url, position, scale); 42 | const bbox = pointcloud.boundingBox.clone().expandByVector(scale); 43 | viewer.navigation.fitBounds(false, bbox); 44 | ``` 45 | -------------------------------------------------------------------------------- /public/extensions/PotreeExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PotreeExtension", 3 | "displayname": "Potree Point Clouds", 4 | "description": "Forge Viewer extension for loading and rendering Potree point clouds.", 5 | "options": { 6 | "url": "https://aps-extensions-sample-data.s3.us-west-2.amazonaws.com/PotreeExtension/lion_takanawa/cloud.js", 7 | "position": [0.0, 0.0, 0.0], 8 | "scale": [5.0, 5.0, 5.0] 9 | }, 10 | "editoptions": "true", 11 | "viewerversion": "7.*", 12 | "loadonstartup": "false", 13 | "filestoload": { 14 | "cssfiles": [], 15 | "jsfiles": [ 16 | "potree.js", 17 | "PotreeExtension.js" 18 | ] 19 | }, 20 | "gif": "extension.gif", 21 | "includeinlist": "true", 22 | "bloglink": "" 23 | } -------------------------------------------------------------------------------- /public/extensions/PotreeExtension/contents/PotreeExtension.js: -------------------------------------------------------------------------------- 1 | class PotreeExtension extends Autodesk.Viewing.Extension { 2 | constructor(viewer, options) { 3 | super(viewer, options); 4 | this._group = null; 5 | this._pointclouds = new Map(); 6 | this._timer = null; 7 | if (options && options.url) { 8 | this._initConfig = { 9 | url: options.url, 10 | position: null, 11 | scale: null 12 | }; 13 | if (options.position && Array.isArray(options.position)) { 14 | this._initConfig.position = new THREE.Vector3(options.position[0], options.position[1], options.position[2]); 15 | } 16 | if (options.scale && Array.isArray(options.scale)) { 17 | this._initConfig.scale = new THREE.Vector3(options.scale[0], options.scale[1], options.scale[2]); 18 | } 19 | } 20 | } 21 | 22 | load() { 23 | // Setup the overlay scene 24 | this._group = new THREE.Group(); 25 | if (!this.viewer.overlays.hasScene('potree-scene')) { 26 | this.viewer.overlays.addScene('potree-scene'); 27 | } 28 | this.viewer.overlays.addMesh(this._group, 'potree-scene'); 29 | 30 | // Update point clouds on every camera change... 31 | this.viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, this.updatePointClouds.bind(this)); 32 | // ... and also in hard-coded intervals 33 | this._timer = setInterval(this.updatePointClouds.bind(this), 500); 34 | 35 | console.log('PotreeExtension loaded.'); 36 | 37 | if (this._initConfig) { 38 | this.loadPointCloud('my-pointcloud', this._initConfig.url, this._initConfig.position, this._initConfig.scale); 39 | } 40 | 41 | return true; 42 | } 43 | 44 | unload() { 45 | this.viewer.overlays.removeScene('potree-scene'); 46 | console.log('PotreeExtension unloaded.'); 47 | return true; 48 | } 49 | 50 | /** 51 | * Adds potree model into the scene and starts streaming its data. 52 | * @param {string} name Unique name of the model. 53 | * @param {string} url URL of the potree model main file (typically "cloud.js"). 54 | * @param {THREE.Vector3} [position] Optional position to apply to the newly loaded pointcloud. 55 | * @param {THREE.Vector3} [scale] Optional scale to apply to the newly loaded pointcloud. 56 | * @returns {Promise} Potree point cloud object. 57 | * @example 58 | * const ext = viewer.getExtension('PotreeExtension'); 59 | * const pointcloud = await ext.loadPointCloud('lion', '/potree/data/lion_takanawa/cloud.js', new THREE.Vector3(0, 0, -25), new THREE.Vector3(5, 5, 5)); 60 | * console.log(pointcloud); 61 | */ 62 | loadPointCloud(name, url, position, scale) { 63 | if (this._pointclouds.has(name)) { 64 | return Promise.resolve(this._pointclouds.get(name)); 65 | } 66 | return new Promise((resolve, reject) => { 67 | Potree.loadPointCloud(url, name, (ev) => { 68 | const { pointcloud } = ev; 69 | const { material } = pointcloud; 70 | if (position) { 71 | pointcloud.position.copy(position); 72 | } 73 | if (scale) { 74 | pointcloud.scale.copy(scale); 75 | } 76 | material.size = 2; 77 | material.pointColorType = Potree.PointColorType.RGB; //RGB | DEPTH | HEIGHT | POINT_INDEX | LOD | CLASSIFICATION 78 | material.pointSizeType = Potree.PointSizeType.ADAPTIVE; //ADAPTIVE | FIXED 79 | material.shape = Potree.PointShape.CIRCLE; //CIRCLE | SQUARE 80 | this._group.add(pointcloud); 81 | this._pointclouds.set(name, pointcloud); 82 | this.updatePointClouds(); 83 | resolve(pointcloud); 84 | }); 85 | }); 86 | } 87 | 88 | updatePointClouds() { 89 | const pointclouds = Array.from(this._pointclouds.values()); 90 | if (pointclouds) { 91 | const camera = this.viewer.impl.camera; //.perspectiveCamera; 92 | const renderer = this.viewer.impl.glrenderer(); 93 | Potree.updatePointClouds(pointclouds, camera, renderer); 94 | } 95 | } 96 | } 97 | 98 | Autodesk.Viewing.theExtensionManager.registerExtension('PotreeExtension', PotreeExtension); 99 | -------------------------------------------------------------------------------- /public/extensions/PotreeExtension/contents/workers/BinaryDecoderWorker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function CustomView(buffer) 4 | { 5 | this.buffer = buffer; 6 | this.u8 = new Uint8Array(buffer); 7 | 8 | var tmp = new ArrayBuffer(4); 9 | var tmpf = new Float32Array(tmp); 10 | var tmpu8 = new Uint8Array(tmp); 11 | 12 | this.getUint32 = function(i) 13 | { 14 | return (this.u8[i + 3] << 24) | (this.u8[i + 2] << 16) | (this.u8[i + 1] << 8) | this.u8[i]; 15 | }; 16 | 17 | this.getUint16 = function(i) 18 | { 19 | return (this.u8[i + 1] << 8) | this.u8[i]; 20 | }; 21 | 22 | this.getFloat32 = function(i) 23 | { 24 | tmpu8[0] = this.u8[i + 0]; 25 | tmpu8[1] = this.u8[i + 1]; 26 | tmpu8[2] = this.u8[i + 2]; 27 | tmpu8[3] = this.u8[i + 3]; 28 | 29 | return tmpf[0]; 30 | }; 31 | 32 | this.getUint8 = function(i) 33 | { 34 | return this.u8[i]; 35 | }; 36 | } 37 | 38 | onmessage = function(event) 39 | { 40 | try 41 | { 42 | var buffer = event.data.buffer; 43 | var pointAttributes = event.data.pointAttributes; 44 | var numPoints = buffer.byteLength / pointAttributes.byteSize; 45 | var cv = new CustomView(buffer); 46 | var version = new Version(event.data.version); 47 | var nodeOffset = event.data.offset; 48 | var scale = event.data.scale; 49 | var spacing = event.data.spacing; 50 | var hasChildren = event.data.hasChildren; 51 | var name = event.data.name; 52 | 53 | var tightBoxMin = [ Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY ]; 54 | var tightBoxMax = [ Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY ]; 55 | var mean = [0, 0, 0]; 56 | 57 | var attributeBuffers = {}; 58 | var inOffset = 0; 59 | 60 | for(var pointAttribute of pointAttributes.attributes) 61 | { 62 | if(pointAttribute.name === PointAttribute.POSITION_CARTESIAN.name) 63 | { 64 | var buff = new ArrayBuffer(numPoints * 4 * 3); 65 | var positions = new Float32Array(buff); 66 | 67 | for(var j = 0; j < numPoints; j++) 68 | { 69 | var x, y, z; 70 | 71 | if(version.newerThan("1.3")) 72 | { 73 | x = (cv.getUint32(inOffset + j * pointAttributes.byteSize + 0, true) * scale); 74 | y = (cv.getUint32(inOffset + j * pointAttributes.byteSize + 4, true) * scale); 75 | z = (cv.getUint32(inOffset + j * pointAttributes.byteSize + 8, true) * scale); 76 | } 77 | else 78 | { 79 | x = cv.getFloat32(j * pointAttributes.byteSize + 0, true) + nodeOffset[0]; 80 | y = cv.getFloat32(j * pointAttributes.byteSize + 4, true) + nodeOffset[1]; 81 | z = cv.getFloat32(j * pointAttributes.byteSize + 8, true) + nodeOffset[2]; 82 | } 83 | 84 | positions[3 * j + 0] = x; 85 | positions[3 * j + 1] = y; 86 | positions[3 * j + 2] = z; 87 | 88 | mean[0] += x / numPoints; 89 | mean[1] += y / numPoints; 90 | mean[2] += z / numPoints; 91 | 92 | tightBoxMin[0] = Math.min(tightBoxMin[0], x); 93 | tightBoxMin[1] = Math.min(tightBoxMin[1], y); 94 | tightBoxMin[2] = Math.min(tightBoxMin[2], z); 95 | 96 | tightBoxMax[0] = Math.max(tightBoxMax[0], x); 97 | tightBoxMax[1] = Math.max(tightBoxMax[1], y); 98 | tightBoxMax[2] = Math.max(tightBoxMax[2], z); 99 | } 100 | 101 | attributeBuffers[pointAttribute.name] = {buffer: buff, attribute: pointAttribute}; 102 | } else if(pointAttribute.name === PointAttribute.COLOR_PACKED.name) 103 | { 104 | var buff = new ArrayBuffer(numPoints * 4); 105 | var colors = new Uint8Array(buff); 106 | 107 | for(var j = 0; j < numPoints; j++) 108 | { 109 | colors[4 * j + 0] = cv.getUint8(inOffset + j * pointAttributes.byteSize + 0); 110 | colors[4 * j + 1] = cv.getUint8(inOffset + j * pointAttributes.byteSize + 1); 111 | colors[4 * j + 2] = cv.getUint8(inOffset + j * pointAttributes.byteSize + 2); 112 | } 113 | 114 | attributeBuffers[pointAttribute.name] = {buffer: buff, attribute: pointAttribute}; 115 | } 116 | else if(pointAttribute.name === PointAttribute.INTENSITY.name) 117 | { 118 | var buff = new ArrayBuffer(numPoints * 4); 119 | var intensities = new Float32Array(buff); 120 | 121 | for(var j = 0; j < numPoints; j++) 122 | { 123 | var intensity = cv.getUint16(inOffset + j * pointAttributes.byteSize, true); 124 | intensities[j] = intensity; 125 | } 126 | 127 | attributeBuffers[pointAttribute.name] = {buffer: buff, attribute: pointAttribute}; 128 | } 129 | else if(pointAttribute.name === PointAttribute.CLASSIFICATION.name) 130 | { 131 | var buff = new ArrayBuffer(numPoints); 132 | var classifications = new Uint8Array(buff); 133 | 134 | for(var j = 0; j < numPoints; j++) 135 | { 136 | var classification = cv.getUint8(inOffset + j * pointAttributes.byteSize); 137 | classifications[j] = classification; 138 | } 139 | 140 | attributeBuffers[pointAttribute.name] = {buffer: buff, attribute: pointAttribute}; 141 | } 142 | else if(pointAttribute.name === PointAttribute.NORMAL_SPHEREMAPPED.name) 143 | { 144 | var buff = new ArrayBuffer(numPoints * 4 * 3); 145 | var normals = new Float32Array(buff); 146 | 147 | for(var j = 0; j < numPoints; j++) 148 | { 149 | var bx = cv.getUint8(inOffset + j * pointAttributes.byteSize + 0); 150 | var by = cv.getUint8(inOffset + j * pointAttributes.byteSize + 1); 151 | 152 | var ex = bx / 255; 153 | var ey = by / 255; 154 | 155 | var nx = ex * 2 - 1; 156 | var ny = ey * 2 - 1; 157 | var nz = 1; 158 | var nw = -1; 159 | 160 | var l = (nx * (-nx)) + (ny * (-ny)) + (nz * (-nw)); 161 | nz = l; 162 | nx = nx * Math.sqrt(l); 163 | ny = ny * Math.sqrt(l); 164 | 165 | nx = nx * 2; 166 | ny = ny * 2; 167 | nz = nz * 2 - 1; 168 | 169 | normals[3 * j + 0] = nx; 170 | normals[3 * j + 1] = ny; 171 | normals[3 * j + 2] = nz; 172 | } 173 | 174 | attributeBuffers[pointAttribute.name] = {buffer: buff, attribute: pointAttribute}; 175 | } 176 | else if(pointAttribute.name === PointAttribute.NORMAL_OCT16.name) 177 | { 178 | var buff = new ArrayBuffer(numPoints * 4 * 3); 179 | var normals = new Float32Array(buff); 180 | 181 | for(var j = 0; j < numPoints; j++) { 182 | var bx = cv.getUint8(inOffset + j * pointAttributes.byteSize + 0); 183 | var by = cv.getUint8(inOffset + j * pointAttributes.byteSize + 1); 184 | 185 | var u = (bx / 255) * 2 - 1; 186 | var v = (by / 255) * 2 - 1; 187 | 188 | var z = 1 - Math.abs(u) - Math.abs(v); 189 | 190 | var x = 0; 191 | var y = 0; 192 | if(z >= 0) { 193 | x = u; 194 | y = v; 195 | } else { 196 | x = -(v / Math.sign(v) - 1) / Math.sign(u); 197 | y = -(u / Math.sign(u) - 1) / Math.sign(v); 198 | } 199 | 200 | var length = Math.sqrt(x * x + y * y + z * z); 201 | x = x / length; 202 | y = y / length; 203 | z = z / length; 204 | 205 | normals[3 * j + 0] = x; 206 | normals[3 * j + 1] = y; 207 | normals[3 * j + 2] = z; 208 | } 209 | 210 | attributeBuffers[pointAttribute.name] = {buffer: buff, attribute: pointAttribute}; 211 | } else if(pointAttribute.name === PointAttribute.NORMAL.name) { 212 | var buff = new ArrayBuffer(numPoints * 4 * 3); 213 | var normals = new Float32Array(buff); 214 | 215 | for(var j = 0; j < numPoints; j++) { 216 | var x = cv.getFloat32(inOffset + j * pointAttributes.byteSize + 0, true); 217 | var y = cv.getFloat32(inOffset + j * pointAttributes.byteSize + 4, true); 218 | var z = cv.getFloat32(inOffset + j * pointAttributes.byteSize + 8, true); 219 | 220 | normals[3 * j + 0] = x; 221 | normals[3 * j + 1] = y; 222 | normals[3 * j + 2] = z; 223 | } 224 | 225 | attributeBuffers[pointAttribute.name] = {buffer: buff, attribute: pointAttribute}; 226 | } 227 | 228 | inOffset += pointAttribute.byteSize; 229 | } 230 | 231 | //add indices 232 | var buff = new ArrayBuffer(numPoints * 4); 233 | var indices = new Uint32Array(buff); 234 | 235 | for(var i = 0; i < numPoints; i++) 236 | { 237 | indices[i] = i; 238 | } 239 | 240 | attributeBuffers[PointAttribute.INDICES.name] = {buffer: buff, attribute: PointAttribute.INDICES}; 241 | 242 | var message = 243 | { 244 | buffer: buffer, 245 | mean: mean, 246 | attributeBuffers: attributeBuffers, 247 | tightBoundingBox: {min: tightBoxMin, max: tightBoxMax}, 248 | }; 249 | 250 | var transferables = []; 251 | for(var property in message.attributeBuffers) 252 | { 253 | transferables.push(message.attributeBuffers[property].buffer); 254 | } 255 | transferables.push(buffer); 256 | 257 | postMessage(message, transferables); 258 | } 259 | catch(e) 260 | { 261 | postMessage({error: "Exeption thrown during execution."}); 262 | } 263 | }; 264 | 265 | 266 | function Version(version) 267 | { 268 | this.version = version; 269 | var vmLength = (version.indexOf(".") === -1) ? version.length : version.indexOf("."); 270 | this.versionMajor = parseInt(version.substr(0, vmLength)); 271 | this.versionMinor = parseInt(version.substr(vmLength + 1)); 272 | 273 | if(this.versionMinor.length === 0) 274 | { 275 | this.versionMinor = 0; 276 | } 277 | }; 278 | 279 | Version.prototype.newerThan = function(version) 280 | { 281 | var v = new Version(version); 282 | 283 | if((this.versionMajor > v.versionMajor) || (this.versionMajor === v.versionMajor && this.versionMinor > v.versionMinor)) 284 | { 285 | return true; 286 | } 287 | 288 | return false; 289 | }; 290 | 291 | var PointAttributeNames = 292 | { 293 | POSITION_CARTESIAN: 0, //float x, y, z, 294 | COLOR_PACKED: 1, //byte r, g, b, a, I: [0,1] 295 | COLOR_FLOATS_1: 2, //float r, g, b, I: [0,1] 296 | COLOR_FLOATS_255: 3, //float r, g, b, I: [0,255] 297 | NORMAL_FLOATS: 4, //float x, y, z, 298 | FILLER: 5, 299 | INTENSITY: 6, 300 | CLASSIFICATION: 7, 301 | NORMAL_SPHEREMAPPED: 8, 302 | NORMAL_OCT16: 9, 303 | NORMAL: 10, 304 | RETURN_NUMBER: 11, 305 | NUMBER_OF_RETURNS: 12, 306 | SOURCE_ID: 13, 307 | INDICES: 14, 308 | SPACING: 15 309 | }; 310 | 311 | /** 312 | * Some types of possible point attribute data formats 313 | * 314 | * @class 315 | */ 316 | var PointAttributeTypes = 317 | { 318 | DATA_TYPE_DOUBLE: {ordinal: 0, size: 8}, 319 | DATA_TYPE_FLOAT: {ordinal: 1, size: 4}, 320 | DATA_TYPE_INT8: {ordinal: 2, size: 1}, 321 | DATA_TYPE_UINT8: {ordinal: 3, size: 1}, 322 | DATA_TYPE_INT16: {ordinal: 4, size: 2}, 323 | DATA_TYPE_UINT16: {ordinal: 5, size: 2}, 324 | DATA_TYPE_INT32: {ordinal: 6, size: 4}, 325 | DATA_TYPE_UINT32: {ordinal: 7, size: 4}, 326 | DATA_TYPE_INT64: {ordinal: 8, size: 8}, 327 | DATA_TYPE_UINT64: {ordinal: 9, size: 8} 328 | }; 329 | 330 | var i = 0; 331 | for(var obj in PointAttributeTypes) 332 | { 333 | PointAttributeTypes[i] = PointAttributeTypes[obj]; 334 | i++; 335 | } 336 | 337 | function PointAttribute(name, type, numElements) 338 | { 339 | this.name = name; 340 | this.type = type; 341 | this.numElements = numElements; 342 | this.byteSize = this.numElements * this.type.size; 343 | }; 344 | 345 | PointAttribute.POSITION_CARTESIAN = new PointAttribute(PointAttributeNames.POSITION_CARTESIAN, PointAttributeTypes.DATA_TYPE_FLOAT, 3); 346 | PointAttribute.RGBA_PACKED = new PointAttribute(PointAttributeNames.COLOR_PACKED, PointAttributeTypes.DATA_TYPE_INT8, 4); 347 | PointAttribute.COLOR_PACKED = PointAttribute.RGBA_PACKED; 348 | PointAttribute.RGB_PACKED = new PointAttribute(PointAttributeNames.COLOR_PACKED, PointAttributeTypes.DATA_TYPE_INT8, 3); 349 | PointAttribute.NORMAL_FLOATS = new PointAttribute(PointAttributeNames.NORMAL_FLOATS, PointAttributeTypes.DATA_TYPE_FLOAT, 3); 350 | PointAttribute.FILLER_1B = new PointAttribute(PointAttributeNames.FILLER, PointAttributeTypes.DATA_TYPE_UINT8, 1); 351 | PointAttribute.INTENSITY = new PointAttribute(PointAttributeNames.INTENSITY, PointAttributeTypes.DATA_TYPE_UINT16, 1); 352 | PointAttribute.CLASSIFICATION = new PointAttribute(PointAttributeNames.CLASSIFICATION, PointAttributeTypes.DATA_TYPE_UINT8, 1); 353 | PointAttribute.NORMAL_SPHEREMAPPED = new PointAttribute(PointAttributeNames.NORMAL_SPHEREMAPPED, PointAttributeTypes.DATA_TYPE_UINT8, 2); 354 | PointAttribute.NORMAL_OCT16 = new PointAttribute(PointAttributeNames.NORMAL_OCT16, PointAttributeTypes.DATA_TYPE_UINT8, 2); 355 | PointAttribute.NORMAL = new PointAttribute(PointAttributeNames.NORMAL, PointAttributeTypes.DATA_TYPE_FLOAT, 3); 356 | PointAttribute.RETURN_NUMBER = new PointAttribute(PointAttributeNames.RETURN_NUMBER, PointAttributeTypes.DATA_TYPE_UINT8, 1); 357 | PointAttribute.NUMBER_OF_RETURNS = new PointAttribute(PointAttributeNames.NUMBER_OF_RETURNS, PointAttributeTypes.DATA_TYPE_UINT8, 1); 358 | PointAttribute.SOURCE_ID = new PointAttribute(PointAttributeNames.SOURCE_ID, PointAttributeTypes.DATA_TYPE_UINT8, 1); 359 | PointAttribute.INDICES = new PointAttribute(PointAttributeNames.INDICES, PointAttributeTypes.DATA_TYPE_UINT32, 1); 360 | PointAttribute.SPACING = new PointAttribute(PointAttributeNames.SPACING, PointAttributeTypes.DATA_TYPE_FLOAT, 1); 361 | 362 | function PointAttributes(pointAttributes) 363 | { 364 | this.attributes = []; 365 | this.byteSize = 0; 366 | this.size = 0; 367 | 368 | if(pointAttributes != null) 369 | { 370 | for(var i = 0; i < pointAttributes.length; i++) 371 | { 372 | var pointAttributeName = pointAttributes[i]; 373 | var pointAttribute = PointAttribute[pointAttributeName]; 374 | this.attributes.push(pointAttribute); 375 | this.byteSize += pointAttribute.byteSize; 376 | this.size++; 377 | } 378 | } 379 | }; 380 | 381 | PointAttributes.prototype.add = function(pointAttribute) 382 | { 383 | this.attributes.push(pointAttribute); 384 | this.byteSize += pointAttribute.byteSize; 385 | this.size++; 386 | }; 387 | 388 | PointAttributes.prototype.hasColors = function() 389 | { 390 | for(var name in this.attributes) 391 | { 392 | var pointAttribute = this.attributes[name]; 393 | if(pointAttribute.name === PointAttributeNames.COLOR_PACKED) 394 | { 395 | return true; 396 | } 397 | } 398 | 399 | return false; 400 | }; 401 | 402 | PointAttributes.prototype.hasNormals = function() 403 | { 404 | for(var name in this.attributes) 405 | { 406 | var pointAttribute = this.attributes[name]; 407 | if( 408 | pointAttribute === PointAttribute.NORMAL_SPHEREMAPPED || 409 | pointAttribute === PointAttribute.NORMAL_FLOATS || 410 | pointAttribute === PointAttribute.NORMAL || 411 | pointAttribute === PointAttribute.NORMAL_OCT16) { 412 | return true; 413 | } 414 | } 415 | 416 | return false; 417 | }; 418 | -------------------------------------------------------------------------------- /public/extensions/PotreeExtension/contents/workers/DEMWorker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | onmessage = function(event) 4 | { 5 | var boundingBox = event.data.boundingBox; 6 | var position = new Float32Array(event.data.position); 7 | var numPoints = position.length / 3; 8 | var width = 64, height = 64; 9 | 10 | var boxSize = 11 | { 12 | x: boundingBox.max[0] - boundingBox.min[0], 13 | y: boundingBox.max[1] - boundingBox.min[1], 14 | z: boundingBox.max[2] - boundingBox.min[2] 15 | }; 16 | 17 | var dem = new Float32Array(width * height); 18 | dem.fill(-Infinity); 19 | 20 | for(var i = 0; i < numPoints; i++) 21 | { 22 | var x = position[3 * i + 0]; 23 | var y = position[3 * i + 1]; 24 | var z = position[3 * i + 2]; 25 | 26 | var dx = x / boxSize.x; 27 | var dy = y / boxSize.y; 28 | 29 | var ix = parseInt(Math.min(width * dx, width - 1)); 30 | var iy = parseInt(Math.min(height * dy, height - 1)); 31 | 32 | var index = ix + width * iy; 33 | dem[index] = z; 34 | } 35 | 36 | var message = 37 | { 38 | dem: 39 | { 40 | width: width, 41 | height: height, 42 | data: dem.buffer 43 | } 44 | }; 45 | 46 | postMessage(message, [message.dem.data]); 47 | }; 48 | -------------------------------------------------------------------------------- /public/extensions/PotreeExtension/contents/workers/EptBinaryDecoderWorker.js: -------------------------------------------------------------------------------- 1 | Potree = { }; 2 | 3 | onmessage = function(event) { 4 | let buffer = event.data.buffer; 5 | let view = new DataView(buffer); 6 | let schema = event.data.schema; 7 | let scale = event.data.scale; 8 | let offset = event.data.offset; 9 | let mins = event.data.mins; 10 | 11 | let dimensions = schema.reduce((p, c) => { 12 | p[c.name] = c; 13 | return p; 14 | }, { }); 15 | 16 | let dimOffset = (name) => { 17 | let offset = 0; 18 | for (var i = 0; i < schema.length; ++i) { 19 | if (schema[i].name == name) return offset; 20 | offset += schema[i].size; 21 | } 22 | return undefined; 23 | }; 24 | 25 | let getExtractor = (name) => { 26 | let offset = dimOffset(name); 27 | let type = dimensions[name].type; 28 | let size = dimensions[name].size; 29 | 30 | if (type == "signed") switch (size) { 31 | case 1: return (p) => view.getInt8(p + offset); 32 | case 2: return (p) => view.getInt16(p + offset, true); 33 | case 4: return (p) => view.getInt32(p + offset, true); 34 | case 8: return (p) => view.getInt64(p + offset, true); 35 | } 36 | if (type == "unsigned") switch (size) { 37 | case 1: return (p) => view.getUint8(p + offset); 38 | case 2: return (p) => view.getUint16(p + offset, true); 39 | case 4: return (p) => view.getUint32(p + offset, true); 40 | case 8: return (p) => view.getUint64(p + offset, true); 41 | } 42 | if (type == "float") switch (size) { 43 | case 4: return (p) => view.getFloat32(p + offset, true); 44 | case 8: return (p) => view.getFloat64(p + offset, true); 45 | } 46 | 47 | let str = JSON.stringify(dimensions[name]); 48 | throw new Error(`Invalid dimension specification for ${name}: ${str}`); 49 | }; 50 | 51 | let pointSize = schema.reduce((p, c) => p + c.size, 0); 52 | let numPoints = buffer.byteLength / pointSize; 53 | 54 | let xyzBuffer, rgbBuffer, intensityBuffer, classificationBuffer, 55 | returnNumberBuffer, numberOfReturnsBuffer, pointSourceIdBuffer; 56 | let xyz, rgb, intensity, classification, returnNumber, numberOfReturns, 57 | pointSourceId; 58 | let xyzExtractor, rgbExtractor, intensityExtractor, classificationExtractor, 59 | returnNumberExtractor, numberOfReturnsExtractor, pointSourceIdExtractor; 60 | let twoByteColor = false; 61 | 62 | if (dimensions["X"] && dimensions["Y"] && dimensions["Z"]) { 63 | xyzBuffer = new ArrayBuffer(numPoints * 4 * 3); 64 | xyz = new Float32Array(xyzBuffer); 65 | xyzExtractor = [ 66 | getExtractor("X"), 67 | getExtractor("Y"), 68 | getExtractor("Z") 69 | ]; 70 | } 71 | 72 | if (dimensions["Red"] && dimensions["Green"] && dimensions["Blue"]) { 73 | rgbBuffer = new ArrayBuffer(numPoints * 4); 74 | rgb = new Uint8Array(rgbBuffer); 75 | rgbExtractor = [ 76 | getExtractor("Red"), 77 | getExtractor("Green"), 78 | getExtractor("Blue") 79 | ]; 80 | 81 | let r, g, b, pos; 82 | for (let i = 0; i < numPoints && !twoByteColor; ++i) { 83 | pos = i * pointSize; 84 | r = rgbExtractor[0](pos); 85 | g = rgbExtractor[1](pos); 86 | b = rgbExtractor[2](pos); 87 | if (r > 255 || g > 255 || b > 255) twoByteColor = true; 88 | } 89 | } 90 | 91 | if (dimensions["Intensity"]) { 92 | intensityBuffer = new ArrayBuffer(numPoints * 4); 93 | intensity = new Float32Array(intensityBuffer); 94 | intensityExtractor = getExtractor("Intensity"); 95 | } 96 | 97 | if (dimensions["Classification"]) { 98 | classificationBuffer = new ArrayBuffer(numPoints); 99 | classification = new Uint8Array(classificationBuffer); 100 | classificationExtractor = getExtractor("Classification"); 101 | } 102 | 103 | if (dimensions["ReturnNumber"]) { 104 | returnNumberBuffer = new ArrayBuffer(numPoints); 105 | returnNumber = new Uint8Array(returnNumberBuffer); 106 | returnNumberExtractor = getExtractor("ReturnNumber"); 107 | } 108 | 109 | if (dimensions["NumberOfReturns"]) { 110 | numberOfReturnsBuffer = new ArrayBuffer(numPoints); 111 | numberOfReturns = new Uint8Array(numberOfReturnsBuffer); 112 | numberOfReturnsExtractor = getExtractor("NumberOfReturns"); 113 | } 114 | 115 | if (dimensions["PointSourceId"]) { 116 | pointSourceIdBuffer = new ArrayBuffer(numPoints * 2); 117 | pointSourceId = new Uint16Array(pointSourceIdBuffer); 118 | pointSourceIdExtractor = getExtractor("PointSourceId"); 119 | } 120 | 121 | let mean = [0, 0, 0]; 122 | let bounds = { 123 | min: [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE], 124 | max: [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE], 125 | }; 126 | 127 | let x, y, z, r, g, b; 128 | for (let i = 0; i < numPoints; ++i) { 129 | let pos = i * pointSize; 130 | if (xyz) { 131 | x = xyzExtractor[0](pos) * scale.x + offset.x - mins[0]; 132 | y = xyzExtractor[1](pos) * scale.y + offset.y - mins[1]; 133 | z = xyzExtractor[2](pos) * scale.z + offset.z - mins[2]; 134 | 135 | mean[0] += x / numPoints; 136 | mean[1] += y / numPoints; 137 | mean[2] += z / numPoints; 138 | 139 | bounds.min[0] = Math.min(bounds.min[0], x); 140 | bounds.min[1] = Math.min(bounds.min[1], y); 141 | bounds.min[2] = Math.min(bounds.min[2], z); 142 | 143 | bounds.max[0] = Math.max(bounds.max[0], x); 144 | bounds.max[1] = Math.max(bounds.max[1], y); 145 | bounds.max[2] = Math.max(bounds.max[2], z); 146 | 147 | xyz[3 * i + 0] = x; 148 | xyz[3 * i + 1] = y; 149 | xyz[3 * i + 2] = z; 150 | } 151 | 152 | if (rgb) { 153 | r = rgbExtractor[0](pos); 154 | g = rgbExtractor[1](pos); 155 | b = rgbExtractor[2](pos); 156 | 157 | if (twoByteColor) { 158 | r /= 256; 159 | g /= 256; 160 | b /= 256; 161 | } 162 | 163 | rgb[4 * i + 0] = r; 164 | rgb[4 * i + 1] = g; 165 | rgb[4 * i + 2] = b; 166 | } 167 | 168 | if (intensity) intensity[i] = intensityExtractor(pos); 169 | if (classification) classification[i] = classificationExtractor(pos); 170 | if (returnNumber) returnNumber[i] = returnNumberExtractor(pos); 171 | if (numberOfReturns) numberOfReturns[i] = numberOfReturnsExtractor(pos); 172 | if (pointSourceId) pointSourceId[i] = pointSourceIdExtractor(pos); 173 | } 174 | 175 | let indicesBuffer = new ArrayBuffer(numPoints * 4); 176 | let indices = new Uint32Array(indicesBuffer); 177 | for (let i = 0; i < numPoints; ++i) { 178 | indices[i] = i; 179 | } 180 | 181 | let message = { 182 | numPoints: numPoints, 183 | tightBoundingBox: bounds, 184 | mean: mean, 185 | 186 | position: xyzBuffer, 187 | color: rgbBuffer, 188 | intensity: intensityBuffer, 189 | classification: classificationBuffer, 190 | returnNumber: returnNumberBuffer, 191 | numberOfReturns: numberOfReturnsBuffer, 192 | pointSourceId: pointSourceIdBuffer, 193 | indices: indicesBuffer 194 | }; 195 | 196 | let transferables = [ 197 | message.position, 198 | message.color, 199 | message.intensity, 200 | message.classification, 201 | message.returnNumber, 202 | message.numberOfReturns, 203 | message.pointSourceId, 204 | message.indices 205 | ].filter((v) => v); 206 | 207 | postMessage(message, transferables); 208 | } 209 | 210 | -------------------------------------------------------------------------------- /public/extensions/PotreeExtension/contents/workers/EptLaszipDecoderWorker.js: -------------------------------------------------------------------------------- 1 | function readUsingDataView(event) { 2 | performance.mark("laslaz-start"); 3 | 4 | let buffer = event.data.buffer; 5 | let numPoints = event.data.numPoints; 6 | let pointSize = event.data.pointSize; 7 | let pointFormat = event.data.pointFormatID; 8 | let scale = event.data.scale; 9 | let offset = event.data.offset; 10 | 11 | let sourceUint8 = new Uint8Array(buffer); 12 | let sourceView = new DataView(buffer); 13 | 14 | let tightBoundingBox = { 15 | min: [ 16 | Number.POSITIVE_INFINITY, 17 | Number.POSITIVE_INFINITY, 18 | Number.POSITIVE_INFINITY 19 | ], 20 | max: [ 21 | Number.NEGATIVE_INFINITY, 22 | Number.NEGATIVE_INFINITY, 23 | Number.NEGATIVE_INFINITY 24 | ] 25 | }; 26 | 27 | let mean = [0, 0, 0]; 28 | 29 | let pBuff = new ArrayBuffer(numPoints * 3 * 4); 30 | let cBuff = new ArrayBuffer(numPoints * 4); 31 | let iBuff = new ArrayBuffer(numPoints * 4); 32 | let clBuff = new ArrayBuffer(numPoints); 33 | let rnBuff = new ArrayBuffer(numPoints); 34 | let nrBuff = new ArrayBuffer(numPoints); 35 | let psBuff = new ArrayBuffer(numPoints * 2); 36 | 37 | let positions = new Float32Array(pBuff); 38 | let colors = new Uint8Array(cBuff); 39 | let intensities = new Float32Array(iBuff); 40 | let classifications = new Uint8Array(clBuff); 41 | let returnNumbers = new Uint8Array(rnBuff); 42 | let numberOfReturns = new Uint8Array(nrBuff); 43 | let pointSourceIDs = new Uint16Array(psBuff); 44 | 45 | // Point format 3 contains an 8-byte GpsTime before RGB values, so make 46 | // sure we have the correct color offset. 47 | let hasColor = pointFormat == 2 || pointFormat == 3; 48 | let co = pointFormat == 2 ? 20 : 28; 49 | 50 | // TODO This should be cached per-resource since this is an expensive check. 51 | let twoByteColor = false; 52 | if (hasColor) { 53 | let r, g, b, pos; 54 | for (let i = 0; i < numPoints && !twoByteColor; ++i) { 55 | pos = i * pointSize; 56 | r = sourceView.getUint16(pos + co, true) 57 | g = sourceView.getUint16(pos + co + 2, true) 58 | b = sourceView.getUint16(pos + co + 4, true) 59 | if (r > 255 || g > 255 || b > 255) twoByteColor = true; 60 | } 61 | } 62 | 63 | for (let i = 0; i < numPoints; i++) { 64 | // POSITION 65 | let ux = sourceView.getInt32(i * pointSize + 0, true); 66 | let uy = sourceView.getInt32(i * pointSize + 4, true); 67 | let uz = sourceView.getInt32(i * pointSize + 8, true); 68 | 69 | x = ux * scale[0] + offset[0] - event.data.mins[0]; 70 | y = uy * scale[1] + offset[1] - event.data.mins[1]; 71 | z = uz * scale[2] + offset[2] - event.data.mins[2]; 72 | 73 | positions[3 * i + 0] = x; 74 | positions[3 * i + 1] = y; 75 | positions[3 * i + 2] = z; 76 | 77 | mean[0] += x / numPoints; 78 | mean[1] += y / numPoints; 79 | mean[2] += z / numPoints; 80 | 81 | tightBoundingBox.min[0] = Math.min(tightBoundingBox.min[0], x); 82 | tightBoundingBox.min[1] = Math.min(tightBoundingBox.min[1], y); 83 | tightBoundingBox.min[2] = Math.min(tightBoundingBox.min[2], z); 84 | 85 | tightBoundingBox.max[0] = Math.max(tightBoundingBox.max[0], x); 86 | tightBoundingBox.max[1] = Math.max(tightBoundingBox.max[1], y); 87 | tightBoundingBox.max[2] = Math.max(tightBoundingBox.max[2], z); 88 | 89 | // INTENSITY 90 | let intensity = sourceView.getUint16(i * pointSize + 12, true); 91 | intensities[i] = intensity; 92 | 93 | // RETURN NUMBER, stored in the first 3 bits - 00000111 94 | // number of returns stored in next 3 bits - 00111000 95 | let returnNumberAndNumberOfReturns = sourceView.getUint8(i * pointSize + 14, true); 96 | let returnNumber = returnNumberAndNumberOfReturns & 0b0111; 97 | let numberOfReturn = (returnNumberAndNumberOfReturns & 0b00111000) >> 3; 98 | returnNumbers[i] = returnNumber; 99 | numberOfReturns[i] = numberOfReturn; 100 | 101 | // CLASSIFICATION 102 | let classification = sourceView.getUint8(i * pointSize + 15, true); 103 | classifications[i] = classification; 104 | 105 | // POINT SOURCE ID 106 | let pointSourceID = sourceView.getUint16(i * pointSize + 18, true); 107 | pointSourceIDs[i] = pointSourceID; 108 | 109 | // COLOR, if available 110 | if (hasColor) { 111 | let r = sourceView.getUint16(i * pointSize + co, true) 112 | let g = sourceView.getUint16(i * pointSize + co + 2, true) 113 | let b = sourceView.getUint16(i * pointSize + co + 4, true) 114 | 115 | if (twoByteColor) { 116 | r /= 256; 117 | g /= 256; 118 | b /= 256; 119 | } 120 | 121 | colors[4 * i + 0] = r; 122 | colors[4 * i + 1] = g; 123 | colors[4 * i + 2] = b; 124 | colors[4 * i + 3] = 255; 125 | } 126 | } 127 | 128 | let indices = new ArrayBuffer(numPoints * 4); 129 | let iIndices = new Uint32Array(indices); 130 | for (let i = 0; i < numPoints; i++) { 131 | iIndices[i] = i; 132 | } 133 | 134 | performance.mark("laslaz-end"); 135 | 136 | //{ // print timings 137 | // performance.measure("laslaz", "laslaz-start", "laslaz-end"); 138 | // let measure = performance.getEntriesByType("measure")[0]; 139 | // let dpp = 1000 * measure.duration / numPoints; 140 | // let debugMessage = `${measure.duration.toFixed(3)} ms, ${numPoints} points, ${dpp.toFixed(3)} µs / point`; 141 | // console.log(debugMessage); 142 | //} 143 | performance.clearMarks(); 144 | performance.clearMeasures(); 145 | 146 | let message = { 147 | mean: mean, 148 | position: pBuff, 149 | color: cBuff, 150 | intensity: iBuff, 151 | classification: clBuff, 152 | returnNumber: rnBuff, 153 | numberOfReturns: nrBuff, 154 | pointSourceID: psBuff, 155 | tightBoundingBox: tightBoundingBox, 156 | indices: indices 157 | }; 158 | 159 | let transferables = [ 160 | message.position, 161 | message.color, 162 | message.intensity, 163 | message.classification, 164 | message.returnNumber, 165 | message.numberOfReturns, 166 | message.pointSourceID, 167 | message.indices 168 | ]; 169 | 170 | postMessage(message, transferables); 171 | }; 172 | 173 | 174 | 175 | onmessage = readUsingDataView; 176 | 177 | -------------------------------------------------------------------------------- /public/extensions/PotreeExtension/contents/workers/LASDecoderWorker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | onmessage = function(event) 4 | { 5 | var buffer = event.data.buffer; 6 | var numPoints = event.data.numPoints; 7 | var sourcePointSize = event.data.pointSize; 8 | var pointFormatID = event.data.pointFormatID; 9 | var scale = event.data.scale; 10 | var offset = event.data.offset; 11 | 12 | var sourceUint8 = new Uint8Array(buffer); 13 | var sourceView = new DataView(buffer); 14 | 15 | var targetPointSize = 40; 16 | var targetBuffer = new ArrayBuffer(numPoints * targetPointSize); 17 | var targetView = new DataView(targetBuffer); 18 | 19 | var tightBoundingBox = 20 | { 21 | min: [ Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY ], 22 | max: [ Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY ] 23 | }; 24 | 25 | var mean = [0, 0, 0]; 26 | 27 | var pBuff = new ArrayBuffer(numPoints * 3 * 4); 28 | var cBuff = new ArrayBuffer(numPoints * 4); 29 | var iBuff = new ArrayBuffer(numPoints * 4); 30 | var clBuff = new ArrayBuffer(numPoints); 31 | var rnBuff = new ArrayBuffer(numPoints); 32 | var nrBuff = new ArrayBuffer(numPoints); 33 | var psBuff = new ArrayBuffer(numPoints * 2); 34 | 35 | var positions = new Float32Array(pBuff); 36 | var colors = new Uint8Array(cBuff); 37 | var intensities = new Float32Array(iBuff); 38 | var classifications = new Uint8Array(clBuff); 39 | var returnNumbers = new Uint8Array(rnBuff); 40 | var numberOfReturns = new Uint8Array(nrBuff); 41 | var pointSourceIDs = new Uint16Array(psBuff); 42 | 43 | for (var i = 0; i < numPoints; i++) 44 | { 45 | //POSITION 46 | var ux = sourceView.getInt32(i * sourcePointSize + 0, true); 47 | var uy = sourceView.getInt32(i * sourcePointSize + 4, true); 48 | var uz = sourceView.getInt32(i * sourcePointSize + 8, true); 49 | 50 | var x = ux * scale[0] + offset[0] - event.data.mins[0]; 51 | var y = uy * scale[1] + offset[1] - event.data.mins[1]; 52 | var z = uz * scale[2] + offset[2] - event.data.mins[2]; 53 | 54 | positions[3 * i + 0] = x; 55 | positions[3 * i + 1] = y; 56 | positions[3 * i + 2] = z; 57 | 58 | mean[0] += x / numPoints; 59 | mean[1] += y / numPoints; 60 | mean[2] += z / numPoints; 61 | 62 | tightBoundingBox.min[0] = Math.min(tightBoundingBox.min[0], x); 63 | tightBoundingBox.min[1] = Math.min(tightBoundingBox.min[1], y); 64 | tightBoundingBox.min[2] = Math.min(tightBoundingBox.min[2], z); 65 | 66 | tightBoundingBox.max[0] = Math.max(tightBoundingBox.max[0], x); 67 | tightBoundingBox.max[1] = Math.max(tightBoundingBox.max[1], y); 68 | tightBoundingBox.max[2] = Math.max(tightBoundingBox.max[2], z); 69 | 70 | //INTENSITY 71 | var intensity = sourceView.getUint16(i * sourcePointSize + 12, true); 72 | intensities[i] = intensity; 73 | 74 | //RETURN NUMBER, stored in the first 3 bits - 00000111 75 | //number of returns stored in next 3 bits - 00111000 76 | var returnNumberAndNumberOfReturns = sourceView.getUint8(i * sourcePointSize + 14, true); 77 | var returnNumber = returnNumberAndNumberOfReturns & 0b0111; 78 | var numberOfReturn = (returnNumberAndNumberOfReturns & 0b00111000) >> 3; 79 | returnNumbers[i] = returnNumber; 80 | numberOfReturns[i] = numberOfReturn; 81 | 82 | //CLASSIFICATION 83 | var classification = sourceView.getUint8(i * sourcePointSize + 15, true); 84 | classifications[i] = classification; 85 | 86 | //POINT SOURCE ID 87 | var pointSourceID = sourceView.getUint16(i * sourcePointSize + 18, true); 88 | pointSourceIDs[i] = pointSourceID; 89 | 90 | //COLOR, if available 91 | if (pointFormatID === 2) { 92 | var r = sourceView.getUint16(i * sourcePointSize + 20, true) / 256; 93 | var g = sourceView.getUint16(i * sourcePointSize + 22, true) / 256; 94 | var b = sourceView.getUint16(i * sourcePointSize + 24, true) / 256; 95 | 96 | colors[4 * i + 0] = r; 97 | colors[4 * i + 1] = g; 98 | colors[4 * i + 2] = b; 99 | colors[4 * i + 3] = 255; 100 | } 101 | } 102 | 103 | var indices = new ArrayBuffer(numPoints * 4); 104 | var iIndices = new Uint32Array(indices); 105 | for (var i = 0; i < numPoints; i++) 106 | { 107 | iIndices[i] = i; 108 | } 109 | 110 | var message = 111 | { 112 | mean: mean, 113 | position: pBuff, 114 | color: cBuff, 115 | intensity: iBuff, 116 | classification: clBuff, 117 | returnNumber: rnBuff, 118 | numberOfReturns: nrBuff, 119 | pointSourceID: psBuff, 120 | tightBoundingBox: tightBoundingBox, 121 | indices: indices 122 | }; 123 | 124 | var transferables = 125 | [ 126 | message.position, 127 | message.color, 128 | message.intensity, 129 | message.classification, 130 | message.returnNumber, 131 | message.numberOfReturns, 132 | message.pointSourceID, 133 | message.indices 134 | ]; 135 | 136 | postMessage(message, transferables); 137 | }; 138 | -------------------------------------------------------------------------------- /public/extensions/PotreeExtension/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/PotreeExtension/extension.gif -------------------------------------------------------------------------------- /public/extensions/RoomLocatorExtension/README.md: -------------------------------------------------------------------------------- 1 | # Room Locator 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=RoomLocatorExtension) 4 | 5 | A APS Viewer extension for finding the room where the selected object is located 6 | 7 | ![thumbnail 1](extension-1.jpeg) 8 | 9 | ![thumbnail 2](extension-2.jpeg) 10 | 11 | ## Limitation 12 | 13 | - Revit room only exists in the [master views](https://aps.autodesk.com/en/docs/model-derivative/v2/tutorials/prep-roominfo4viewer/about-this-tutorial/), so before loading this extension, please ensure the current loaded model is a master view. 14 | - This process would reduce the viewer performance since JavaScript is running on a single thread on the Web Browser, so you may use some technologies like the [web worker](https://www.w3schools.com/html/html5_webworkers.asp) to do the complex calculations on a separate thread. 15 | 16 | ## Usage 17 | 18 | Enable the extension, select an object in the viewer, right-click to open the context menu, and then click on the `Find room`. 19 | 20 | ## Setup 21 | 22 | Include the JS file on your page. This CDN is compatible with the latest Viewer version (v7). 23 | 24 | ```xml 25 | 26 | ``` 27 | 28 | After Viewer is ready, preferable inside `onDocumentLoadSuccess`, load the extension 29 | 30 | ```javascript 31 | viewer.loadExtension("RoomLocatorExtension") 32 | ``` 33 | 34 | ## Author 35 | [Eason Kang](https://twitter.com/yiskang) -------------------------------------------------------------------------------- /public/extensions/RoomLocatorExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RoomLocatorExtension", 3 | "displayname": "Room Locator", 4 | "description": "A APS Viewer extension for finding the room where the selected object is located", 5 | "options": { 6 | "alertOnDefaultFailure": true, 7 | "autoUnloadOnNoAecModelData": true 8 | }, 9 | "editoptions":"true", 10 | "viewerversion": "7.*", 11 | "loadonstartup": "false", 12 | "filestoload": { 13 | "cssfiles": [], 14 | "jsfiles": [ 15 | "main.js" 16 | ] 17 | }, 18 | "bloglink": "", 19 | "includeinlist": "true", 20 | "externaldependencies": [ 21 | { 22 | "type": "js", 23 | "link": "https://cdn.jsdelivr.net/gh/Wilt/ThreeCSG@develop/ThreeCSG.js" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /public/extensions/RoomLocatorExtension/extension-1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/RoomLocatorExtension/extension-1.jpeg -------------------------------------------------------------------------------- /public/extensions/RoomLocatorExtension/extension-2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/RoomLocatorExtension/extension-2.jpeg -------------------------------------------------------------------------------- /public/extensions/TabSelectionExtension/README.md: -------------------------------------------------------------------------------- 1 | # TAB Selection Extension 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=TabSelectionExtension) 4 | 5 | Use TAB key to rotate selection, similar to Revit TAB to select nested families. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Setup 10 | 11 | Include the JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 12 | 13 | ```xml 14 | 15 | ``` 16 | 17 | Load the extension passing the properties you want to add to various objects based on their `dbId`. 18 | 19 | ```javascript 20 | viewer.loadExtension('TabSelectionExtension'); 21 | ``` 22 | 23 | ## How it works 24 | 25 | Select an element, press TAB to select the parent node. Each TAB press will move up on the tree until the root, then reset back to the initial selection. 26 | -------------------------------------------------------------------------------- /public/extensions/TabSelectionExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"TabSelectionExtension", 3 | "displayname": "Tab selection", 4 | "description":"Use TAB to rotate selection based on the model tree. For Revit files, it mimics the nested family selection.", 5 | "options":{}, 6 | "loadonstartup": "false", 7 | "filestoload":{ 8 | "jsfiles":["main.js"], 9 | "cssfiles": [] 10 | }, 11 | "bloglink":"https://github.com/autodesk-forge/forge-extensions/tree/master/public/extensions/TabSelectionExtension", 12 | "gif": "extension.gif", 13 | "includeinlist":"true", 14 | "externaldependencies":[] 15 | } -------------------------------------------------------------------------------- /public/extensions/TabSelectionExtension/contents/main.js: -------------------------------------------------------------------------------- 1 | class TabSelectionExtension extends Autodesk.Viewing.Extension { 2 | constructor(viewer, options) { 3 | super(viewer, options); 4 | this.viewer = viewer; 5 | this.keyDownEventBinder = this.keyDownEvent.bind(this); 6 | this.selectionChangedEventBinder = this.selectionChangedEvent.bind(this); 7 | this.initialSelection = []; 8 | } 9 | 10 | load() { 11 | // track tabs on the document level 12 | document.addEventListener('keydown', this.keyDownEventBinder); 13 | // and viewer selection event 14 | this.viewer.addEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.selectionChangedEventBinder); 15 | return true; 16 | } 17 | 18 | unload() { 19 | document.removeEventListener('keydown', this.keyDownEventBinder); 20 | this.viewer.removeEventListener(Autodesk.Viewing.SELECTION_CHANGED_EVENT, this.selectionChangedEventBinder); 21 | return true; 22 | } 23 | 24 | keyDownEvent(e) { 25 | // key to track 26 | let rotateKey = 9; // TAB 27 | if (e.keyCode == rotateKey) { 28 | // just start the rotate if something is select 29 | if (this.viewer.getSelectionCount() > 0) { 30 | // prevent the default TAB behavior 31 | e.preventDefault(); 32 | this.rotate(); 33 | } 34 | } 35 | } 36 | 37 | selectionChangedEvent() { 38 | // clear the initial selection if user unselect 39 | if (this.viewer.getSelectionCount() === 0) this.initialSelection = []; 40 | } 41 | 42 | rotate() { 43 | // get current selection 44 | let dbIds = this.viewer.getSelection(); 45 | // if first time, let's store the initial dbId 46 | if (this.initialSelection.length == 0) this.initialSelection = dbIds; 47 | // this gives the model tree 48 | let tree = this.viewer.model.getInstanceTree(); 49 | // prepare to store the parent nodes 50 | let newSelection = []; 51 | // let's check the selection... 52 | dbIds.forEach(dbId => { 53 | // get the parent of each selected dbId 54 | let parentId = tree.getNodeParentId(dbId); 55 | // if we reach the root, stop 56 | if (parentId === tree.getRootId()) return; 57 | // store the parent for selection 58 | newSelection.push(parentId); 59 | }); 60 | // any parent to select? 61 | if (newSelection.length > 0) 62 | this.viewer.select(newSelection); 63 | else { 64 | // otherwise return to the initial selection 65 | this.viewer.select(this.initialSelection); 66 | this.initialSelection = []; 67 | } 68 | } 69 | } 70 | 71 | Autodesk.Viewing.theExtensionManager.registerExtension('TabSelectionExtension', TabSelectionExtension); -------------------------------------------------------------------------------- /public/extensions/TabSelectionExtension/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/TabSelectionExtension/extension.gif -------------------------------------------------------------------------------- /public/extensions/TransformationExtension/README.md: -------------------------------------------------------------------------------- 1 | # Transform 2 | 3 | [Demo](https://aps-extensions.autodesk.io/?extension=TransformationExtension) 4 | 5 | Allows moving elements on the model. 6 | 7 | ![thumbnail](extension.gif) 8 | 9 | ## Usage 10 | 11 | Enable the extension, click on the model element, use the `gizmo` to move the element. 12 | 13 | ## Setup 14 | 15 | Include the CSS & JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 16 | 17 | ```xml 18 | 19 | 20 | ``` 21 | 22 | After Viewer is ready, preferable inside `onDocumentLoadSuccess`, load the extension 23 | 24 | ```javascript 25 | viewer.loadExtension("TransformationExtension") 26 | ``` 27 | 28 | ## How it works 29 | 30 | Based on the Three.js [transform control](https://threejs.org/examples/misc_controls_transform.html), the development team implemented the section command using this nice 3d manipulator allowing to select plane, axis and rotation. This is provided out of the box with the [GuiViewer3D](https://forge.autodesk.com/en/docs/viewer/v7/reference/Viewing/GuiViewer3D/) 31 | 32 | Reusing some of their implementation to provide the ability to stick the control on the selected mesh and drag it around. It's more challenging that it may seem as the viewer has a complex way to work with Three.js meshes. This is due to enhancements that we created on top of that library to be able to support models with a huge number of meshes. 33 | -------------------------------------------------------------------------------- /public/extensions/TransformationExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"TransformationExtension", 3 | "displayname": "Transformation", 4 | "description":"Pupose of this extension is to select an element and move or transform it in any direction.", 5 | "options":{}, 6 | "loadonstartup": "false", 7 | "filestoload":{ 8 | "jsfiles":["main.js"], 9 | "cssfiles": [] 10 | }, 11 | "gif": "extension.gif", 12 | "includeinlist":"true", 13 | "externaldependencies":[ 14 | {"type": "css", "link": "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css"} 15 | ] 16 | } -------------------------------------------------------------------------------- /public/extensions/TransformationExtension/extension.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/TransformationExtension/extension.gif -------------------------------------------------------------------------------- /public/extensions/XLSExtension/README.md: -------------------------------------------------------------------------------- 1 | # XLS Extension 2 | 3 | This extension demonstrates how to extract metadata and export it as excel file 4 | 5 | [Demo](https://aps-extensions.autodesk.io/?extension=XLSExtension) 6 | 7 | ![thumbnail](extension.jpg) 8 | 9 | ## Usage 10 | 11 | Load the extension and click on excel icon on toolbar. 12 | 13 | ## Setup 14 | 15 | Include the CSS & JS file on your page. This CDN is compatible with the lastest Viewer version (v7). 16 | 17 | ```xml 18 | 19 | 20 | ``` 21 | 22 | ## External Dependencies 23 | 24 | This sample uses [SheetJS](https://github.com/SheetJS/js-xlsx) and [FileSaver](https://github.com/eligrey/FileSaver.js) as main dependencies. [Notifier](https://notifyjs.jpillora.com/) is a secondary or non-core dependency. 25 | 26 | 27 | -------------------------------------------------------------------------------- /public/extensions/XLSExtension/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"XLSExtension", 3 | "displayname": "XLS Extension", 4 | "description": "Download properties in excel file.", 5 | "options":{}, 6 | "viewerversion":"7.*", 7 | "loadonstartup": "false", 8 | "filestoload":{ 9 | "cssfiles":["main.css"], 10 | "jsfiles":["main.js","ForgeXLS.js","modeldata.js"] 11 | }, 12 | "includeinlist":"true", 13 | "externaldependencies":[ 14 | {"type": "js", "link": "https://github.com/eligrey/Blob.js"}, 15 | {"type": "js", "link": "https://github.com/eligrey/FileSaver.js"}, 16 | {"type": "js", "link": "https://github.com/SheetJS/js-xlsx"} 17 | ] 18 | } -------------------------------------------------------------------------------- /public/extensions/XLSExtension/contents/ForgeXLS.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | if (!window.XLSX) alert('Sheet JS is required for this sample'); 20 | 21 | let ApsXLS = { 22 | 23 | downloadXLSX: function (fileName, status) { 24 | 25 | if (status) { 26 | status(false, 'Preparing ' + fileName); 27 | status(false, 'Reading project information....'); 28 | } 29 | 30 | this.prepareTables(function (tables) { 31 | if (status) status(false, 'Building XLSX file...'); 32 | 33 | let wb = new Workbook(); 34 | for (const [name, table] of Object.entries(tables)){ 35 | if (name.indexOf('<')==-1) { // skip tables starting with < 36 | let ws = ApsXLS.sheetFromTable(table); 37 | wb.SheetNames.push(name); 38 | wb.Sheets[name] = ws; 39 | } 40 | }; 41 | 42 | let wbout = XLSX.write(wb, {bookType: 'xlsx', bookSST: true, type: 'binary'}); 43 | saveAs(new Blob([s2ab(wbout)], {type: "application/octet-stream"}), fileName); 44 | 45 | if (status) status(true, 'Downloading...'); 46 | }) 47 | }, 48 | 49 | sheetFromTable: function (table) { 50 | let ws = {}; 51 | let range = {s: {c: 10000000, r: 10000000}, e: {c: 0, r: 0}}; 52 | 53 | let allProperties = []; 54 | table.forEach(function (object) { 55 | for (const [propName, propValue] of Object.entries(object)){ 56 | if (allProperties.indexOf(propName) == -1) 57 | allProperties.push(propName); 58 | } 59 | }); 60 | 61 | table.forEach(function (object) { 62 | allProperties.forEach(function (propName) { 63 | if (!object.hasOwnProperty(propName)) 64 | object[propName] = ''; 65 | }); 66 | }); 67 | 68 | let propsNames = []; 69 | for (let propName in table[0]) { 70 | propsNames.push(propName); 71 | } 72 | //propsNames.sort(); // removed due first 3 ID columns 73 | 74 | let R = 0; 75 | let C = 0; 76 | for (; C != propsNames.length; ++C) { 77 | let cell_ref = XLSX.utils.encode_cell({c: C, r: R}); 78 | ws[cell_ref] = {v: propsNames[C], t: 's'}; 79 | } 80 | R++; 81 | 82 | for (let index = 0; index != table.length; ++index) { 83 | C = 0; 84 | propsNames.forEach(function (propName) { 85 | if (range.s.r > R) range.s.r = 0; 86 | if (range.s.c > C) range.s.c = 0; 87 | if (range.e.r < R) range.e.r = R; 88 | if (range.e.c < C) range.e.c = C; 89 | let cell = {v: table[index][propName]}; 90 | if (cell.v == null) return; 91 | let cell_ref = XLSX.utils.encode_cell({c: C, r: R}); 92 | 93 | if (typeof cell.v === 'number') cell.t = 'n'; 94 | else if (typeof cell.v === 'boolean') cell.t = 'b'; 95 | else if (cell.v instanceof Date) { 96 | cell.t = 'n'; 97 | cell.z = XLSX.SSF._table[14]; 98 | cell.v = datenum(cell.v); 99 | } 100 | else cell.t = 's'; 101 | 102 | ws[cell_ref] = cell; 103 | C++; 104 | }); 105 | R++; 106 | } 107 | if (range.s.c < 10000000) ws['!ref'] = XLSX.utils.encode_range(range); 108 | return ws; 109 | }, 110 | 111 | prepareTables: function (callback) { 112 | let data = new ModelData(this); 113 | data.init(async function () { 114 | let hierarchy = data._modelData.Category; 115 | let t = await ApsXLS.prepareRawData(hierarchy); 116 | callback(t); 117 | }); 118 | }, 119 | 120 | prepareRawData: async function (hierarchy) { 121 | let tables = {}; 122 | for (let key in hierarchy) { 123 | let idsOnCategory = []; 124 | if (hierarchy.hasOwnProperty(key)) { 125 | idsOnCategory = hierarchy[key]; 126 | let rows = await getAllProperties(idsOnCategory); 127 | tables[key] = formatRows(rows); 128 | } 129 | } 130 | return tables;; 131 | } 132 | }; 133 | 134 | // Get Properties by dbid 135 | async function getProperties(model, dbid) { 136 | return new Promise(function(resolve, reject) { 137 | model.getProperties(dbid, function (props) { 138 | resolve(props); 139 | }); 140 | }); 141 | } 142 | 143 | // Get Properties by Category 144 | async function getAllProperties(idsOnCategory) { 145 | return new Promise(function(resolve, reject) { 146 | let promises = []; 147 | idsOnCategory.forEach(function (dbid) { 148 | promises.push(getProperties(NOP_VIEWER.model, dbid)); 149 | }); 150 | resolve(Promise.all(promises)); 151 | }); 152 | } 153 | 154 | // Helper Functions 155 | function Workbook() { 156 | if (!(this instanceof Workbook)) return new Workbook(); 157 | this.SheetNames = []; 158 | this.Sheets = {}; 159 | } 160 | 161 | function datenum(v, date1904) { 162 | if (date1904) v += 1462; 163 | let epoch = Date.parse(v); 164 | return (epoch - new Date(Date.UTC(1899, 11, 30))) / (24 * 60 * 60 * 1000); 165 | } 166 | 167 | function s2ab(s) { 168 | let buf = new ArrayBuffer(s.length); 169 | let view = new Uint8Array(buf); 170 | for (let i = 0; i != s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF; 171 | return buf; 172 | } 173 | 174 | function formatRows(sheets) { 175 | sheets.forEach(sheet => { 176 | let props = sheet.properties 177 | props.forEach(prop =>{ 178 | sheet[prop.displayName] = prop.displayValue 179 | }) 180 | delete sheet.properties 181 | }); 182 | return sheets; 183 | } -------------------------------------------------------------------------------- /public/extensions/XLSExtension/contents/img/excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/XLSExtension/contents/img/excel.png -------------------------------------------------------------------------------- /public/extensions/XLSExtension/contents/libraries/Blob.js: -------------------------------------------------------------------------------- 1 | /* Blob.js 2 | * A Blob implementation. 3 | * 2014-07-24 4 | * 5 | * By Eli Grey, http://eligrey.com 6 | * By Devin Samarin, https://github.com/dsamarin 7 | * License: MIT 8 | * See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md 9 | */ 10 | 11 | /*global self, unescape */ 12 | /*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true, 13 | plusplus: true */ 14 | 15 | /*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */ 16 | 17 | (function (view) { 18 | "use strict"; 19 | 20 | view.URL = view.URL || view.webkitURL; 21 | 22 | if (view.Blob && view.URL) { 23 | try { 24 | new Blob; 25 | return; 26 | } catch (e) {} 27 | } 28 | 29 | // Internally we use a BlobBuilder implementation to base Blob off of 30 | // in order to support older browsers that only have BlobBuilder 31 | var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) { 32 | var 33 | get_class = function(object) { 34 | return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1]; 35 | } 36 | , FakeBlobBuilder = function BlobBuilder() { 37 | this.data = []; 38 | } 39 | , FakeBlob = function Blob(data, type, encoding) { 40 | this.data = data; 41 | this.size = data.length; 42 | this.type = type; 43 | this.encoding = encoding; 44 | } 45 | , FBB_proto = FakeBlobBuilder.prototype 46 | , FB_proto = FakeBlob.prototype 47 | , FileReaderSync = view.FileReaderSync 48 | , FileException = function(type) { 49 | this.code = this[this.name = type]; 50 | } 51 | , file_ex_codes = ( 52 | "NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR " 53 | + "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR" 54 | ).split(" ") 55 | , file_ex_code = file_ex_codes.length 56 | , real_URL = view.URL || view.webkitURL || view 57 | , real_create_object_URL = real_URL.createObjectURL 58 | , real_revoke_object_URL = real_URL.revokeObjectURL 59 | , URL = real_URL 60 | , btoa = view.btoa 61 | , atob = view.atob 62 | 63 | , ArrayBuffer = view.ArrayBuffer 64 | , Uint8Array = view.Uint8Array 65 | 66 | , origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/ 67 | ; 68 | FakeBlob.fake = FB_proto.fake = true; 69 | while (file_ex_code--) { 70 | FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1; 71 | } 72 | // Polyfill URL 73 | if (!real_URL.createObjectURL) { 74 | URL = view.URL = function(uri) { 75 | var 76 | uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a") 77 | , uri_origin 78 | ; 79 | uri_info.href = uri; 80 | if (!("origin" in uri_info)) { 81 | if (uri_info.protocol.toLowerCase() === "data:") { 82 | uri_info.origin = null; 83 | } else { 84 | uri_origin = uri.match(origin); 85 | uri_info.origin = uri_origin && uri_origin[1]; 86 | } 87 | } 88 | return uri_info; 89 | }; 90 | } 91 | URL.createObjectURL = function(blob) { 92 | var 93 | type = blob.type 94 | , data_URI_header 95 | ; 96 | if (type === null) { 97 | type = "application/octet-stream"; 98 | } 99 | if (blob instanceof FakeBlob) { 100 | data_URI_header = "data:" + type; 101 | if (blob.encoding === "base64") { 102 | return data_URI_header + ";base64," + blob.data; 103 | } else if (blob.encoding === "URI") { 104 | return data_URI_header + "," + decodeURIComponent(blob.data); 105 | } if (btoa) { 106 | return data_URI_header + ";base64," + btoa(blob.data); 107 | } else { 108 | return data_URI_header + "," + encodeURIComponent(blob.data); 109 | } 110 | } else if (real_create_object_URL) { 111 | return real_create_object_URL.call(real_URL, blob); 112 | } 113 | }; 114 | URL.revokeObjectURL = function(object_URL) { 115 | if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) { 116 | real_revoke_object_URL.call(real_URL, object_URL); 117 | } 118 | }; 119 | FBB_proto.append = function(data/*, endings*/) { 120 | var bb = this.data; 121 | // decode data to a binary string 122 | if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) { 123 | var 124 | str = "" 125 | , buf = new Uint8Array(data) 126 | , i = 0 127 | , buf_len = buf.length 128 | ; 129 | for (; i < buf_len; i++) { 130 | str += String.fromCharCode(buf[i]); 131 | } 132 | bb.push(str); 133 | } else if (get_class(data) === "Blob" || get_class(data) === "File") { 134 | if (FileReaderSync) { 135 | var fr = new FileReaderSync; 136 | bb.push(fr.readAsBinaryString(data)); 137 | } else { 138 | // async FileReader won't work as BlobBuilder is sync 139 | throw new FileException("NOT_READABLE_ERR"); 140 | } 141 | } else if (data instanceof FakeBlob) { 142 | if (data.encoding === "base64" && atob) { 143 | bb.push(atob(data.data)); 144 | } else if (data.encoding === "URI") { 145 | bb.push(decodeURIComponent(data.data)); 146 | } else if (data.encoding === "raw") { 147 | bb.push(data.data); 148 | } 149 | } else { 150 | if (typeof data !== "string") { 151 | data += ""; // convert unsupported types to strings 152 | } 153 | // decode UTF-16 to binary string 154 | bb.push(unescape(encodeURIComponent(data))); 155 | } 156 | }; 157 | FBB_proto.getBlob = function(type) { 158 | if (!arguments.length) { 159 | type = null; 160 | } 161 | return new FakeBlob(this.data.join(""), type, "raw"); 162 | }; 163 | FBB_proto.toString = function() { 164 | return "[object BlobBuilder]"; 165 | }; 166 | FB_proto.slice = function(start, end, type) { 167 | var args = arguments.length; 168 | if (args < 3) { 169 | type = null; 170 | } 171 | return new FakeBlob( 172 | this.data.slice(start, args > 1 ? end : this.data.length) 173 | , type 174 | , this.encoding 175 | ); 176 | }; 177 | FB_proto.toString = function() { 178 | return "[object Blob]"; 179 | }; 180 | FB_proto.close = function() { 181 | this.size = 0; 182 | delete this.data; 183 | }; 184 | return FakeBlobBuilder; 185 | }(view)); 186 | 187 | view.Blob = function(blobParts, options) { 188 | var type = options ? (options.type || "") : ""; 189 | var builder = new BlobBuilder(); 190 | if (blobParts) { 191 | for (var i = 0, len = blobParts.length; i < len; i++) { 192 | if (Uint8Array && blobParts[i] instanceof Uint8Array) { 193 | builder.append(blobParts[i].buffer); 194 | } 195 | else { 196 | builder.append(blobParts[i]); 197 | } 198 | } 199 | } 200 | var blob = builder.getBlob(type); 201 | if (!blob.slice && blob.webkitSlice) { 202 | blob.slice = blob.webkitSlice; 203 | } 204 | return blob; 205 | }; 206 | 207 | var getPrototypeOf = Object.getPrototypeOf || function(object) { 208 | return object.__proto__; 209 | }; 210 | view.Blob.prototype = getPrototypeOf(new view.Blob()); 211 | }(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this)); 212 | -------------------------------------------------------------------------------- /public/extensions/XLSExtension/contents/libraries/FileSaver.min.js: -------------------------------------------------------------------------------- 1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ 2 | var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})} 3 | -------------------------------------------------------------------------------- /public/extensions/XLSExtension/contents/libraries/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v1.6.1 3 | * https://zenorocha.github.io/clipboard.js 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.Clipboard=e()}}(function(){var e,t,n;return function e(t,n,o){function i(a,c){if(!n[a]){if(!t[a]){var l="function"==typeof require&&require;if(!c&&l)return l(a,!0);if(r)return r(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var s=n[a]={exports:{}};t[a][0].call(s.exports,function(e){var n=t[a][1][e];return i(n?n:e)},s,s.exports,e,t,n,o)}return n[a].exports}for(var r="function"==typeof require&&require,a=0;a0&&void 0!==arguments[0]?arguments[0]:{};this.action=t.action,this.emitter=t.emitter,this.target=t.target,this.text=t.text,this.trigger=t.trigger,this.selectedText=""}},{key:"initSelection",value:function e(){this.text?this.selectFake():this.target&&this.selectTarget()}},{key:"selectFake",value:function e(){var t=this,n="rtl"==document.documentElement.getAttribute("dir");this.removeFake(),this.fakeHandlerCallback=function(){return t.removeFake()},this.fakeHandler=document.body.addEventListener("click",this.fakeHandlerCallback)||!0,this.fakeElem=document.createElement("textarea"),this.fakeElem.style.fontSize="12pt",this.fakeElem.style.border="0",this.fakeElem.style.padding="0",this.fakeElem.style.margin="0",this.fakeElem.style.position="absolute",this.fakeElem.style[n?"right":"left"]="-9999px";var o=window.pageYOffset||document.documentElement.scrollTop;this.fakeElem.style.top=o+"px",this.fakeElem.setAttribute("readonly",""),this.fakeElem.value=this.text,document.body.appendChild(this.fakeElem),this.selectedText=(0,i.default)(this.fakeElem),this.copyText()}},{key:"removeFake",value:function e(){this.fakeHandler&&(document.body.removeEventListener("click",this.fakeHandlerCallback),this.fakeHandler=null,this.fakeHandlerCallback=null),this.fakeElem&&(document.body.removeChild(this.fakeElem),this.fakeElem=null)}},{key:"selectTarget",value:function e(){this.selectedText=(0,i.default)(this.target),this.copyText()}},{key:"copyText",value:function e(){var t=void 0;try{t=document.execCommand(this.action)}catch(e){t=!1}this.handleResult(t)}},{key:"handleResult",value:function e(t){this.emitter.emit(t?"success":"error",{action:this.action,text:this.selectedText,trigger:this.trigger,clearSelection:this.clearSelection.bind(this)})}},{key:"clearSelection",value:function e(){this.target&&this.target.blur(),window.getSelection().removeAllRanges()}},{key:"destroy",value:function e(){this.removeFake()}},{key:"action",set:function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"copy";if(this._action=t,"copy"!==this._action&&"cut"!==this._action)throw new Error('Invalid "action" value, use either "copy" or "cut"')},get:function e(){return this._action}},{key:"target",set:function e(t){if(void 0!==t){if(!t||"object"!==("undefined"==typeof t?"undefined":r(t))||1!==t.nodeType)throw new Error('Invalid "target" value, use a valid Element');if("copy"===this.action&&t.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if("cut"===this.action&&(t.hasAttribute("readonly")||t.hasAttribute("disabled")))throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');this._target=t}},get:function e(){return this._target}}]),e}();e.exports=c})},{select:5}],8:[function(t,n,o){!function(i,r){if("function"==typeof e&&e.amd)e(["module","./clipboard-action","tiny-emitter","good-listener"],r);else if("undefined"!=typeof o)r(n,t("./clipboard-action"),t("tiny-emitter"),t("good-listener"));else{var a={exports:{}};r(a,i.clipboardAction,i.tinyEmitter,i.goodListener),i.clipboard=a.exports}}(this,function(e,t,n,o){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function c(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function l(e,t){var n="data-clipboard-"+e;if(t.hasAttribute(n))return t.getAttribute(n)}var u=i(t),s=i(n),f=i(o),d=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};this.action="function"==typeof t.action?t.action:this.defaultAction,this.target="function"==typeof t.target?t.target:this.defaultTarget,this.text="function"==typeof t.text?t.text:this.defaultText}},{key:"listenClick",value:function e(t){var n=this;this.listener=(0,f.default)(t,"click",function(e){return n.onClick(e)})}},{key:"onClick",value:function e(t){var n=t.delegateTarget||t.currentTarget;this.clipboardAction&&(this.clipboardAction=null),this.clipboardAction=new u.default({action:this.action(n),target:this.target(n),text:this.text(n),trigger:n,emitter:this})}},{key:"defaultAction",value:function e(t){return l("action",t)}},{key:"defaultTarget",value:function e(t){var n=l("target",t);if(n)return document.querySelector(n)}},{key:"defaultText",value:function e(t){return l("text",t)}},{key:"destroy",value:function e(){this.listener.destroy(),this.clipboardAction&&(this.clipboardAction.destroy(),this.clipboardAction=null)}}],[{key:"isSupported",value:function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:["copy","cut"],n="string"==typeof t?[t]:t,o=!!document.queryCommandSupported;return n.forEach(function(e){o=o&&!!document.queryCommandSupported(e)}),o}}]),t}(s.default);e.exports=h})},{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)}); -------------------------------------------------------------------------------- /public/extensions/XLSExtension/contents/libraries/notify.min.js: -------------------------------------------------------------------------------- 1 | (function(e){typeof define=="function"&&define.amd?define(["jquery"],e):typeof module=="object"&&module.exports?module.exports=function(t,n){return n===undefined&&(typeof window!="undefined"?n=require("jquery"):n=require("jquery")(t)),e(n),n}:e(jQuery)})(function(e){function A(t,n,i){typeof i=="string"&&(i={className:i}),this.options=E(w,e.isPlainObject(i)?i:{}),this.loadHTML(),this.wrapper=e(h.html),this.options.clickToHide&&this.wrapper.addClass(r+"-hidable"),this.wrapper.data(r,this),this.arrow=this.wrapper.find("."+r+"-arrow"),this.container=this.wrapper.find("."+r+"-container"),this.container.append(this.userContainer),t&&t.length&&(this.elementType=t.attr("type"),this.originalElement=t,this.elem=N(t),this.elem.data(r,this),this.elem.before(this.wrapper)),this.container.hide(),this.run(n)}var t=[].indexOf||function(e){for(var t=0,n=this.length;t\n
\n
\n',css:"."+r+"-corner {\n position: fixed;\n margin: 5px;\n z-index: 1050;\n}\n\n."+r+"-corner ."+r+"-wrapper,\n."+r+"-corner ."+r+"-container {\n position: relative;\n display: block;\n height: inherit;\n width: inherit;\n margin: 3px;\n}\n\n."+r+"-wrapper {\n z-index: 1;\n position: absolute;\n display: inline-block;\n height: 0;\n width: 0;\n}\n\n."+r+"-container {\n display: none;\n z-index: 1;\n position: absolute;\n}\n\n."+r+"-hidable {\n cursor: pointer;\n}\n\n[data-notify-text],[data-notify-html] {\n position: relative;\n}\n\n."+r+"-arrow {\n position: absolute;\n z-index: 2;\n width: 0;\n height: 0;\n}"},p={"border-radius":["-webkit-","-moz-"]},d=function(e){return c[e]},v=function(e){if(!e)throw"Missing Style name";c[e]&&delete c[e]},m=function(t,i){if(!t)throw"Missing Style name";if(!i)throw"Missing Style definition";if(!i.html)throw"Missing Style HTML";var s=c[t];s&&s.cssElem&&(window.console&&console.warn(n+": overwriting style '"+t+"'"),c[t].cssElem.remove()),i.name=t,c[t]=i;var o="";i.classes&&e.each(i.classes,function(t,n){return o+="."+r+"-"+i.name+"-"+t+" {\n",e.each(n,function(t,n){return p[t]&&e.each(p[t],function(e,r){return o+=" "+r+t+": "+n+";\n"}),o+=" "+t+": "+n+";\n"}),o+="}\n"}),i.css&&(o+="/* styles for "+i.name+" */\n"+i.css),o&&(i.cssElem=g(o),i.cssElem.attr("id","notify-"+i.name));var u={},a=e(i.html);y("html",a,u),y("text",a,u),i.fields=u},g=function(t){var n,r,i;r=x("style"),r.attr("type","text/css"),e("head").append(r);try{r.html(t)}catch(s){r[0].styleSheet.cssText=t}return r},y=function(t,n,r){var s;return t!=="html"&&(t="text"),s="data-notify-"+t,b(n,"["+s+"]").each(function(){var n;n=e(this).attr(s),n||(n=i),r[n]=t})},b=function(e,t){return e.is(t)?e:e.find(t)},w={clickToHide:!0,autoHide:!0,autoHideDelay:5e3,arrowShow:!0,arrowSize:5,breakNewLines:!0,elementPosition:"bottom",globalPosition:"top right",style:"bootstrap",className:"error",showAnimation:"slideDown",showDuration:400,hideAnimation:"slideUp",hideDuration:200,gap:5},E=function(t,n){var r;return r=function(){},r.prototype=t,e.extend(!0,new r,n)},S=function(t){return e.extend(w,t)},x=function(t){return e("<"+t+">")},T={},N=function(t){var n;return t.is("[type=radio]")&&(n=t.parents("form:first").find("[type=radio]").filter(function(n,r){return e(r).attr("name")===t.attr("name")}),t=n.first()),t},C=function(e,t,n){var r,i;if(typeof n=="string")n=parseInt(n,10);else if(typeof n!="number")return;if(isNaN(n))return;return r=s[f[t.charAt(0)]],i=t,e[r]!==undefined&&(t=s[r.charAt(0)],n=-n),e[t]===undefined?e[t]=n:e[t]+=n,null},k=function(e,t,n){if(e==="l"||e==="t")return 0;if(e==="c"||e==="m")return n/2-t/2;if(e==="r"||e==="b")return n-t;throw"Invalid alignment"},L=function(e){return L.e=L.e||x("div"),L.e.text(e).html()};A.prototype.loadHTML=function(){var t;t=this.getStyle(),this.userContainer=e(t.html),this.userFields=t.fields},A.prototype.show=function(e,t){var n,r,i,s,o;r=function(n){return function(){!e&&!n.elem&&n.destroy();if(t)return t()}}(this),o=this.container.parent().parents(":hidden").length>0,i=this.container.add(this.arrow),n=[];if(o&&e)s="show";else if(o&&!e)s="hide";else if(!o&&e)s=this.options.showAnimation,n.push(this.options.showDuration);else{if(!!o||!!e)return r();s=this.options.hideAnimation,n.push(this.options.hideDuration)}return n.push(r),i[s].apply(i,n)},A.prototype.setGlobalPosition=function(){var t=this.getPosition(),n=t[0],i=t[1],o=s[n],u=s[i],a=n+"|"+i,f=T[a];if(!f||!document.body.contains(f[0])){f=T[a]=x("div");var l={};l[o]=0,u==="middle"?l.top="45%":u==="center"?l.left="45%":l[u]=0,f.css(l).addClass(r+"-corner"),e("body").append(f)}return f.prepend(this.wrapper)},A.prototype.setElementPosition=function(){var n,r,i,l,c,h,p,d,v,m,g,y,b,w,E,S,x,T,N,L,A,O,M,_,D,P,H,B,j;H=this.getPosition(),_=H[0],O=H[1],M=H[2],g=this.elem.position(),d=this.elem.outerHeight(),y=this.elem.outerWidth(),v=this.elem.innerHeight(),m=this.elem.innerWidth(),j=this.wrapper.position(),c=this.container.height(),h=this.container.width(),T=s[_],L=f[_],A=s[L],p={},p[A]=_==="b"?d:_==="r"?y:0,C(p,"top",g.top-j.top),C(p,"left",g.left-j.left),B=["top","left"];for(w=0,S=B.length;w=0&&C(r,s[O],i*2)}t.call(u,_)>=0?(C(p,"left",k(O,h,y)),r&&C(r,"left",k(O,i,m))):t.call(o,_)>=0&&(C(p,"top",k(O,c,d)),r&&C(r,"top",k(O,i,v))),this.container.is(":visible")&&(p.display="block"),this.container.removeAttr("style").css(p);if(r)return this.arrow.removeAttr("style").css(r)},A.prototype.getPosition=function(){var e,n,r,i,s,f,c,h;h=this.options.position||(this.elem?this.options.elementPosition:this.options.globalPosition),e=l(h),e.length===0&&(e[0]="b");if(n=e[0],t.call(a,n)<0)throw"Must be one of ["+a+"]";if(e.length===1||(r=e[0],t.call(u,r)>=0)&&(i=e[1],t.call(o,i)<0)||(s=e[0],t.call(o,s)>=0)&&(f=e[1],t.call(u,f)<0))e[1]=(c=e[0],t.call(o,c)>=0)?"m":"l";return e.length===2&&(e[2]=e[1]),e},A.prototype.getStyle=function(e){var t;e||(e=this.options.style),e||(e="default"),t=c[e];if(!t)throw"Missing style: "+e;return t},A.prototype.updateClasses=function(){var t,n;return t=["base"],e.isArray(this.options.className)?t=t.concat(this.options.className):this.options.className&&t.push(this.options.className),n=this.getStyle(),t=e.map(t,function(e){return r+"-"+n.name+"-"+e}).join(" "),this.userContainer.attr("class",t)},A.prototype.run=function(t,n){var r,s,o,u,a;e.isPlainObject(n)?e.extend(this.options,n):e.type(n)==="string"&&(this.options.className=n);if(this.container&&!t){this.show(!1);return}if(!this.container&&!t)return;s={},e.isPlainObject(t)?s=t:s[i]=t;for(o in s){r=s[o],u=this.userFields[o];if(!u)continue;u==="text"&&(r=L(r),this.options.breakNewLines&&(r=r.replace(/\n/g,"
"))),a=o===i?"":"="+o,b(this.userContainer,"[data-notify-"+u+a+"]").html(r)}this.updateClasses(),this.elem?this.setElementPosition():this.setGlobalPosition(),this.show(!0),this.options.autoHide&&(clearTimeout(this.autohideTimer),this.autohideTimer=setTimeout(this.show.bind(this,!1),this.options.autoHideDelay))},A.prototype.destroy=function(){this.wrapper.data(r,null),this.wrapper.remove()},e[n]=function(t,r,i){return t&&t.nodeName||t.jquery?e(t)[n](r,i):(i=r,r=t,new A(null,r,i)),t},e.fn[n]=function(t,n){return e(this).each(function(){var i=N(e(this)).data(r);i&&i.destroy();var s=new A(e(this),t,n)}),this},e.extend(e[n],{defaults:S,addStyle:m,removeStyle:v,pluginOptions:w,getStyle:d,insertCSS:g}),m("bootstrap",{html:"
\n\n
",classes:{base:{"font-weight":"bold",padding:"8px 15px 8px 14px","text-shadow":"0 1px 0 rgba(255, 255, 255, 0.5)","background-color":"#fcf8e3",border:"1px solid #fbeed5","border-radius":"4px","white-space":"nowrap","padding-left":"25px","background-repeat":"no-repeat","background-position":"3px 7px"},error:{color:"#B94A48","background-color":"#F2DEDE","border-color":"#EED3D7","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAtRJREFUeNqkVc1u00AQHq+dOD+0poIQfkIjalW0SEGqRMuRnHos3DjwAH0ArlyQeANOOSMeAA5VjyBxKBQhgSpVUKKQNGloFdw4cWw2jtfMOna6JOUArDTazXi/b3dm55socPqQhFka++aHBsI8GsopRJERNFlY88FCEk9Yiwf8RhgRyaHFQpPHCDmZG5oX2ui2yilkcTT1AcDsbYC1NMAyOi7zTX2Agx7A9luAl88BauiiQ/cJaZQfIpAlngDcvZZMrl8vFPK5+XktrWlx3/ehZ5r9+t6e+WVnp1pxnNIjgBe4/6dAysQc8dsmHwPcW9C0h3fW1hans1ltwJhy0GxK7XZbUlMp5Ww2eyan6+ft/f2FAqXGK4CvQk5HueFz7D6GOZtIrK+srupdx1GRBBqNBtzc2AiMr7nPplRdKhb1q6q6zjFhrklEFOUutoQ50xcX86ZlqaZpQrfbBdu2R6/G19zX6XSgh6RX5ubyHCM8nqSID6ICrGiZjGYYxojEsiw4PDwMSL5VKsC8Yf4VRYFzMzMaxwjlJSlCyAQ9l0CW44PBADzXhe7xMdi9HtTrdYjFYkDQL0cn4Xdq2/EAE+InCnvADTf2eah4Sx9vExQjkqXT6aAERICMewd/UAp/IeYANM2joxt+q5VI+ieq2i0Wg3l6DNzHwTERPgo1ko7XBXj3vdlsT2F+UuhIhYkp7u7CarkcrFOCtR3H5JiwbAIeImjT/YQKKBtGjRFCU5IUgFRe7fF4cCNVIPMYo3VKqxwjyNAXNepuopyqnld602qVsfRpEkkz+GFL1wPj6ySXBpJtWVa5xlhpcyhBNwpZHmtX8AGgfIExo0ZpzkWVTBGiXCSEaHh62/PoR0p/vHaczxXGnj4bSo+G78lELU80h1uogBwWLf5YlsPmgDEd4M236xjm+8nm4IuE/9u+/PH2JXZfbwz4zw1WbO+SQPpXfwG/BBgAhCNZiSb/pOQAAAAASUVORK5CYII=)"},success:{color:"#468847","background-color":"#DFF0D8","border-color":"#D6E9C6","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAutJREFUeNq0lctPE0Ecx38zu/RFS1EryqtgJFA08YCiMZIAQQ4eRG8eDGdPJiYeTIwHTfwPiAcvXIwXLwoXPaDxkWgQ6islKlJLSQWLUraPLTv7Gme32zoF9KSTfLO7v53vZ3d/M7/fIth+IO6INt2jjoA7bjHCJoAlzCRw59YwHYjBnfMPqAKWQYKjGkfCJqAF0xwZjipQtA3MxeSG87VhOOYegVrUCy7UZM9S6TLIdAamySTclZdYhFhRHloGYg7mgZv1Zzztvgud7V1tbQ2twYA34LJmF4p5dXF1KTufnE+SxeJtuCZNsLDCQU0+RyKTF27Unw101l8e6hns3u0PBalORVVVkcaEKBJDgV3+cGM4tKKmI+ohlIGnygKX00rSBfszz/n2uXv81wd6+rt1orsZCHRdr1Imk2F2Kob3hutSxW8thsd8AXNaln9D7CTfA6O+0UgkMuwVvEFFUbbAcrkcTA8+AtOk8E6KiQiDmMFSDqZItAzEVQviRkdDdaFgPp8HSZKAEAL5Qh7Sq2lIJBJwv2scUqkUnKoZgNhcDKhKg5aH+1IkcouCAdFGAQsuWZYhOjwFHQ96oagWgRoUov1T9kRBEODAwxM2QtEUl+Wp+Ln9VRo6BcMw4ErHRYjH4/B26AlQoQQTRdHWwcd9AH57+UAXddvDD37DmrBBV34WfqiXPl61g+vr6xA9zsGeM9gOdsNXkgpEtTwVvwOklXLKm6+/p5ezwk4B+j6droBs2CsGa/gNs6RIxazl4Tc25mpTgw/apPR1LYlNRFAzgsOxkyXYLIM1V8NMwyAkJSctD1eGVKiq5wWjSPdjmeTkiKvVW4f2YPHWl3GAVq6ymcyCTgovM3FzyRiDe2TaKcEKsLpJvNHjZgPNqEtyi6mZIm4SRFyLMUsONSSdkPeFtY1n0mczoY3BHTLhwPRy9/lzcziCw9ACI+yql0VLzcGAZbYSM5CCSZg1/9oc/nn7+i8N9p/8An4JMADxhH+xHfuiKwAAAABJRU5ErkJggg==)"},info:{color:"#3A87AD","background-color":"#D9EDF7","border-color":"#BCE8F1","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYFAhkSsdes/QAAA8dJREFUOMvVlGtMW2UYx//POaWHXg6lLaW0ypAtw1UCgbniNOLcVOLmAjHZolOYlxmTGXVZdAnRfXQm+7SoU4mXaOaiZsEpC9FkiQs6Z6bdCnNYruM6KNBw6YWewzl9z+sHImEWv+vz7XmT95f/+3/+7wP814v+efDOV3/SoX3lHAA+6ODeUFfMfjOWMADgdk+eEKz0pF7aQdMAcOKLLjrcVMVX3xdWN29/GhYP7SvnP0cWfS8caSkfHZsPE9Fgnt02JNutQ0QYHB2dDz9/pKX8QjjuO9xUxd/66HdxTeCHZ3rojQObGQBcuNjfplkD3b19Y/6MrimSaKgSMmpGU5WevmE/swa6Oy73tQHA0Rdr2Mmv/6A1n9w9suQ7097Z9lM4FlTgTDrzZTu4StXVfpiI48rVcUDM5cmEksrFnHxfpTtU/3BFQzCQF/2bYVoNbH7zmItbSoMj40JSzmMyX5qDvriA7QdrIIpA+3cdsMpu0nXI8cV0MtKXCPZev+gCEM1S2NHPvWfP/hL+7FSr3+0p5RBEyhEN5JCKYr8XnASMT0xBNyzQGQeI8fjsGD39RMPk7se2bd5ZtTyoFYXftF6y37gx7NeUtJJOTFlAHDZLDuILU3j3+H5oOrD3yWbIztugaAzgnBKJuBLpGfQrS8wO4FZgV+c1IxaLgWVU0tMLEETCos4xMzEIv9cJXQcyagIwigDGwJgOAtHAwAhisQUjy0ORGERiELgG4iakkzo4MYAxcM5hAMi1WWG1yYCJIcMUaBkVRLdGeSU2995TLWzcUAzONJ7J6FBVBYIggMzmFbvdBV44Corg8vjhzC+EJEl8U1kJtgYrhCzgc/vvTwXKSib1paRFVRVORDAJAsw5FuTaJEhWM2SHB3mOAlhkNxwuLzeJsGwqWzf5TFNdKgtY5qHp6ZFf67Y/sAVadCaVY5YACDDb3Oi4NIjLnWMw2QthCBIsVhsUTU9tvXsjeq9+X1d75/KEs4LNOfcdf/+HthMnvwxOD0wmHaXr7ZItn2wuH2SnBzbZAbPJwpPx+VQuzcm7dgRCB57a1uBzUDRL4bfnI0RE0eaXd9W89mpjqHZnUI5Hh2l2dkZZUhOqpi2qSmpOmZ64Tuu9qlz/SEXo6MEHa3wOip46F1n7633eekV8ds8Wxjn37Wl63VVa+ej5oeEZ/82ZBETJjpJ1Rbij2D3Z/1trXUvLsblCK0XfOx0SX2kMsn9dX+d+7Kf6h8o4AIykuffjT8L20LU+w4AZd5VvEPY+XpWqLV327HR7DzXuDnD8r+ovkBehJ8i+y8YAAAAASUVORK5CYII=)"},warn:{color:"#C09853","background-color":"#FCF8E3","border-color":"#FBEED5","background-image":"url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAMAAAC6V+0/AAABJlBMVEXr6eb/2oD/wi7/xjr/0mP/ykf/tQD/vBj/3o7/uQ//vyL/twebhgD/4pzX1K3z8e349vK6tHCilCWbiQymn0jGworr6dXQza3HxcKkn1vWvV/5uRfk4dXZ1bD18+/52YebiAmyr5S9mhCzrWq5t6ufjRH54aLs0oS+qD751XqPhAybhwXsujG3sm+Zk0PTwG6Shg+PhhObhwOPgQL4zV2nlyrf27uLfgCPhRHu7OmLgAafkyiWkD3l49ibiAfTs0C+lgCniwD4sgDJxqOilzDWowWFfAH08uebig6qpFHBvH/aw26FfQTQzsvy8OyEfz20r3jAvaKbhgG9q0nc2LbZxXanoUu/u5WSggCtp1anpJKdmFz/zlX/1nGJiYmuq5Dx7+sAAADoPUZSAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfdBgUBGhh4aah5AAAAlklEQVQY02NgoBIIE8EUcwn1FkIXM1Tj5dDUQhPU502Mi7XXQxGz5uVIjGOJUUUW81HnYEyMi2HVcUOICQZzMMYmxrEyMylJwgUt5BljWRLjmJm4pI1hYp5SQLGYxDgmLnZOVxuooClIDKgXKMbN5ggV1ACLJcaBxNgcoiGCBiZwdWxOETBDrTyEFey0jYJ4eHjMGWgEAIpRFRCUt08qAAAAAElFTkSuQmCC)"}}}),e(function(){g(h.css).attr("id","core-notify"),e(document).on("click","."+r+"-hidable",function(t){e(this).trigger("notify-hide")}),e(document).on("notify-hide","."+r+"-wrapper",function(t){var n=e(this).data(r);n&&n.show(!1)})})}) -------------------------------------------------------------------------------- /public/extensions/XLSExtension/contents/main.css: -------------------------------------------------------------------------------- 1 | .toolbarXLSButton { 2 | background-image: url(img/excel.png); 3 | background-size: 16px; 4 | background-repeat: no-repeat; 5 | background-position: center; 6 | } -------------------------------------------------------------------------------- /public/extensions/XLSExtension/contents/main.js: -------------------------------------------------------------------------------- 1 | class XLSExtension extends Autodesk.Viewing.Extension { 2 | constructor(viewer, options) { 3 | super(viewer, options); 4 | this._group = null; 5 | this._button = null; 6 | } 7 | 8 | load() { 9 | return true; 10 | } 11 | 12 | unload() { 13 | // Clean our UI elements if we added any 14 | if (this._group) { 15 | this._group.removeControl(this._button); 16 | if (this._group.getNumberOfControls() === 0) { 17 | this.viewer.toolbar.removeControl(this._group); 18 | } 19 | } 20 | console.log('MyAwesomeExtensions has been unloaded'); 21 | return true; 22 | } 23 | 24 | onToolbarCreated() { 25 | // Button 1 26 | var button1 = new Autodesk.Viewing.UI.Button('toolbarXLS'); 27 | button1.onClick = function (e) { 28 | ApsXLS.downloadXLSX(fileName.replace(/\./g, '') + ".xlsx", statusCallback );/*Optional*/ 29 | }; 30 | button1.addClass('toolbarXLSButton'); 31 | button1.setToolTip('Export to .XLSX'); 32 | 33 | // SubToolbar 34 | this.subToolbar = new Autodesk.Viewing.UI.ControlGroup('myAppGroup1'); 35 | this.subToolbar.addControl(button1); 36 | 37 | this.viewer.toolbar.addControl(this.subToolbar); 38 | } 39 | 40 | } 41 | let statusCallback = function(completed, message) { 42 | $.notify(message, { className: "info", position:"bottom right" }); 43 | $('#downloadExcel').prop("disabled", !completed); 44 | } 45 | 46 | Autodesk.Viewing.theExtensionManager.registerExtension('XLSExtension', XLSExtension); 47 | -------------------------------------------------------------------------------- /public/extensions/XLSExtension/contents/modeldata.js: -------------------------------------------------------------------------------- 1 | 2 | // Model data in format for charts 3 | class ModelData { 4 | constructor(viewer) { 5 | this._modelData = {}; 6 | this._viewer = viewer; 7 | } 8 | 9 | init(callback) { 10 | var _this = this; 11 | 12 | _this.getAllLeafComponents(function (dbIds) { 13 | var count = dbIds.length; 14 | dbIds.forEach(function (dbId) { 15 | viewer.getProperties(dbId, function (props) { 16 | props.properties.forEach(function (prop) { 17 | if (!isNaN(prop.displayValue)) return; // let's not categorize properties that store numbers 18 | 19 | // some adjustments for revit: 20 | prop.displayValue = prop.displayValue.replace('Revit ', ''); // remove this Revit prefix 21 | if (prop.displayValue.indexOf('<') == 0) return; // skip categories that start with < 22 | 23 | // ok, now let's organize the data into this hash table 24 | if (_this._modelData[prop.displayName] == null) _this._modelData[prop.displayName] = {}; 25 | if (_this._modelData[prop.displayName][prop.displayValue] == null) _this._modelData[prop.displayName][prop.displayValue] = []; 26 | _this._modelData[prop.displayName][prop.displayValue].push(dbId); 27 | }) 28 | if ((--count) == 0) callback(); 29 | }); 30 | }) 31 | }) 32 | } 33 | 34 | getAllLeafComponents(callback) { 35 | // from https://learnforge.autodesk.io/#/viewer/extensions/panel?id=enumerate-leaf-nodes 36 | viewer.getObjectTree(function (tree) { 37 | var leaves = []; 38 | tree.enumNodeChildren(tree.getRootId(), function (dbId) { 39 | if (tree.getChildCount(dbId) === 0) { 40 | leaves.push(dbId); 41 | } 42 | }, true); 43 | callback(leaves); 44 | }); 45 | } 46 | 47 | hasProperty(propertyName){ 48 | return (this._modelData[propertyName] !== undefined); 49 | } 50 | 51 | getLabels(propertyName) { 52 | return Object.keys(this._modelData[propertyName]); 53 | } 54 | 55 | getCountInstances(propertyName) { 56 | return Object.keys(this._modelData[propertyName]).map(key => this._modelData[propertyName][key].length); 57 | } 58 | 59 | getIds(propertyName, propertyValue) { 60 | return this._modelData[propertyName][propertyValue]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /public/extensions/XLSExtension/extension.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/extensions/XLSExtension/extension.jpg -------------------------------------------------------------------------------- /public/extensions/extensionloader.css: -------------------------------------------------------------------------------- 1 | #extensionlist{padding: 10px 0px;} 2 | .tobegin{position: absolute;font-size: 20px;opacity: 0.5;top: 15%;left: 5%;} 3 | .extensions-heading{margin-top: 0;} 4 | .popover{min-width: 520px !important;max-width: 520px !important;} 5 | #editextensionconfig{height: 200px;} 6 | #learnmore{float: left;} 7 | #saveconfig{background: #5cb85c;color: white;} 8 | -------------------------------------------------------------------------------- /public/extensions/extensionloader.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | $(document).ready(function() { 20 | 21 | loadJSON(init); 22 | 23 | function init(config){ 24 | var Extensions = config.Extensions; 25 | var loaderconfig = {"initialload":false} 26 | Extensions.forEach(element => { 27 | let path = "extensions/"+element.name+"/contents/"; 28 | element.filestoload.cssfiles.forEach(ele => {loadjscssfile((path+ele), 'css')}); 29 | element.filestoload.jsfiles.forEach(ele => {loadjscssfile((path+ele), 'js')}); 30 | }); 31 | document.addEventListener('loadextension',function(e){ 32 | loaderconfig.Viewer = e.detail.viewer;loaderconfig.Viewer.loadExtension(e.detail.extension); 33 | e.detail.viewer.loadExtension(e.detail.extension); 34 | }) 35 | document.addEventListener('unloadextension',function(e){ 36 | e.detail.viewer.unloadExtension(e.detail.extension); 37 | }) 38 | document.addEventListener('viewerinstance',function(e){ 39 | loaderconfig.Viewer = e.detail.viewer; 40 | if (!loaderconfig.initialload) { 41 | loadStartupExtensions(); 42 | loaderconfig.initialload = true; 43 | } 44 | document.getElementById(config.ListConfig.ListId).style.display = 'block'; 45 | if(config.InbuiltExtensionsConfig && config.InbuiltExtensionsConfig.CreateList === "true") ListInbuiltExtensions(); 46 | if(config.ListConfig && config.ListConfig.CreateList === "true") CreateList(); 47 | }); 48 | 49 | function loadStartupExtensions(){ 50 | Extensions.forEach(element => { 51 | if (element.loadonstartup === "true") { 52 | loaderconfig.Viewer.loadExtension(element.name); 53 | } 54 | }); 55 | } 56 | 57 | function CreateList() { 58 | var list = document.getElementById(config.ListConfig.ListId); 59 | var ExtensionList = ''; 60 | let index = 0; 61 | Extensions.forEach(element => { 62 | if (element.includeinlist === "true") { 63 | let name = element.name; 64 | let checked = ''; 65 | let editoptions = ''; 66 | if(element.loadonstartup === 'true') checked = ' checked '; 67 | if(element.editoptions === 'true') editoptions = ' '; 68 | ExtensionList += ' '+editoptions+'
'; 69 | } 70 | index++; 71 | }); 72 | list.innerHTML = ExtensionList; 73 | var checkbox = document.getElementsByClassName('checkextension'); 74 | for (var i=0; i < checkbox.length; i++) { 75 | checkbox.item(i).onclick = togglecustomextension; 76 | let index = checkbox.item(i).attributes['data-index'].value; 77 | let element = Extensions[index]; 78 | let moredetails = ''; 79 | let gif = ''; 80 | if(element.bloglink) moredetails = 'Learn more'; 81 | if(element.gif) gif = '
Sample Image'; 82 | let contents = '

'+Extensions[index].description+'

'+moredetails+gif; 83 | $(checkbox.item(i).parentNode).next().popover({ 84 | html : true, 85 | container: 'body', 86 | boundary: 'viewport', 87 | title: Extensions[index].displayname, 88 | placement:'left', 89 | content : contents 90 | }); 91 | $("html").on("mouseup", function (e) { 92 | var l = $(e.target); 93 | if (l[0].className.indexOf("popover") == -1) { 94 | $(".popover").each(function () { 95 | $(this).popover("hide"); 96 | }); 97 | } 98 | }); 99 | } 100 | if (window.extension) { 101 | $("input:checkbox[value='"+window.extension+"']").click(); 102 | } 103 | // $('[data-toggle="popover"]').popover(); 104 | let editbuttons = document.getElementsByClassName('editoptions'); 105 | for (var i=0; i < editbuttons.length; i++) { 106 | let index = editbuttons.item(i).attributes['data-index'].value; 107 | editbuttons.item(i).onclick = editextensionconfig; 108 | } 109 | let editoptionindex; 110 | function editextensionconfig(e) { 111 | editoptionindex = parseInt( e.target.getAttribute('data-index') ); 112 | let element = Extensions[editoptionindex]; 113 | console.log(element.options); 114 | let options = JSON.stringify(element.options, undefined, 2); 115 | document.getElementById("editextensionconfig").value = options; 116 | document.getElementById("learnmore").setAttribute('href',element.bloglink); 117 | } 118 | document.getElementById("saveconfig").onclick = saveoptions; 119 | function saveoptions() { 120 | console.log(editoptionindex); 121 | Extensions[editoptionindex].options = JSON.parse(document.getElementById('editextensionconfig').value); 122 | loaderconfig.Viewer.unloadExtension(Extensions[editoptionindex].name); 123 | loaderconfig.Viewer.loadExtension(Extensions[editoptionindex].name,Extensions[editoptionindex].options); 124 | } 125 | function togglecustomextension(e) { 126 | console.log(e.target.value) 127 | if (e.target.checked) { 128 | loaderconfig.Viewer.loadExtension(e.target.value,Extensions[parseInt(this.dataset.index)].options) 129 | } else { 130 | loaderconfig.Viewer.unloadExtension(e.target.value) 131 | } 132 | } 133 | } 134 | 135 | function ListInbuiltExtensions() { 136 | let Extensions = config.InbuiltExtensions; 137 | let list = document.getElementById(config.InbuiltExtensionsConfig.ListId); 138 | let ExtensionList = ''; 139 | for (let index = 0; index < Extensions.length; index++) { 140 | let element = Extensions[index]; 141 | if (element.includeinlist !== "false") { 142 | let checked = ''; 143 | if(element.default === 'true') checked = ' checked '; 144 | ExtensionList += '
'; 145 | } 146 | 147 | }; 148 | list.innerHTML = ExtensionList; 149 | let checkbox = document.getElementsByClassName('checkextensionbuiltin'); 150 | for (var i=0; i < checkbox.length; i++) { 151 | checkbox.item(i).onclick = togglebuiltinextension; 152 | } 153 | function togglebuiltinextension(e) { 154 | console.log(e.target.value) 155 | if (e.target.checked) { 156 | loaderconfig.Viewer.loadExtension(e.target.value) 157 | } else { 158 | loaderconfig.Viewer.unloadExtension(e.target.value) 159 | } 160 | } 161 | } 162 | 163 | function loadjscssfile(filename, filetype){ 164 | if (filetype=="js"){ 165 | var fileref=document.createElement('script') 166 | fileref.setAttribute("type","text/javascript") 167 | fileref.setAttribute("src", filename) 168 | } 169 | else if (filetype=="css"){ 170 | var fileref=document.createElement("link") 171 | fileref.setAttribute("rel", "stylesheet") 172 | fileref.setAttribute("type", "text/css") 173 | fileref.setAttribute("href", filename) 174 | } 175 | if (typeof fileref!="undefined"); 176 | document.getElementsByTagName("head")[0].appendChild(fileref); 177 | } 178 | } 179 | 180 | function loadJSON(callback) { 181 | var xobj = new XMLHttpRequest(); 182 | xobj.overrideMimeType("application/json"); 183 | xobj.open('GET', 'extensions/config.json', true); 184 | xobj.onreadystatechange = function () { 185 | if (xobj.readyState == 4 && xobj.status == "200") { 186 | callback(JSON.parse(xobj.responseText)); 187 | } 188 | }; 189 | xobj.send(null); 190 | } 191 | 192 | }); -------------------------------------------------------------------------------- /public/img/GitHub_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/public/img/GitHub_Logo.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | APS Extensions 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 58 | 59 |
60 |
61 |
62 |
63 |
64 | Sample Models 65 | 66 |
67 |
68 | tree here 69 |
70 |
71 |
72 |
73 |
  Select a model to load viewer and extensions
74 |
75 |
76 |
77 | 78 |
79 |

Extensions

80 | 81 | 85 | 86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | 96 |
97 |
98 |
99 |
100 | 101 |
102 | 103 | 104 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /public/js/ApsTree.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | $(document).ready(function () { 20 | prepareAppBucketTree(); 21 | $('#refreshBuckets').click(function () { 22 | $('#appBuckets').jstree(true).refresh(); 23 | }); 24 | 25 | $('#createNewBucket').click(function () { 26 | createNewBucket(); 27 | }); 28 | 29 | $('#createBucketModal').on('shown.bs.modal', function () { 30 | $("#newBucketKey").focus(); 31 | }) 32 | 33 | $('#hiddenUploadField').change(function () { 34 | var node = $('#appBuckets').jstree(true).get_selected(true)[0]; 35 | var _this = this; 36 | if (_this.files.length == 0) return; 37 | var file = _this.files[0]; 38 | switch (node.type) { 39 | case 'bucket': 40 | var formData = new FormData(); 41 | formData.append('fileToUpload', file); 42 | formData.append('bucketKey', node.id); 43 | 44 | $.ajax({ 45 | url: '/api/models/upload', 46 | data: formData, 47 | processData: false, 48 | contentType: false, 49 | type: 'POST', 50 | success: function (data) { 51 | $('#appBuckets').jstree(true).refresh_node(node); 52 | _this.value = ''; 53 | } 54 | }); 55 | break; 56 | } 57 | }); 58 | }); 59 | 60 | var extensionloaded = false; 61 | function prepareAppBucketTree() { 62 | $('#appBuckets').jstree({ 63 | 'core': { 64 | 'themes': { "icons": true }, 65 | 'data': { 66 | "url": '/api/models/buckets', 67 | "dataType": "json", 68 | 'multiple': false, 69 | "data": function (node) { 70 | return { "id": node.id }; 71 | } 72 | } 73 | }, 74 | 'types': { 75 | 'default': { 76 | 'icon': 'glyphicon glyphicon-question-sign' 77 | }, 78 | '#': { 79 | 'icon': 'glyphicon glyphicon-cloud' 80 | }, 81 | 'bucket': { 82 | 'icon': 'glyphicon glyphicon-folder-open' 83 | }, 84 | 'object': { 85 | 'icon': 'glyphicon glyphicon-file' 86 | } 87 | }, 88 | "plugins": ["types", "state", "sort", "contextmenu"], 89 | contextmenu: { items: autodeskCustomMenu } 90 | }).on('loaded.jstree', function () { 91 | $('#appBuckets').jstree('open_all'); 92 | }).bind("activate_node.jstree", function (evt, data) { 93 | if (data != null && data.node != null && data.node.type == 'object') { 94 | $("#apsViewer").empty(); 95 | var urn = data.node.id; 96 | var filename = data.node.text 97 | document.getElementsByClassName('tobegin')[0].style.display = 'none'; 98 | getApsToken(function (access_token) { 99 | jQuery.ajax({ 100 | url: 'https://developer.api.autodesk.com/modelderivative/v2/designdata/' + urn + '/manifest', 101 | headers: { 'Authorization': 'Bearer ' + access_token }, 102 | success: function (res) { 103 | if (res.progress === 'success' || res.progress === 'complete') launchViewer(urn,filename); 104 | else $("#apsViewer").html('The translation job still running: ' + res.progress + '. Please try again in a moment.'); 105 | }, 106 | error: function (err) { 107 | var msgButton = 'This file is not translated yet!' 108 | $("#apsViewer").html(msgButton); 109 | } 110 | }); 111 | }) 112 | } 113 | }).on('ready.jstree', function () { 114 | if (!extensionloaded) { 115 | const queryString = window.location.search; 116 | const urlParams = new URLSearchParams(queryString); 117 | let extension = urlParams.get('extension'); 118 | if (extension) { 119 | document.getElementById('dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c2FtcGxlbW9kZWxzL09mZmljZS5ydnQ=_anchor').click(); 120 | window.extension = extension; 121 | } 122 | extensionloaded = true; 123 | } 124 | }); 125 | } 126 | 127 | function autodeskCustomMenu(autodeskNode) { 128 | var items; 129 | 130 | switch (autodeskNode.type) { 131 | case "bucket": 132 | if(autodeskNode.id === "samplemodels"){ 133 | alert('upload in the below transient bucket'); 134 | break; 135 | } 136 | items = { 137 | uploadFile: { 138 | label: "Upload file", 139 | action: function () { 140 | uploadFile(); 141 | }, 142 | icon: 'glyphicon glyphicon-cloud-upload' 143 | } 144 | }; 145 | break; 146 | case "object": 147 | items = { 148 | translateFile: { 149 | label: "Check status", 150 | action: function () { 151 | var treeNode = $('#appBuckets').jstree(true).get_selected(true)[0]; 152 | translateObject(treeNode); 153 | }, 154 | icon: 'glyphicon glyphicon-eye-open' 155 | } 156 | }; 157 | break; 158 | } 159 | 160 | return items; 161 | } 162 | 163 | function uploadFile() { 164 | $('#hiddenUploadField').click(); 165 | } 166 | -------------------------------------------------------------------------------- /public/js/ApsViewer.js: -------------------------------------------------------------------------------- 1 |  ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | var viewer; 20 | var fileName; 21 | 22 | function launchViewer(urn, name) { 23 | var options = { 24 | env: 'AutodeskProduction', 25 | getAccessToken: getApsToken 26 | }; 27 | 28 | fileName = name; 29 | 30 | Autodesk.Viewing.Initializer(options, () => { 31 | viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('apsViewer')); 32 | viewer.start(null, null, null, null, { 33 | webglInitParams: { 34 | useWebGL2: false // It's for Potree extension. If this is causing performance issue or any other problem, just use viewer.start(); 35 | } 36 | }); 37 | // viewer.start(); 38 | var documentId = 'urn:' + urn; 39 | Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure); 40 | }); 41 | } 42 | 43 | function onDocumentLoadSuccess(doc) { 44 | var viewables = doc.getRoot().getDefaultGeometry(); 45 | viewer.loadDocumentNode(doc, viewables).then(i => { 46 | // documented loaded, any action? 47 | var ViewerInstance = new CustomEvent("viewerinstance", {detail: {viewer: viewer}}); 48 | document.dispatchEvent(ViewerInstance); 49 | // var LoadExtensionEvent = new CustomEvent("loadextension", { 50 | // detail: { 51 | // extension: "Extension1", 52 | // viewer: viewer 53 | // } 54 | // }); 55 | // document.dispatchEvent(LoadExtensionEvent); 56 | }); 57 | } 58 | 59 | function onDocumentLoadFailure(viewerErrorCode) { 60 | console.error('onDocumentLoadFailure() - errorCode:' + viewerErrorCode); 61 | } 62 | 63 | function getApsToken(callback) { 64 | fetch('/api/auth/token').then(res => { 65 | res.json().then(data => { 66 | callback(data.access_token, data.expires_in); 67 | }); 68 | }); 69 | } -------------------------------------------------------------------------------- /routes/auth.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | const express = require('express'); 20 | const { getPublicToken } = require('../services/aps.js'); 21 | 22 | let router = express.Router(); 23 | 24 | router.get('/api/auth/token', async function (req, res, next) { 25 | try { 26 | res.json(await getPublicToken()); 27 | } catch (err) { 28 | next(err); 29 | } 30 | }); 31 | 32 | module.exports = router; 33 | -------------------------------------------------------------------------------- /routes/models.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | const express = require('express'); 20 | const formidable = require('express-formidable'); 21 | const { listBuckets, listObjects, uploadObject, translateObject, getManifest, urnify } = require('../services/aps.js'); 22 | 23 | let router = express.Router(); 24 | 25 | router.get('/api/models/buckets', async function (req, res, next) { 26 | try { 27 | const bucket_name = req.query.id; 28 | if (!bucket_name || bucket_name === '#') { 29 | const buckets = await listBuckets(); 30 | res.json(buckets.map((bucket) => { 31 | return { 32 | id: bucket.bucketKey, 33 | text: bucket.bucketKey, 34 | type: 'bucket', 35 | children: true 36 | }; 37 | })); 38 | 39 | } else { 40 | const objects = await listObjects(bucket_name); 41 | res.json(objects.map((object) => { 42 | return { 43 | id: Buffer.from(object.objectId).toString('base64'), 44 | text: object.objectKey, 45 | type: 'object', 46 | children: false 47 | }; 48 | })); 49 | } 50 | } catch (err) { 51 | next(err); 52 | } 53 | }); 54 | 55 | router.post('/api/models/upload', formidable({ maxFileSize: Infinity }), async function (req, res, next) { 56 | const file = req.files.fileToUpload; 57 | if (!file) { 58 | res.status(400).send('The required field ("model-file") is missing.'); 59 | return; 60 | } 61 | try { 62 | const obj = await uploadObject(file.name, file.path, req.fields.bucketKey); 63 | await translateObject(urnify(obj.objectId), req.fields['model-zip-entrypoint']); 64 | res.json({ 65 | name: obj.objectKey, 66 | urn: urnify(obj.objectId) 67 | }); 68 | } catch (err) { 69 | next(err); 70 | } 71 | }); 72 | 73 | module.exports = router; 74 | -------------------------------------------------------------------------------- /services/aps.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | const { SdkManagerBuilder } = require('@aps_sdk/autodesk-sdkmanager'); 20 | const { AuthenticationClient, Scopes } = require('@aps_sdk/authentication'); 21 | const { OssClient, CreateBucketsPayloadPolicyKeyEnum, CreateBucketXAdsRegionEnum } = require('@aps_sdk/oss'); 22 | const { ModelDerivativeClient, View, Type } = require('@aps_sdk/model-derivative'); 23 | const { APS_CLIENT_ID, APS_CLIENT_SECRET, APS_BUCKET } = require('../config.js'); 24 | 25 | const sdk = SdkManagerBuilder.create().build(); 26 | const authenticationClient = new AuthenticationClient(sdk); 27 | const ossClient = new OssClient(sdk); 28 | const modelDerivativeClient = new ModelDerivativeClient(sdk); 29 | 30 | const service = module.exports = {}; 31 | 32 | service.getInternalToken = async () => { 33 | const credentials = await authenticationClient.getTwoLeggedToken(APS_CLIENT_ID, APS_CLIENT_SECRET, [ 34 | Scopes.DataRead, 35 | Scopes.DataCreate, 36 | Scopes.DataWrite, 37 | Scopes.BucketCreate, 38 | Scopes.BucketRead 39 | ]); 40 | return credentials; 41 | }; 42 | 43 | service.getPublicToken = async () => { 44 | const credentials = await authenticationClient.getTwoLeggedToken(APS_CLIENT_ID, APS_CLIENT_SECRET, [ 45 | Scopes.DataRead 46 | ]); 47 | return credentials; 48 | }; 49 | 50 | service.ensureBucketExists = async (bucketKey) => { 51 | const { access_token } = await service.getInternalToken(); 52 | try { 53 | await ossClient.getBucketDetails(access_token, bucketKey); 54 | } catch (err) { 55 | if (err.axiosError.response.status === 404) { 56 | await ossClient.createBucket(access_token, CreateBucketXAdsRegionEnum.Us, { 57 | bucketKey: bucketKey, 58 | policyKey: CreateBucketsPayloadPolicyKeyEnum.Temporary 59 | }); 60 | } else { 61 | throw err; 62 | } 63 | } 64 | }; 65 | 66 | service.listObjects = async (bucket) => { 67 | const { access_token } = await service.getInternalToken(); 68 | let resp = await ossClient.getObjects(access_token, bucket, { limit: 64 }); 69 | let objects = resp.items; 70 | return objects; 71 | }; 72 | 73 | service.listBuckets = async () => { 74 | const { access_token } = await service.getInternalToken(); 75 | let resp = await ossClient.getBuckets(access_token); 76 | let buckets = resp.items; 77 | return buckets; 78 | }; 79 | 80 | service.uploadObject = async (objectName, filePath, bucket) => { 81 | await service.ensureBucketExists(bucket); 82 | const { access_token } = await service.getInternalToken(); 83 | const obj = await ossClient.upload(bucket, objectName, filePath, access_token); 84 | return obj; 85 | }; 86 | 87 | service.translateObject = async (urn, rootFilename) => { 88 | const { access_token } = await service.getInternalToken(); 89 | const job = await modelDerivativeClient.startJob(access_token, { 90 | input: { 91 | urn, 92 | compressedUrn: !!rootFilename, 93 | rootFilename 94 | }, 95 | output: { 96 | formats: [{ 97 | views: [View._2d, View._3d], 98 | type: Type.Svf 99 | }] 100 | } 101 | }); 102 | return job.result; 103 | }; 104 | 105 | service.getManifest = async (urn) => { 106 | const { access_token } = await service.getInternalToken(); 107 | try { 108 | const manifest = await modelDerivativeClient.getManifest(access_token, urn); 109 | return manifest; 110 | } catch (err) { 111 | if (err.axiosError.response.status === 404) { 112 | return null; 113 | } else { 114 | throw err; 115 | } 116 | } 117 | }; 118 | 119 | service.urnify = (id) => Buffer.from(id).toString('base64').replace(/=/g, ''); -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by APS Partner Development 4 | // 5 | // Permission to use, copy, modify, and distribute this software in 6 | // object code form for any purpose and without fee is hereby granted, 7 | // provided that the above copyright notice appears in all copies and 8 | // that both that copyright notice and the limited warranty and 9 | // restricted rights notice below appear in all supporting 10 | // documentation. 11 | // 12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 16 | // UNINTERRUPTED OR ERROR FREE. 17 | ///////////////////////////////////////////////////////////////////// 18 | 19 | const path = require('path'); 20 | const express = require('express'); 21 | const fs = require('fs'); 22 | const { PORT } = require('./config.js'); 23 | 24 | let masterconfigpath = './public/extensions/config.json'; 25 | let extensionsconfig = require(masterconfigpath); 26 | let source = './public/extensions'; 27 | let extensions = []; 28 | fs.readdirSync(source, { withFileTypes: true }) 29 | .filter(dirent => dirent.isDirectory()) 30 | .forEach(folder => { 31 | let econfig = require(source+'/'+folder.name+'/config.json') 32 | extensions.push(econfig); 33 | }); 34 | extensionsconfig.Extensions = extensions; 35 | fs.writeFileSync(masterconfigpath, JSON.stringify(extensionsconfig), function(err) { 36 | if (err) throw err; 37 | }); 38 | 39 | let app = express(); 40 | app.use(express.static('public')); 41 | app.use(require('./routes/auth.js')); 42 | app.use(require('./routes/models.js')); 43 | app.listen(PORT, function () { console.log(`Server listening on port ${PORT}...`); }); 44 | -------------------------------------------------------------------------------- /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/autodesk-platform-services/aps-extensions/f597728058e997d862c166b805b55e98133871eb/thumbnail.png --------------------------------------------------------------------------------