├── help ├── docmap.png ├── main.png ├── table.png ├── tooltip.png ├── workflow.png └── relationship.png ├── thumbnail.png ├── www ├── img │ ├── Step1.png │ ├── Step2.png │ ├── Step3.png │ ├── bim360.png │ ├── favicon.ico │ ├── autodesk-logo.png │ ├── autodesk_text.png │ └── autodesk-forge.png ├── js │ ├── utility.js │ ├── oauth.js │ ├── nav.help.js │ ├── mc.clash.raw.view.js │ ├── ForgeViewer.js │ ├── dm.projects.js │ └── mc.ms.set.js ├── panelPage.html ├── helpDiv │ ├── aboutHelp.html │ └── configHelp.html ├── css │ └── main.css ├── index.js ├── index.html └── libs │ ├── MultipleModelUtil.js │ └── bootstrap-notify.min.js ├── package.json ├── LICENSE ├── app.json ├── .gitignore ├── server ├── services │ ├── oauth.services.js │ ├── userSession.js │ ├── dm.projects.services.js │ ├── mc.modelset.services.js │ ├── mc.clash.services.js │ └── dm.hubs.services.js ├── endpoints │ ├── mc.clash.endpoints.js │ ├── dm.endpoints.js │ ├── oauth.endpoints.js │ └── mc.modelset.endpoints.js ├── config.js ├── utility.js └── analyze.js ├── start.js └── README.md /help/docmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/help/docmap.png -------------------------------------------------------------------------------- /help/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/help/main.png -------------------------------------------------------------------------------- /help/table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/help/table.png -------------------------------------------------------------------------------- /thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/thumbnail.png -------------------------------------------------------------------------------- /help/tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/help/tooltip.png -------------------------------------------------------------------------------- /help/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/help/workflow.png -------------------------------------------------------------------------------- /www/img/Step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/www/img/Step1.png -------------------------------------------------------------------------------- /www/img/Step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/www/img/Step2.png -------------------------------------------------------------------------------- /www/img/Step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/www/img/Step3.png -------------------------------------------------------------------------------- /www/img/bim360.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/www/img/bim360.png -------------------------------------------------------------------------------- /www/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/www/img/favicon.ico -------------------------------------------------------------------------------- /help/relationship.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/help/relationship.png -------------------------------------------------------------------------------- /www/img/autodesk-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/www/img/autodesk-logo.png -------------------------------------------------------------------------------- /www/img/autodesk_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/www/img/autodesk_text.png -------------------------------------------------------------------------------- /www/img/autodesk-forge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk-Forge/forge-bim360-clashview/HEAD/www/img/autodesk-forge.png -------------------------------------------------------------------------------- /www/js/utility.js: -------------------------------------------------------------------------------- 1 | class Utility { 2 | constructor() { 3 | 4 | } 5 | 6 | successMessage(title) { 7 | $.notify( 8 | { 9 | icon: 'fa fa-bell-o', 10 | title: title, 11 | message:'' 12 | }, 13 | { 14 | type:'info' 15 | } 16 | ); 17 | } 18 | 19 | failMessage(title) { 20 | $.notify( 21 | { 22 | icon: 'fa fa-bell-o', 23 | title: title, 24 | message:'' 25 | }, 26 | { 27 | type:'danger' 28 | } 29 | ); 30 | } 31 | 32 | checkTimeout(st,end){ 33 | return end - st > 5 * 60 * 1000 // 5 minutes 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bim360-mcapi-clash-basic-view", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "start.js", 6 | "scripts": { 7 | "start": "NODE_ENV=production node start.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/xiaodongliang/bim360-mcapi-node-clashview-basic.sample" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "" 17 | }, 18 | "homepage": "", 19 | "dependencies": { 20 | "forge-bim360-modelcoordination-clash": "^3.3.27", 21 | "forge-bim360-modelcoordination-modelset": "^3.0.65", 22 | "cookie-parser": "^1.4.4", 23 | "crypto": "^1.0.1", 24 | "express": "^4.17.1", 25 | "express-session": "^1.16.2", 26 | "forge-apis": "^0.4.5", 27 | "fs": "0.0.1-security", 28 | "jspdf": "^1.5.3", 29 | "mkdirp": "^0.5.1", 30 | "node-fetch": "^2.6.0", 31 | "pako": "^1.0.10", 32 | "powerbi-client": "^2.8.0", 33 | "rimraf": "^3.0.0", 34 | "route-async": "^1.0.5", 35 | "superagent": "^5.1.0", 36 | "zlib": "^1.0.5" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 xiaodongliang 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 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BIM 360 Model Coordination API Sample - Clash View Basic Application", 3 | "description": "This repository demonstrates basic viewing of clash raw data by Model Coordination API. It lists all clash instances data, and allows the user to click one instance to highlight within Forge Viewer.", 4 | "repository": "https://github.com/Autodesk-Forge/forge-bim360-clashview", 5 | "logo": "https://avatars0.githubusercontent.com/u/8017462?v=3&s=200", 6 | "keywords": [ 7 | "express", 8 | "framework", 9 | "autodesk", 10 | "forge", 11 | "template", 12 | "bim360appstore", 13 | "modelcoordination" 14 | ], 15 | "env": { 16 | "FORGE_CLIENT_ID": { 17 | "description": "Forge Client ID. Ensure this ID also provisions with BIM 360 account" 18 | }, 19 | "FORGE_CLIENT_SECRET": { 20 | "description": "Forge Client Secret" 21 | }, 22 | "FORGE_CALLBACK_URL": { 23 | "description": "Callback URL of your Forge app, required for 3-legged OAuth", 24 | "value": "https://<>.herokuapp.com/api/forge/callback/oauth" 25 | } 26 | }, 27 | "website": "", 28 | "success_url": "/" 29 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | /ClashData 65 | /Status 66 | .DS_Store 67 | -------------------------------------------------------------------------------- /www/panelPage.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

Model Sets 5 | 7 |


8 |
9 |
10 |
Select one BIM hub and one project
11 | 12 |
13 |
14 |
15 | 16 |
17 |
18 |
19 |

Clash Raw Data 20 | 22 |

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
Inst IdDistaceStatusL Model IdR Model IdL View IdR View Id
39 |
40 |
41 |
42 | 43 |
-------------------------------------------------------------------------------- /server/services/oauth.services.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | // forge oAuth package 22 | var forgeSDK = require('forge-apis'); 23 | var config = require('../config'); 24 | 25 | function getAdminTwoLeggedToken() { 26 | 27 | return new Promise(function(resolve,reject) { 28 | 29 | var forgeOAuth = new forgeSDK.AuthClientTwoLegged( 30 | config.credentials.client_id, 31 | config.credentials.client_secret, 32 | config.scope.scopeInternal); 33 | 34 | forgeOAuth.authenticate() 35 | .then(function (twoleggedcredentials) { 36 | 37 | console.log('get admin credentials succeeded!'); 38 | 39 | resolve({oAuth:forgeOAuth, 40 | credentials:twoleggedcredentials}); 41 | }) 42 | .catch(function (error) { 43 | console.log('get admin credentials failed!'); 44 | reject({error:error}); 45 | }); 46 | }); 47 | } 48 | 49 | module.exports = { 50 | getAdminTwoLeggedToken:getAdminTwoLeggedToken 51 | }; 52 | -------------------------------------------------------------------------------- /www/helpDiv/aboutHelp.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /www/js/oauth.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | class oAuth{ 20 | constructor() { 21 | } 22 | 23 | async forgeSignIn() { 24 | $.ajax({ 25 | url: '/oauth/url', 26 | success: function (rootUrl) { 27 | location.href = rootUrl 28 | } 29 | }); 30 | } 31 | 32 | async forgeLogoff() { 33 | $.ajax({ 34 | url: '/oauth/logoff', 35 | success: function (oauthUrl) { 36 | location.href = oauthUrl; 37 | } 38 | }) 39 | } 40 | 41 | getForgeToken() { 42 | var token = ''; 43 | $.ajax({ 44 | url: '/oauth/publictoken', 45 | success: function (res) { 46 | token = res; 47 | }, 48 | async: false // this request must be synchronous for the Forge Viewer 49 | }); 50 | return token; 51 | } 52 | 53 | async getForgeUserProfile() { 54 | return new Promise((resolve, reject) => { 55 | jQuery.ajax({ 56 | url: '/dm/user/profile', 57 | success: function (profile) { 58 | resolve(profile); 59 | } 60 | }) 61 | }) 62 | } 63 | 64 | } 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /server/endpoints/mc.clash.endpoints.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | var express = require('express'); 22 | var router = express.Router(); 23 | 24 | const UserSession = require('../services/userSession'); 25 | const analyze = require('../analyze'); 26 | 27 | router.get('/mc/clash/getRawClashData/:mc_container_id/:ms_id/:ms_v_id', async (req, res, next) => { 28 | 29 | try { 30 | const userSession = new UserSession(req.session) 31 | if (!userSession.isAuthorized()) { 32 | console.log('no valid authorization!') 33 | res.status(401).end('Please login first') 34 | return 35 | } 36 | 37 | const mc_container_id = req.params['mc_container_id'] 38 | const ms_id = req.params['ms_id'] 39 | const ms_v_id = req.params['ms_v_id'] 40 | const clashData = analyze.getRawClashData(mc_container_id,ms_id,ms_v_id) 41 | if(!clashData) 42 | res.status(500).end('raw clash data is null') 43 | else 44 | res.send(clashData) 45 | 46 | } catch(e) { 47 | // here goes out error handler 48 | console.log('getRawClashData failed: '+ e.message) 49 | res.status(500).end() 50 | } 51 | }); 52 | 53 | module.exports = router 54 | 55 | 56 | -------------------------------------------------------------------------------- /server/config.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | // Autodesk Forge configuration 22 | module.exports = { 23 | // set environment variables or hard-code here 24 | credentials: { 25 | client_id: process.env.FORGE_CLIENT_ID || '', 26 | client_secret: process.env.FORGE_CLIENT_SECRET || '' 27 | }, 28 | //ensure the callback url is same to what has been registered with the Forge app 29 | callbackURL: process.env.FORGE_CALLBACK_URL || 'http://localhost:3000/api/forge/callback/oauth', 30 | 31 | // Required scopes for your application on server-side 32 | scopeInternal: ['bucket:create', 'bucket:read', 'data:read', 'data:create', 'data:write'], 33 | // Required scope of the token sent to the client 34 | scopePublic: ['viewables:read'], 35 | 36 | //some endpoints of BIM 360. No SDK at this moment.. 37 | hqv1: { 38 | basedUrl: 'https://developer.api.autodesk.com', 39 | httpHeaders: function (access_token) { 40 | return { 41 | Authorization: 'Bearer ' + access_token, 42 | 'Content-Type': 'application/json' 43 | } 44 | }, 45 | getUserProfileAtMe: function () { 46 | return this.basedUrl + '/userprofile/v1/users/@me' 47 | } 48 | } 49 | }; -------------------------------------------------------------------------------- /start.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | const express = require('express'); 21 | const app = express(); 22 | const server = require('http').Server(app); 23 | 24 | const cookieParser = require('cookie-parser'); 25 | const session = require('express-session'); 26 | 27 | const oauth = require('./server/endpoints/oauth.endpoints'); 28 | const dm = require('./server/endpoints/dm.endpoints.js') 29 | const mc_modelsets = require('./server/endpoints/mc.modelset.endpoints'); 30 | const mc_clash = require('./server/endpoints/mc.clash.endpoints'); 31 | 32 | 33 | app.use(cookieParser()); 34 | app.set('trust proxy', 1) // trust first proxy - HTTPS on Heroku 35 | app.use(session({ 36 | secret: 'autodeskforge', 37 | cookie: { 38 | httpOnly: true, 39 | secure: false, 40 | maxAge: 1000 * 60 * 60 // 1 hours to expire the session and avoid memory leak 41 | }, 42 | resave: false, 43 | saveUninitialized: true 44 | })); 45 | 46 | app.use('/', express.static(__dirname+ '/www') ); 47 | app.use('/', dm) 48 | app.use('/', oauth); 49 | app.use('/', mc_modelsets); 50 | app.use('/', mc_clash); 51 | 52 | app.set('port', process.env.PORT || 3000); 53 | 54 | server.listen(app.get('port'), function() { 55 | console.log('Server listening on port ' + server.address().port); 56 | }); -------------------------------------------------------------------------------- /www/css/main.css: -------------------------------------------------------------------------------- 1 | html, body{ 2 | margin:0px; 3 | padding:0px; 4 | } 5 | 6 | 7 | .dropdown-menu{ 8 | overflow: auto; 9 | } 10 | 11 | .dropdown-height { 12 | height:400px; 13 | } 14 | 15 | .tooltip-inner { 16 | background-color: #a7cc00 !important; 17 | } 18 | 19 | .popover{ 20 | background-color: #a7cc00 !important; 21 | } 22 | .tooltip.bs-tooltip-right .arrow:before { 23 | border-right-color: #a7cc00 !important; 24 | } 25 | .tooltip.bs-tooltip-left .arrow:before { 26 | border-right-color: #a7cc00 !important; 27 | } 28 | .tooltip.bs-tooltip-bottom .arrow:before { 29 | border-right-color: #a7cc00 !important; 30 | } 31 | .tooltip.bs-tooltip-top .arrow:before { 32 | border-right-color: #2500cc !important; 33 | } 34 | 35 | 36 | .my-custom-scrollbar { 37 | position: relative; 38 | height: 200px; 39 | overflow: auto; 40 | } 41 | 42 | .table-wrapper-scroll-y { 43 | display: block; 44 | } 45 | .table-hover { 46 | background: #0b1927; 47 | } 48 | html, body{ 49 | min-height: 100%; 50 | height: 100%; 51 | } 52 | 53 | .fill{ 54 | height: calc(100vh - 100px); 55 | } 56 | 57 | body { 58 | padding-top: 30px; /* space for the top nav bar */ 59 | margin-right: 20px; 60 | } 61 | 62 | #userHubs { 63 | overflow: auto; 64 | width: 100%; 65 | height: calc(100vh - 150px); 66 | } 67 | 68 | #forgeViewer { 69 | width: 95%; 70 | height: 90% !important; 71 | display:block; 72 | position: absolute; 73 | 74 | } 75 | 76 | #myTabContent{ 77 | display:block; 78 | height: 500px; 79 | } 80 | #modelsetList { 81 | height: 200px; 82 | border:block; 83 | } 84 | 85 | 86 | .adsk-viewing-viewer.dark-theme .adsk-button.active { 87 | color: #EBB30B; 88 | } 89 | .adsk-viewing-viewer.dark-theme .adsk-button:hover { 90 | border: 1px solid #EBB30B; 91 | } 92 | 93 | .list-group{ 94 | max-height: 200px; 95 | margin-bottom: 10px; 96 | overflow:scroll; 97 | -webkit-overflow-scrolling: touch; 98 | } 99 | 100 | .table { 101 | display:block; 102 | height: 530px; 103 | overflow: auto; 104 | text-align: center; 105 | font-size: 14px; 106 | } 107 | -------------------------------------------------------------------------------- /server/services/userSession.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | //manage user 3-legged token 22 | 23 | function UserSession(session) { 24 | this._session = session; 25 | } 26 | 27 | UserSession.prototype.getUserServerOAuth = function () { 28 | return this._session.userServerOAuth; 29 | }; 30 | 31 | UserSession.prototype.setUserServerOAuth = function (o) { 32 | this._session.userServerOAuth = o; 33 | }; 34 | 35 | UserSession.prototype.getUserClientOAuth = function () { 36 | return this._session.userClientOAuth ; 37 | }; 38 | 39 | UserSession.prototype.setUserClientOAuth= function (o) { 40 | this._session.userClientOAuth = o; 41 | }; 42 | 43 | UserSession.prototype.getUserServerCredentials = function () { 44 | return this._session.userServerCredentials; 45 | }; 46 | 47 | UserSession.prototype.setUserServerCredentials = function (c) { 48 | this._session.userServerCredentials = c; 49 | }; 50 | 51 | UserSession.prototype.getUserClientCredentials = function () { 52 | return this._session.userClientCredentials ; 53 | }; 54 | 55 | UserSession.prototype.setUserClientCredentials = function (c) { 56 | this._session.userClientCredentials = c; 57 | }; 58 | 59 | UserSession.prototype.isAuthorized = function () { 60 | // !! converts value into boolean 61 | return (!!this._session.userServerCredentials); 62 | }; 63 | 64 | 65 | module.exports = UserSession; 66 | -------------------------------------------------------------------------------- /www/js/nav.help.js: -------------------------------------------------------------------------------- 1 | class NavHelp { 2 | 3 | constructor() { 4 | 5 | } 6 | 7 | init(){ 8 | 9 | $('#aboutHelp').click((evt)=>{ 10 | if(document.getElementsByName('aboutHelpDialog').length>0) 11 | $('#aboutHelpDialog').modal('show'); 12 | else 13 | this.createHelpAndShow('aboutHelp'); 14 | }); 15 | 16 | $('#configHelp').click((evt)=>{ 17 | if(document.getElementsByName('configHelpDialog').length>0) 18 | $('#configHelpDialog').modal('show'); 19 | else 20 | this.createHelpAndShow('configHelp'); 21 | }); 22 | 23 | $('#basicHelp').click((evt)=>{ 24 | if(document.getElementsByName('basicHelpDialog').length>0) 25 | $('#basicHelpDialog').modal('show'); 26 | else 27 | this.createHelpAndShow('basicHelp'); 28 | }); 29 | 30 | $('#exportHelp').click((evt)=>{ 31 | if(document.getElementsByName('exportHelpDialog').length>0) 32 | $('#exportHelpDialog').modal('show'); 33 | else 34 | this.createHelpAndShow('exportHelp'); 35 | }); 36 | 37 | $('#dashboardHelp').click((evt)=>{ 38 | if(document.getElementsByName('dashboardHelpDialog').length>0) 39 | $('#dashboardHelpDialog').modal('show'); 40 | else 41 | this.createHelpAndShow('dashboardHelp'); 42 | }); 43 | 44 | $('#integrationHelp').click((evt)=>{ 45 | if(document.getElementsByName('integrationHelpDialog')>0) 46 | $('#integrationHelpDialog').modal('show'); 47 | else 48 | this.createHelpAndShow('integrationHelp'); 49 | }); 50 | } 51 | 52 | createHelpAndShow(helpName){ 53 | 54 | $.ajax({ 55 | url: 'helpDiv/'+helpName+'.html', 56 | success: function(data) { 57 | var tempDiv = document.createElement('div'); 58 | tempDiv.innerHTML = data; 59 | document.body.appendChild(tempDiv); 60 | 61 | if(helpName == 'configHelp'){ 62 | $.getJSON("/oauth/clientid", function (res) { 63 | $("#ClientID").val(res.ForgeClientId); 64 | $('#'+helpName+'Dialog').modal('show'); 65 | }); 66 | $("#provisionAccountSave").click(function () { 67 | $('#configHelpDialog').modal('toggle'); 68 | }); 69 | }else 70 | $('#'+helpName+'Dialog').modal('show'); 71 | } 72 | } ); 73 | } 74 | } 75 | 76 | 77 | -------------------------------------------------------------------------------- /www/js/mc.clash.raw.view.js: -------------------------------------------------------------------------------- 1 | class ClashRawView { 2 | 3 | constructor() { 4 | this._clashJsonObj = null 5 | this._clashInsJsonObj = null 6 | } 7 | 8 | async produceClashRawTable(mc_container_id, ms_id, ms_v_id) { 9 | 10 | try { 11 | $('#clashviewSpinner').css({ display: "block" }) 12 | $('#forgeSpinner').css({ display: "block" }) 13 | await this.getRawData(mc_container_id, ms_id, ms_v_id) 14 | 15 | $("#clashRawTable tbody").empty() 16 | for (let index in this._clashJsonObj.clashes) { 17 | var eachItem = this._clashJsonObj.clashes[index]; 18 | 19 | var ins = this._clashInsJsonObj.instances.filter( 20 | function (data) { return data.cid == eachItem.id } 21 | ); 22 | 23 | var tr = document.createElement('tr') 24 | tr.id = eachItem.id 25 | var th = document.createElement('th') 26 | th.innerHTML = eachItem.id 27 | tr.append(th) 28 | 29 | const tdArray = [eachItem.dist, eachItem.status, ins[0].ldid, ins[0].rdid, ins[0].lvid, ins[0].rvid] 30 | 31 | for (let index in tdArray) { 32 | var td = document.createElement('td') 33 | var a = document.createElement('a') 34 | a.innerHTML = tdArray[index] 35 | //prereserved for custom tooltip if needed. 36 | //a.setAttribute('data-poload', '') 37 | td.append(a) 38 | tr.append(td) 39 | } 40 | $("#clashRawTable tbody").append(tr) 41 | } 42 | global_Utility.successMessage('Produce ClashRawTable Succeeded!') 43 | 44 | $('#clashviewSpinner').css({ display: "none" }) 45 | $('#forgeSpinner').css({ display: "none" }) 46 | 47 | return true 48 | } 49 | catch(e){ 50 | console.log('Produce ClashRawTable Failed!! ' + e ) 51 | global_Utility.failMessage('Produce ClashRawTable Failed!') 52 | 53 | $('#clashviewSpinner').css({ display: "none" }) 54 | $('#forgeSpinner').css({ display: "none" }) 55 | return false 56 | } 57 | } 58 | 59 | getRawData(mc_container_id, ms_id, ms_v_id) { 60 | var _this = this 61 | return new Promise((resolve, reject) => { 62 | $.ajax({ 63 | url: '/mc/clash/getRawClashData/' + mc_container_id + '/' + ms_id + '/' + ms_v_id, 64 | type: 'GET', 65 | success: (data) => { 66 | //decompressed the buffer string 67 | const depressedData = new TextDecoder("utf-8").decode(pako.inflate(data)) 68 | const clashData = JSON.parse(depressedData) 69 | this._clashInsJsonObj = clashData.clashInsJsonObj 70 | this._clashJsonObj = clashData.clashJsonObj 71 | resolve(true) 72 | }, error: (error) => { 73 | reject(null) 74 | } 75 | }); 76 | }) 77 | } 78 | } -------------------------------------------------------------------------------- /server/services/dm.projects.services.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | var forgeSDK = require('forge-apis'); 22 | 23 | async function getProjects(input) { 24 | 25 | return new Promise((resolve, reject) => { 26 | 27 | var projectsAPI = new forgeSDK.ProjectsApi();; 28 | 29 | projectsAPI.getHubProjects(input.hubId, {}, 30 | input.oAuth,input.credentials) 31 | .then( (response) =>{ 32 | 33 | console.log('get projects succeeded!'); 34 | var projects= []; 35 | response.body.data.forEach(function (project) { 36 | var projectType = 'projects'; 37 | switch (project.attributes.extension.type) { 38 | //case 'projects:autodesk.core:Project': 39 | // projectType = 'a360projects'; 40 | // break; 41 | case 'projects:autodesk.bim360:Project': 42 | projectType = 'bim360projects'; 43 | break; 44 | } 45 | projects.push({id:project.id,name:project.attributes.name,type:'bim360projects'}) 46 | }); 47 | resolve(projects); 48 | }) 49 | .catch(function (error) { 50 | console.log('get projects failed!'); 51 | reject({error:error}); 52 | }); 53 | }); 54 | } 55 | 56 | async function getProject(input) { 57 | 58 | return new Promise((resolve, reject) => { 59 | 60 | var projects = new forgeSDK.ProjectsApi(); 61 | projects.getProject(input.hubId, input.projectId, 62 | input.oAuth,input.credentials) 63 | .then(function (response) { 64 | 65 | console.log('get one project succeeded!'); 66 | const data = response.body.data 67 | resolve({id:data.id, 68 | name:data.attributes.name, 69 | type:data.attributes.type, 70 | rootFolder: data.relationships.rootFolder.data.id, 71 | containerId:data.relationships.issues.data.id}); 72 | }) 73 | .catch(function (error) { 74 | console.log('get one project failed!'); 75 | reject({error:error}); 76 | }); 77 | }); 78 | } 79 | 80 | 81 | module.exports = { 82 | getProjects:getProjects, 83 | getProject:getProject 84 | } 85 | 86 | 87 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /server/services/mc.modelset.services.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | const msclient = require("forge-bim360-modelcoordination-modelset") 22 | 23 | module.exports = { 24 | getModelSets:getModelSets, 25 | getModelSet:getModelSet, 26 | getModelSetVersions:getModelSetVersions, 27 | getModelSetVersion:getModelSetVersion 28 | } 29 | 30 | async function getModelSets(input) { 31 | msclient.ApiClient.instance.authentications["oauth2AuthCode"].accessToken = input.credentials.access_token; 32 | return new Promise((resolve, reject) => { 33 | const modelsetApi = new msclient.ModelSetApi() 34 | modelsetApi.getModelSets(input.mc_container_id) 35 | .then(res=>{ 36 | resolve(res) 37 | }) 38 | .catch(ex =>{ 39 | console.log(ex) 40 | reject(null) 41 | }) 42 | }) 43 | } 44 | 45 | async function getModelSet(input) { 46 | msclient.ApiClient.instance.authentications["oauth2AuthCode"].accessToken = input.credentials.access_token; 47 | return new Promise((resolve, reject) => { 48 | const modelsetApi = new msclient.ModelSetApi() 49 | modelsetApi.getModelSet(input.mc_container_id,input.ms_id) 50 | .then(res=>{ 51 | resolve(res) 52 | }) 53 | .catch(ex =>{ 54 | console.log(ex) 55 | reject(ex) 56 | }) 57 | }) 58 | } 59 | 60 | async function getModelSetVersions(input) { 61 | msclient.ApiClient.instance.authentications["oauth2AuthCode"].accessToken = input.credentials.access_token; 62 | return new Promise((resolve, reject) => { 63 | const msVersionApi = new msclient.ModelSetVersionsApi() 64 | msVersionApi.getModelSetVersions(input.mc_container_id,input.ms_id) 65 | .then(res=>{ 66 | resolve(res) 67 | }) 68 | .catch(ex =>{ 69 | console.log(ex) 70 | reject(ex) 71 | }) 72 | }) 73 | } 74 | async function getModelSetVersion(input) { 75 | msclient.ApiClient.instance.authentications["oauth2AuthCode"].accessToken = input.credentials.access_token; 76 | return new Promise((resolve, reject) => { 77 | const msVersionApi = new msclient.ModelSetVersionsApi() 78 | msVersionApi.getModelSetVersion(input.mc_container_id,input.ms_id,input.ms_v_id) 79 | .then(res=>{ 80 | resolve(res) 81 | }) 82 | .catch(ex =>{ 83 | console.log(ex) 84 | reject(ex) 85 | }) 86 | }) 87 | } -------------------------------------------------------------------------------- /server/endpoints/dm.endpoints.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | const express = require('express'); 22 | const router = express.Router(); 23 | 24 | const UserSession = require('../services/userSession'); 25 | const oAuthServices = require('../services/oauth.services'); 26 | const hubsServices = require('../services/dm.hubs.services'); 27 | const projectsServices = require('../services/dm.projects.services'); 28 | 29 | 30 | router.get('/dm/getBIMHubs', async (req, res, next) => { 31 | 32 | try{ 33 | const userSession = new UserSession(req.session); 34 | if (!userSession.isAuthorized()) { 35 | res.status(401).end('Please login first'); 36 | return; 37 | } 38 | 39 | let input = { 40 | oAuth:userSession.getUserServerOAuth(), 41 | credentials:userSession.getUserServerCredentials() 42 | } 43 | const hubs = await hubsServices.getHubs(input) 44 | if(hubs){ 45 | res.json(hubs) 46 | //getAllHubsInfo(hubs) 47 | } 48 | else 49 | res.status(500).end() 50 | } 51 | catch(e){ 52 | console.log('getBIMHubs failed: '+ e.message) 53 | res.status(500).end() 54 | } 55 | }) 56 | 57 | router.get('/dm/getBIMProjects/:hubId', async (req, res, next) => { 58 | try{ 59 | const userSession = new UserSession(req.session); 60 | if (!userSession.isAuthorized()) { 61 | res.status(401).end('Please login first'); 62 | return; 63 | } 64 | const hubId = req.params['hubId'] 65 | let input = { 66 | oAuth:userSession.getUserServerOAuth(), 67 | credentials:userSession.getUserServerCredentials(), 68 | hubId:hubId 69 | } 70 | const projects = await projectsServices.getProjects(input) 71 | if(projects){ 72 | res.json(projects) 73 | } 74 | else 75 | res.status(500).end() 76 | } 77 | catch(e){ 78 | console.log('getBIMProjects failed: '+ e.message) 79 | res.status(500).end() 80 | } 81 | }) 82 | 83 | router.get('/dm/user/profile', async (req, res, next) => { 84 | 85 | try{ 86 | const userSession = new UserSession(req.session); 87 | if (!userSession.isAuthorized()) { 88 | res.status(401).end('Please login first'); 89 | return; 90 | } 91 | let input = { 92 | oAuth:userSession.getUserServerOAuth(), 93 | credentials:userSession.getUserServerCredentials() 94 | } 95 | const userprofile = await hubsServices.getUserProfile(input) 96 | if(userprofile) 97 | res.json(userprofile) 98 | else 99 | res.status(500).end() 100 | } 101 | catch(e){ 102 | console.log('get user profile failed: '+ e.message) 103 | res.status(500).end() 104 | } 105 | }); 106 | 107 | module.exports = router 108 | 109 | 110 | -------------------------------------------------------------------------------- /www/js/ForgeViewer.js: -------------------------------------------------------------------------------- 1 | class ForgeViewer { 2 | 3 | constructor() { 4 | this._viewer = null 5 | this._clashDocToModel = {} 6 | } 7 | 8 | fetchForgeToken(callback) { 9 | 10 | $.ajax({ 11 | url: '/oauth/publictoken', 12 | success: function (res) { 13 | callback(res, 3600) 14 | } 15 | }); 16 | } 17 | 18 | launchViewer( models ) { 19 | 20 | 21 | if (this._viewer!=null) { 22 | this._viewer.tearDown() 23 | this._viewer.finish() 24 | this._viewer = null 25 | $("#forgeViewer").empty(); 26 | this._clashDocToModel = {} 27 | } 28 | 29 | if( !models || models.length <= 0 ) 30 | return console.error( 'Empty model input' ); 31 | 32 | const options = { 33 | env: 'AutodeskProduction', 34 | getAccessToken: this.fetchForgeToken 35 | }; 36 | 37 | var _this = this 38 | Autodesk.Viewing.Initializer( options, () => { 39 | 40 | //get the viewer div 41 | const viewerDiv = document.getElementById( 'forgeViewer' ); 42 | 43 | var config3d = { 44 | //'extensions': ['ToolbarExtension'] 45 | }; 46 | 47 | //initialize the viewer object 48 | _this._viewer = new Autodesk.Viewing.Private.GuiViewer3D( viewerDiv ,config3d) 49 | _this._viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, () =>{}); 50 | 51 | //load model one by one in sequence 52 | const util = new MultipleModelUtil(_this._viewer); 53 | util.processModels( models ); 54 | }); 55 | } 56 | 57 | 58 | isolateClash(clashes){ 59 | 60 | if(global_msSet._docsMap.length> Object.keys(global_forgeViewer._clashDocToModel).length){ 61 | alert('not all models are loaded in viewer. try after a moment!') 62 | return 63 | } 64 | let _viewer = this._viewer 65 | let viewerModels = _viewer.getVisibleModels() 66 | for(let i in viewerModels){ 67 | _viewer.clearThemingColors(viewerModels[i]); 68 | } 69 | _viewer.clearSelection(); 70 | _viewer.impl.visibilityManager.aggregateIsolate([]); 71 | 72 | var isolate_pair = [] 73 | 74 | var nodebBox = new THREE.Box3(); 75 | for(let id in clashes){ 76 | 77 | var Ldid = clashes[id].Ldid 78 | var Rdid = clashes[id].Rdid 79 | var Lvid = clashes[id].Lvid 80 | var Rvid = clashes[id].Rvid 81 | 82 | 83 | var Lmodel = global_forgeViewer._clashDocToModel[Ldid].model 84 | var Rmodel = global_forgeViewer._clashDocToModel[Rdid].model 85 | 86 | var LFragsList = Lmodel.getFragmentList() 87 | var RFragsList = Rmodel.getFragmentList() 88 | var LIT = Lmodel.getData().instanceTree 89 | var RIT = Rmodel.getData().instanceTree 90 | 91 | _viewer.setThemingColor(Lvid,new THREE.Vector4(1,0,0,1),Lmodel) 92 | _viewer.setThemingColor(Rvid,new THREE.Vector4(0,0,1,1),Rmodel) 93 | 94 | if(Ldid in isolate_pair){ 95 | isolate_pair[Ldid].push(Lvid) 96 | }else{ 97 | isolate_pair[Ldid] = [] 98 | isolate_pair[Ldid].push(Lvid) 99 | } 100 | 101 | if(Rdid in isolate_pair){ 102 | isolate_pair[Rdid].push(Rvid) 103 | }else{ 104 | isolate_pair[Rdid] = [] 105 | isolate_pair[Rdid].push(Rvid) 106 | } 107 | 108 | var fragbBox = new THREE.Box3(); 109 | LIT.enumNodeFragments(Lvid, (fragId) =>{ 110 | LFragsList.getWorldBounds(fragId, fragbBox); 111 | nodebBox.union(fragbBox); 112 | }) 113 | RIT.enumNodeFragments(Rvid, (fragId) =>{ 114 | RFragsList.getWorldBounds(fragId, fragbBox); 115 | nodebBox.union(fragbBox); 116 | }) 117 | } 118 | 119 | let toIsolate = [] 120 | for(let clashDocId in isolate_pair){ 121 | let thismodel = global_forgeViewer._clashDocToModel[clashDocId].model 122 | toIsolate.push({model:thismodel,ids:isolate_pair[clashDocId]}) 123 | } 124 | _viewer.impl.visibilityManager.aggregateIsolate(toIsolate) 125 | _viewer.navigation.fitBounds(true, nodebBox ) 126 | 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /server/services/mc.clash.services.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | // Copyright (c) Autodesk, Inc. All rights reserved 21 | // Written by Forge Partner Development 22 | // 23 | // Permission to use, copy, modify, and distribute this software in 24 | // object code form for any purpose and without fee is hereby granted, 25 | // provided that the above copyright notice appears in all copies and 26 | // that both that copyright notice and the limited warranty and 27 | // restricted rights notice below appear in all supporting 28 | // documentation. 29 | // 30 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 31 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 32 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 33 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 34 | // UNINTERRUPTED OR ERROR FREE. 35 | ///////////////////////////////////////////////////////////////////// 36 | 37 | 'use strict'; 38 | const fetch = require('node-fetch'); 39 | const fs = require("fs"); 40 | 41 | const clashclient = require("forge-bim360-modelcoordination-clash"); 42 | 43 | module.exports = { 44 | getClashTests:getClashTests, 45 | getClashTest:getClashTest, 46 | getClashTestResources:getClashTestResources, 47 | downloadResources:downloadResources 48 | } 49 | 50 | async function getClashTests(input) { 51 | clashclient.ApiClient.instance.authentications["oauth2AuthCode"].accessToken = input.credentials.access_token; 52 | 53 | return new Promise((resolve, reject) => { 54 | var testsApi = new clashclient.ClashTestApi() 55 | 56 | testsApi.getModelSetClashTests(input.mc_container_id,input.ms_id) 57 | .then(res=>{ 58 | resolve(res) 59 | }) 60 | .catch(ex =>{ 61 | console.log(ex) 62 | reject(ex) 63 | }) 64 | }) 65 | } 66 | 67 | async function getClashTest(input) { 68 | clashclient.ApiClient.instance.authentications["oauth2AuthCode"].accessToken = input.credentials.access_token; 69 | 70 | return new Promise((resolve, reject) => { 71 | var testsApi = new clashclient.ClashTestApi() 72 | testsApi.getClashTest(input.mc_container_id,input.testid) 73 | .then(res=>{ 74 | resolve(res) 75 | }) 76 | .catch(ex =>{ 77 | console.log(ex) 78 | reject(ex) 79 | }) 80 | }) 81 | } 82 | 83 | async function getClashTestResources(input) { 84 | clashclient.ApiClient.instance.authentications["oauth2AuthCode"].accessToken = input.credentials.access_token; 85 | 86 | return new Promise((resolve, reject) => { 87 | var testsApi = new clashclient.ClashTestApi() 88 | 89 | testsApi.getClashTestResources(input.mc_container_id,input.testid) 90 | .then(res=>{ 91 | resolve(res) 92 | }) 93 | .catch(ex =>{ 94 | console.log(ex) 95 | reject(ex) 96 | }) 97 | }) 98 | } 99 | 100 | async function downloadResources(input) { 101 | clashclient.ApiClient.instance.authentications["oauth2AuthCode"].accessToken = input.credentials.access_token; 102 | 103 | const options = { method: 'GET', headers: input.headers }; 104 | const res = await fetch(input.resurl,options); 105 | const fileStream = fs.createWriteStream(input.path+input.filename); 106 | 107 | await new Promise((resolve, reject) => { 108 | res.body.pipe(fileStream); 109 | res.body.on("error", (err) => { 110 | reject(err); 111 | }); 112 | fileStream.on("finish", function(res) { 113 | resolve(); 114 | }); 115 | }); 116 | } -------------------------------------------------------------------------------- /server/endpoints/oauth.endpoints.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | 22 | // web framework 23 | var express = require('express'); 24 | var router = express.Router(); 25 | var request = require('request'); 26 | 27 | // forge oAuth package 28 | var forgeSDK = require('forge-apis'); 29 | 30 | var config = require('../config'); 31 | var UserSession = require('../services/userSession'); 32 | 33 | router.get('/oauth/clientid', function (req, res) { 34 | res.json({ 35 | 'ForgeClientId': config.credentials.client_id 36 | }); 37 | }); 38 | 39 | // this end point will logoff the user by destroying the session 40 | // as of now there is no Forge endpoint to invalidate tokens 41 | router.get('/oauth/logoff', function (req, res) { 42 | req.session.destroy(); 43 | res.end('/'); 44 | }); 45 | 46 | // return the public token of the current user 47 | // the public token should have a limited scope (read-only) 48 | router.get('/oauth/publictoken', function (req, res) { 49 | 50 | var userSession = new UserSession(req.session); 51 | if (!userSession.isAuthorized()) { 52 | console.log('no token for client'); 53 | res.end(""); 54 | return; 55 | } 56 | res.end(userSession.getUserClientCredentials().access_token); 57 | }); 58 | 59 | // return the forge authenticate url 60 | router.get('/oauth/url', function (req, res) { 61 | // redirect the user to this page 62 | var url = 63 | "https://developer.api.autodesk.com" + 64 | '/authentication/v1/authorize?response_type=code' + 65 | '&client_id=' + config.credentials.client_id + 66 | '&redirect_uri=' + config.callbackURL + 67 | '&scope=' + config.scopeInternal.join(" "); 68 | res.end(url); 69 | }); 70 | 71 | // wait for Autodesk callback (oAuth callback) 72 | router.get('/api/forge/callback/oauth', function (req, res) { 73 | 74 | var code = req.query.code; 75 | var userSession = new UserSession(req.session); 76 | 77 | // first get a 3-legged token of the user 78 | var req = new forgeSDK.AuthClientThreeLegged( 79 | config.credentials.client_id, 80 | config.credentials.client_secret, 81 | config.callbackURL, config.scopeInternal); 82 | 83 | req.getToken(code) 84 | .then(function (userServerCredentials) { 85 | 86 | console.log('get user server token succeeded!'); 87 | userSession.setUserServerCredentials(userServerCredentials); 88 | userSession.setUserServerOAuth(req); 89 | 90 | // then refresh and get a token for viewer 91 | // that we can send to the client 92 | var req2 = new forgeSDK.AuthClientThreeLegged( 93 | config.credentials.client_id, 94 | config.credentials.client_secret, 95 | config.callbackURL, 96 | config.scopePublic); 97 | 98 | req2.refreshToken(userServerCredentials) 99 | .then(function (userClientCredentials) { 100 | console.log('get user client token succeeded!'); 101 | userSession.setUserClientCredentials(userClientCredentials); 102 | userSession.setUserClientOAuth(req); 103 | 104 | res.redirect('/'); 105 | }) 106 | .catch(function (error) { 107 | console.log('get user client token failed!'); 108 | respondWithError(res, error) 109 | }); 110 | }) 111 | .catch(function (error) { 112 | console.log('get user server token failed!'); 113 | respondWithError(res, error) 114 | }); 115 | }); 116 | 117 | function respondWithError(res, error) { 118 | if (error.statusCode) { 119 | res.status(error.statusCode).end(error.statusMessage); 120 | } else { 121 | res.status(500).end(error.message); 122 | } 123 | } 124 | 125 | 126 | 127 | module.exports = router; 128 | -------------------------------------------------------------------------------- /www/js/dm.projects.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | class DMProjects { 21 | constructor() { 22 | } 23 | 24 | async refreshBIMHubs() { 25 | 26 | try { 27 | const renderHTML = await this.getRenderHTML() 28 | let tempDiv = document.getElementById('clashview'); 29 | tempDiv.innerHTML = renderHTML; 30 | 31 | $('#hubSpinner').css({ display: "block" }); 32 | 33 | const hubs = await this.getBIMHubs() 34 | if (!hubs) { 35 | $('#hubSpinner').css({ display: "none" }); 36 | return false 37 | } 38 | 39 | let hubs_list = $('#hubs_list') 40 | hubs_list.empty() 41 | hubs.forEach((ele) => { 42 | var hubId = ele.id 43 | var name = ele.name 44 | var validhtmlid = hubId.replace('b.', '') 45 | 46 | //produce hubs dropdown list 47 | const oneItem = document.createElement('a') 48 | oneItem.classList.add('dropdown-item') 49 | oneItem.href = "#"; 50 | oneItem.id = validhtmlid 51 | oneItem.innerHTML = name 52 | hubs_list.append(oneItem); 53 | }); 54 | 55 | if (hubs_list.height() > 400) 56 | hubs_list.addClass('dropdown-height') 57 | else 58 | hubs_list.removeClass('dropdown-height') 59 | 60 | $('#hubSpinner').css({ display: "none" }); 61 | 62 | return true 63 | } 64 | catch (e) { 65 | console.log('refreshBIMHubs failed!') 66 | return false 67 | } 68 | } 69 | 70 | async refreshProjects(hubId) { 71 | 72 | try { 73 | $('#projSpinner').css({ display: "block" }); 74 | 75 | const projects = await this.getBIMProjects(hubId) 76 | if (!projects) { 77 | $('#projSpinner').css({ display: "none" }); 78 | return false 79 | } 80 | 81 | let projs_list = $('#projects_list') 82 | projs_list.empty() 83 | projects.forEach((ele) => { 84 | if (ele.type === 'bim360projects') 85 | var name = ele.name 86 | var projectId = ele.id; 87 | var validhtmlid = projectId.replace('b.', '') 88 | 89 | const oneItem = document.createElement('a'); 90 | oneItem.classList.add('dropdown-item') 91 | oneItem.href = "#"; 92 | oneItem.id = validhtmlid 93 | oneItem.innerHTML = name 94 | 95 | projs_list.append(oneItem); 96 | }) 97 | 98 | if (projs_list.height() > 400) 99 | projs_list.addClass('dropdown-height') 100 | else 101 | projs_list.removeClass('dropdown-height') 102 | $('#projSpinner').css({ display: "none" }); 103 | return true 104 | 105 | }catch (e) { 106 | console.log('refreshProjects failed!') 107 | return false 108 | } 109 | } 110 | 111 | async getBIMHubs() { 112 | return new Promise((resolve, reject) => { 113 | $.ajax({ 114 | url: '/dm/getBIMHubs', 115 | type: 'GET', 116 | success: function (res) { 117 | if (res != null && res != '') 118 | resolve(res) 119 | else 120 | resolve(null) 121 | } 122 | }) 123 | }) 124 | } 125 | 126 | async getBIMProjects(hubId) { 127 | return new Promise((resolve, reject) => { 128 | $.ajax({ 129 | url: '/dm/getBIMProjects/' + hubId, 130 | type: 'GET', 131 | success: function (res) { 132 | if (res != null && res != '') 133 | resolve(res) 134 | else 135 | resolve(null) 136 | } 137 | }) 138 | }) 139 | } 140 | 141 | async getRenderHTML() { 142 | return new Promise((resolve, reject) => { 143 | $.ajax({ 144 | url: 'panelPage.html', 145 | success: function (data) { 146 | resolve(data) 147 | }, error: function (error) { 148 | reject(error) 149 | } 150 | }); 151 | }) 152 | } 153 | } -------------------------------------------------------------------------------- /server/utility.js: -------------------------------------------------------------------------------- 1 | 2 | ///////////////////////////////////////////////////////////////////// 3 | // Copyright (c) Autodesk, Inc. All rights reserved 4 | // Written by Forge Partner Development 5 | // 6 | // Permission to use, copy, modify, and distribute this software in 7 | // object code form for any purpose and without fee is hereby granted, 8 | // provided that the above copyright notice appears in all copies and 9 | // that both that copyright notice and the limited warranty and 10 | // restricted rights notice below appear in all supporting 11 | // documentation. 12 | // 13 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 14 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 15 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 16 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 17 | // UNINTERRUPTED OR ERROR FREE. 18 | ///////////////////////////////////////////////////////////////////// 19 | 20 | const fs = require("fs"); 21 | const fetch = require('node-fetch'); 22 | const rimraf = require('rimraf'); 23 | const readline = require('readline'); 24 | const crypto = require('crypto'); 25 | const pako = require('pako') 26 | 27 | 28 | const statusFolder = './Status/' 29 | 30 | module.exports = { 31 | clearFolder, 32 | saveJsonObj, 33 | downloadResources, 34 | readLinesFile, 35 | randomValueBase64, 36 | storeStatus, 37 | readStatus, 38 | deleteStatus, 39 | compressStream, 40 | flatDeep 41 | } 42 | 43 | async function clearFolder(folder) { 44 | return new Promise((resolve, reject) => { 45 | rimraf(folder + '/*', function () { 46 | console.log('clear output foler done'); 47 | resolve(); 48 | }); 49 | }); 50 | } 51 | 52 | 53 | async function saveJsonObj(path, filename, obj) { 54 | 55 | return new Promise((resolve, reject) => { 56 | const stringToWrite = JSON.stringify(obj, null, ' ') 57 | // Trim leading spaces: 58 | .replace(/^ +/gm, '') 59 | // Add a space after every key, before the `:`: 60 | .replace(/: "(?:[^"]+|\\")*",?$/gm, ' $&'); 61 | 62 | fs.writeFile(path + filename, 63 | stringToWrite, function (err) { 64 | if (err) { 65 | reject(err); 66 | } 67 | resolve(path + filename + ' saved!'); 68 | }); 69 | }); 70 | } 71 | 72 | 73 | async function downloadResources( 74 | url, headers, 75 | path, filename) { 76 | 77 | const options = { method: 'GET', headers: headers }; 78 | const res = await fetch(url, options); 79 | const fileStream = fs.createWriteStream(path + filename); 80 | 81 | return new Promise((resolve, reject) => { 82 | res.body.pipe(fileStream); 83 | res.body.on("error", (err) => { 84 | reject(err); 85 | }); 86 | fileStream.on("finish", function (res) { 87 | resolve(filename); 88 | }); 89 | }); 90 | } 91 | 92 | 93 | async function readLinesFile(csvFilePath) { 94 | 95 | return new Promise((resolve, reject) => { 96 | 97 | var returnJson = [] 98 | let rl = readline.createInterface({ 99 | input: fs.createReadStream(csvFilePath) 100 | }); 101 | 102 | let line_no = 0; 103 | // event is emitted after each line 104 | rl.on('line', function (line) { 105 | line_no++; 106 | returnJson.push(JSON.parse(line.trim())) 107 | }); 108 | // end 109 | rl.on('close', function (line) { 110 | console.log('Total lines : ' + line_no); 111 | resolve(returnJson) 112 | }); 113 | }); 114 | } 115 | 116 | function randomValueBase64(len) { 117 | return crypto.randomBytes(Math.ceil(len * 3 / 4)) 118 | .toString('base64') // convert to base64 format 119 | .slice(0, len) // return required number of characters 120 | .replace(/\+/g, '0') // replace '+' with '0' 121 | .replace(/\//g, '0'); // replace '/' with '0' 122 | } 123 | 124 | function storeStatus(jobId, status) { 125 | fs.writeFileSync(statusFolder + jobId, status) 126 | } 127 | 128 | function readStatus(jobId) { 129 | if (fs.existsSync(statusFolder + jobId)) { 130 | var stats = fs.readFileSync(statusFolder + jobId, "utf8") 131 | return stats; 132 | } 133 | else 134 | return null 135 | } 136 | 137 | function deleteStatus(jobId) { 138 | if (fs.existsSync(statusFolder + jobId)) { 139 | fs.unlinkSync(statusFolder + jobId) 140 | } 141 | } 142 | 143 | 144 | function compressStream(inputJson) { 145 | const inputStr = JSON.stringify(inputJson) 146 | return pako.deflate(inputStr) 147 | } 148 | 149 | 150 | function flatDeep(arr, d = 1) { 151 | return d > 0 ? arr.reduce((acc, val) => acc.concat(Array.isArray(val) ? flatDeep(val, d - 1) : val), []) 152 | : arr.slice(); 153 | }; -------------------------------------------------------------------------------- /www/index.js: -------------------------------------------------------------------------------- 1 | const global_oAuth = new oAuth() 2 | const global_dmProjects = new DMProjects() 3 | const global_msSet = new MSSet() 4 | const global_clashRawView= new ClashRawView() 5 | const global_forgeViewer= new ForgeViewer() 6 | const global_navHelp= new NavHelp() 7 | const global_Utility = new Utility() 8 | 9 | $(document).ready(function () { 10 | 11 | 12 | $('#iconlogin').click(global_oAuth.forgeSignIn); 13 | 14 | var currentToken = global_oAuth.getForgeToken(); 15 | 16 | if (currentToken === '') 17 | $('#signInButton').click(global_oAuth.forgeSignIn); 18 | else { 19 | (async()=>{ 20 | let profile = await global_oAuth.getForgeUserProfile() 21 | 22 | $('#signInProfileImage').removeClass(); 23 | $('#signInProfileImage').html('') 24 | $('#signInButtonText').text(profile.name); 25 | $('#signInButtonText').attr('title', 'Click to Sign Out'); 26 | $('#signInButton').click(global_oAuth.forgeLogoff); 27 | 28 | let r = await global_dmProjects.refreshBIMHubs() 29 | if(!r) 30 | return 31 | 32 | //delegate the event when one hub is selected 33 | delegateHubSelection() 34 | //delegate the event when one project is selected 35 | delegateProjectSelection() 36 | //delegate the event when one modelset is selected 37 | delegateModelsetSelection() 38 | //delegate the event when one table item is selected 39 | delegateRawTableSelection() 40 | //delegate event when refresh MC icon is clicked 41 | delegateRefreshMC() 42 | //delegate event when refresh Clash icon is clicked 43 | delegateRefreshClash() 44 | })() 45 | } 46 | //initialize the helps 47 | global_navHelp.init() 48 | 49 | }); 50 | 51 | function delegateHubSelection(){ 52 | $(document).on('click', '#hubs_list a', function(e) { 53 | $('#hub_dropdown_title').html($(this).html()); 54 | const hub_id_without_b = $(this).attr('id') 55 | const hub_id_with_b = 'b.' + hub_id_without_b 56 | global_dmProjects.refreshProjects(hub_id_with_b) 57 | }); 58 | } 59 | 60 | function delegateProjectSelection(){ 61 | $(document).on('click', '#projects_list a', function(e) { 62 | $('#project_dropdown_title').html($(this).html()); 63 | const proj_id_without_b = $(this).attr('id') 64 | global_msSet.refreshModelSets(proj_id_without_b) 65 | 66 | $('#projects_list .active').removeClass('active'); 67 | $(this).toggleClass('active') 68 | }); 69 | } 70 | 71 | function delegateModelsetSelection(){ 72 | $(document).on('click', '#modelsetList .list-group-item', function(e) { 73 | $('#modelsetList .active').removeClass('active') 74 | $(this).toggleClass('active') 75 | 76 | const mc_containter_id = $('#projects_list .active').attr('id') 77 | const ms_id = $(this).attr("id"); 78 | const ms_v_id = $(this).find("span")[0].innerHTML.replace('v-',''); 79 | 80 | (async(mc_containter_id,ms_id,ms_v_id)=>{ 81 | //refresh clash data 82 | let r = await global_msSet.refreshOneModelset(mc_containter_id,ms_id,ms_v_id) 83 | if(r) 84 | r = await global_clashRawView.produceClashRawTable(mc_containter_id,ms_id,ms_v_id) 85 | if(r) 86 | global_forgeViewer.launchViewer(global_msSet._docsMap) 87 | 88 | })(mc_containter_id,ms_id,ms_v_id) 89 | }) 90 | } 91 | 92 | function delegateRawTableSelection(){ 93 | $(document).on('click', '#clashRawTable tr', function(e) { 94 | $('#clashRawTable .table-success').removeClass('table-success'); 95 | $(this).toggleClass('table-success') 96 | 97 | const Ldid = parseInt($(this).find('td')[2].innerText) 98 | const Rdid = parseInt($(this).find('td')[3].innerText) 99 | const Lvid = parseInt($(this).find('td')[4].innerText) 100 | const Rvid = parseInt($(this).find('td')[5].innerText) 101 | 102 | //isolate the clashed objects 103 | global_forgeViewer.isolateClash([ 104 | {Ldid:Ldid,Rdid:Rdid,Lvid:Lvid,Rvid:Rvid} 105 | ]) 106 | }) 107 | } 108 | 109 | function delegateRefreshMC(){ 110 | $(document).on('click', '#btnRefreshMC', function(e) { 111 | const proj_id_without_b = $('#projects_list .active').attr('id'); 112 | if(proj_id_without_b) 113 | global_msSet.refreshModelSets(proj_id_without_b) 114 | }) 115 | } 116 | 117 | function delegateRefreshClash(){ 118 | $(document).on('click', '#btnRefreshClash', function(e) { 119 | const mc_containter_id = $('#projects_list .active').attr('id'); 120 | const ms_id = $('#modelsetList .active').attr("id"); 121 | const ms_v_id = $('#modelsetList .active').find("span")[0].innerHTML.replace('v-',''); 122 | //refresh clash data 123 | 124 | (async(mc_containter_id,ms_id,ms_v_id)=>{ 125 | //refresh clash data 126 | let r = await global_msSet.refreshOneModelset(mc_containter_id,ms_id,ms_v_id) 127 | if(r) 128 | r = await global_clashRawView.produceClashRawTable(mc_containter_id,ms_id,ms_v_id,true) 129 | if(r) 130 | global_forgeViewer.launchViewer(global_msSet._docsMap) 131 | 132 | })(mc_containter_id,ms_id,ms_v_id) 133 | 134 | }) 135 | 136 | } -------------------------------------------------------------------------------- /www/helpDiv/configHelp.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 35 | 36 | 37 | 38 | 78 | 81 | -------------------------------------------------------------------------------- /server/services/dm.hubs.services.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | const fetch = require('node-fetch'); 21 | 22 | const forgeSDK = require('forge-apis'); 23 | const config = require('../config'); 24 | 25 | module.exports = { 26 | getHubs:getHubs, 27 | getUserProfile:getUserProfile, 28 | getHQUsersList:getHQUsersList, 29 | getHQCompanyList:getHQCompanyList 30 | } 31 | 32 | async function getHubs(input){ 33 | 34 | return new Promise((resolve, reject) => { 35 | 36 | var hubsAPI = new forgeSDK.HubsApi(); 37 | hubsAPI.getHubs({}, input.oAuth,input.credentials) 38 | .then((response)=> { 39 | console.log('get hubs succeeded!'); 40 | var hubs = []; 41 | response.body.data.forEach(function (hub) { 42 | var hubType; 43 | switch (hub.attributes.extension.type) { 44 | case "hubs:autodesk.core:Hub": 45 | hubType = "hubs"; 46 | break; 47 | case "hubs:autodesk.a360:PersonalHub": 48 | hubType = "personalHub"; 49 | break; 50 | case "hubs:autodesk.bim360:Account": 51 | hubType = "bim360Hubs"; 52 | break; 53 | } 54 | if (hubType == "bim360Hubs") { 55 | hubs.push({ id: hub.id, name:hub.attributes.name}) 56 | } 57 | }); 58 | resolve(hubs); 59 | }) 60 | .catch(function (error) { 61 | console.log('get BIM hubs failed!'); 62 | reject({error:error}); 63 | }); 64 | }) 65 | } 66 | 67 | async function getUserProfile(input){ 68 | 69 | const headers = config.hqv1.httpHeaders(input.credentials.access_token) 70 | const endpoint = config.hqv1.getUserProfileAtMe() 71 | const options = { method: 'GET', headers: headers || {} }; 72 | const response = await fetch(endpoint, options); 73 | if (response.status == 200 ) { 74 | const json = await response.json(); 75 | return { 76 | name: json.firstName + ' ' + json.lastName, 77 | picture: json.profileImages.sizeX20, 78 | userId:json.userId 79 | } 80 | } else { 81 | const message = await response.text(); 82 | console.log('get getUserProfile failed' + message) 83 | return null 84 | } 85 | } 86 | 87 | async function getHQUsersList(input){ 88 | let all = [] 89 | let pageOffset = 0 90 | let morePagesAvailable = true; 91 | 92 | while(morePagesAvailable){ 93 | const single = await getHQUserSinglePage(input,pageOffset) 94 | all = all.concat(single); 95 | pageOffset = all.length 96 | morePagesAvailable = single.length 97 | } 98 | 99 | return all 100 | } 101 | 102 | 103 | async function getHQUserSinglePage(input,pageOffset){ 104 | 105 | return new Promise((resolve, reject) => { 106 | 107 | const headers = { 108 | Authorization: 'Bearer '+ input.access_token, 109 | 'Content-Type': 'application/json' 110 | } 111 | var url = 112 | 'https://developer.api.autodesk.com/hq/v1/accounts/' 113 | + input.accountId 114 | + '/users?limit=50&offset=' + pageOffset 115 | 116 | return fetch(url,{ 117 | method: 'GET', 118 | headers:headers 119 | }).then(response => response.json()).then(data => { 120 | resolve(data) 121 | }) 122 | }) 123 | } 124 | 125 | async function getHQCompanyList(input){ 126 | let all = [] 127 | let pageOffset = 0 128 | let morePagesAvailable = true; 129 | 130 | while(morePagesAvailable){ 131 | const single = await getHQCompanySinglePage(input,pageOffset) 132 | all = all.concat(single); 133 | pageOffset = all.length 134 | morePagesAvailable = single.length 135 | } 136 | 137 | return all 138 | } 139 | 140 | async function getHQCompanySinglePage(input,pageOffset){ 141 | 142 | return new Promise((resolve, reject) => { 143 | 144 | const headers = { 145 | Authorization: 'Bearer '+ input.access_token, 146 | 'Content-Type': 'application/json' 147 | } 148 | var url = 149 | 'https://developer.api.autodesk.com/hq/v1/accounts/' 150 | + input.accountId 151 | + '/companies?limit=50&offset=' + pageOffset 152 | 153 | return fetch(url,{ 154 | method: 'GET', 155 | headers:headers 156 | }).then(response => response.json()).then(data => { 157 | resolve(data) 158 | }) 159 | }) 160 | } 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /www/js/mc.ms.set.js: -------------------------------------------------------------------------------- 1 | class MSSet { 2 | 3 | constructor() { 4 | this._docsMap = null 5 | } 6 | 7 | async refreshModelSets(mc_container_id){ 8 | 9 | try{ 10 | var lg = $(".list-group") 11 | lg.empty() 12 | 13 | $('#modelsetList div small').css({ display: "none" }) 14 | $('#msSpinner').css({ display: "block" }); 15 | const modelSets = await this.getModelSets(mc_container_id) 16 | 17 | modelSets.forEach(element => { 18 | let a = document.createElement("a") 19 | a.href = '#' 20 | a.id = element.ms_id 21 | a.classList.add('list-group-item') 22 | a.classList.add('list-group-item-action') 23 | a.innerHTML = element.ms_name 24 | 25 | let tag = document.createElement("span") 26 | tag.classList.add('badge') 27 | tag.classList.add('badge-primary') 28 | tag.classList.add('badge-pill') 29 | tag.classList.add('pull-right') 30 | tag.innerHTML = 'v-' + element.tipVersion 31 | 32 | a.appendChild(tag) 33 | lg[0].appendChild(a) 34 | }); 35 | $('#msSpinner').css({ display: "none" }) 36 | 37 | if(modelSets.length==0){ 38 | $('#modelsetList div small').css({ display: "block" }) 39 | $('#modelsetList div small').html('no model sets with this project') 40 | } 41 | global_Utility.successMessage('Refresh ModelSet Collection Succeeded!!') 42 | 43 | return true 44 | 45 | } 46 | catch(ex){ 47 | console.log('refreshModelSets failed!' + ex) 48 | global_Utility.failMessage('Refresh ModelSet Collection Failed!!') 49 | return false 50 | } 51 | } 52 | 53 | async getModelSets(mc_container_id){ 54 | return new Promise(( resolve, reject ) => { 55 | $.ajax({ 56 | url: '/mc/modelset/getModelSets/'+ mc_container_id, 57 | type: 'GET' , 58 | success: function (data) { 59 | resolve(data) 60 | },error: function (error) { 61 | reject(error) 62 | } 63 | }); 64 | }) 65 | } 66 | 67 | async prepareClashData(mc_container_id,ms_id,ms_v_id,toRefresh=false){ 68 | return new Promise(( resolve, reject ) => { 69 | $.ajax({ 70 | url: '/mc/modelset/prepareClashData/'+mc_container_id + '/'+ ms_id+'/'+ms_v_id+'/'+toRefresh, 71 | type: 'GET' , 72 | success: function (data) { 73 | resolve(data.jobId) 74 | },error: function (error) { 75 | reject(error) 76 | } 77 | }); 78 | }) 79 | } 80 | 81 | async getPrepareStatus(jobId){ 82 | return new Promise(( resolve, reject ) => { 83 | $.ajax({ 84 | url: '/mc/modelset/getPrepareStatus/'+jobId, 85 | type: 'GET' , 86 | success: function (data) { 87 | resolve(data.status) 88 | },error: function (error) { 89 | reject(error) 90 | } 91 | }); 92 | }) 93 | } 94 | 95 | async getClashDocsMap(mc_container_id,ms_id,ms_v_id){ 96 | var _this = this 97 | return new Promise(( resolve, reject ) => { 98 | $.ajax({ 99 | url: '/mc/modelset/getDocMap/'+ mc_container_id + '/' + ms_id+'/'+ms_v_id, 100 | type: 'GET' , 101 | success: (data) => { 102 | _this._docsMap = data 103 | resolve(data) 104 | },error: (error) => { 105 | reject(error) 106 | } 107 | }); 108 | }) 109 | } 110 | 111 | async refreshOneModelset(mc_container_id,ms_id,ms_v_id,toRefresh=false){ 112 | 113 | try{ 114 | 115 | $('#clashviewSpinner').css({ display: "block" }); 116 | $('#forgeSpinner').css({ display: "block" }); 117 | 118 | const jobId = await this.prepareClashData(mc_container_id,ms_id,ms_v_id,toRefresh) 119 | let status = 'running' 120 | 121 | //set timeout 122 | const st = new Date().getTime() 123 | while(status == 'running' 124 | && !global_Utility.checkTimeout(st,new Date().getTime())) 125 | status = await this.getPrepareStatus(jobId) 126 | 127 | if(status == 'failed'){ 128 | global_Utility.failMessage('Prepare ClashData Timeout!') 129 | return false 130 | } 131 | if(status == 'failed'){ 132 | global_Utility.failMessage('Prepare ClashData Failed!') 133 | return false 134 | } 135 | 136 | await this.getClashDocsMap(mc_container_id,ms_id,ms_v_id) 137 | 138 | global_Utility.successMessage('Prepare ClashData Succeeded!') 139 | 140 | $('#clashviewSpinner').css({ display: "none" }); 141 | $('#forgeSpinner').css({ display: "none" }); 142 | return true 143 | } 144 | catch(ex){ 145 | console.log('Prepare ClashData Failed!! ' + ex ) 146 | global_Utility.failMessage('Prepare ClashData Failed!') 147 | 148 | $('#clashviewSpinner').css({ display: "none" }); 149 | $('#forgeSpinner').css({ display: "none" }); 150 | return false 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Model Coordinaition Demo 5 | 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 | 85 | 86 |
87 |
88 |
89 |
90 | 91 |
92 |
93 | 97 |
98 |
99 |
You may also need to provision your 100 |
BIM 360 Docs account for this app. 101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | 111 |
112 |
113 |
114 |
115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /www/libs/MultipleModelUtil.js: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // 4 | // Permission to use, copy, modify, and distribute this software in 5 | // object code form for any purpose and without fee is hereby granted, 6 | // provided that the above copyright notice appears in all copies and 7 | // that both that copyright notice and the limited warranty and 8 | // restricted rights notice below appear in all supporting 9 | // documentation. 10 | // 11 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. 12 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF 13 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. 14 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE 15 | // UNINTERRUPTED OR ERROR FREE. 16 | // 17 | // Utility Class for loading models in sequence for Forge Viewer 18 | // by Eason Kang - Autodesk Developer Network (ADN) 19 | // 20 | class MultipleModelUtil { 21 | /** 22 | * @param {Viewer3D} viewer The Forge Viewer instance 23 | * @constructor 24 | */ 25 | constructor( viewer ) { 26 | this.viewer = viewer; 27 | } 28 | 29 | /** 30 | * Process Forge URNs 31 | * @param {Object[]} data Model data to be loaded, e.g. [ { name: 'house.rvt', urn: 'dXJuOmFkc2sub2JqZWN0c....' } ] 32 | * @returns {Promise} 33 | */ 34 | processModels( data ) { 35 | //process each promise 36 | //refer to http://jsfiddle.net/jfriend00/h3zaw8u8/ 37 | const promisesInSequence = ( tasks, callback ) => { 38 | const results = []; 39 | return tasks.reduce( ( p, item ) => { 40 | return p.then( () => { 41 | return callback( item ).then( ( data ) => { 42 | results.push( data ); 43 | return results; 44 | }); 45 | }); 46 | }, Promise.resolve()); 47 | }; 48 | 49 | //start to process 50 | return promisesInSequence( data, ( d ) => this.loadDocumentPromised( d ) ); 51 | } 52 | 53 | /** 54 | * Promised function for loading Forge derivative manifest 55 | * @param {Object} data Model data to be loaded, e.g. { name: 'house.rvt', urn: 'dXJuOmFkc2sub2JqZWN0c....' } 56 | * @returns {Promise} Loaded viewer model 57 | */ 58 | loadDocumentPromised( data ) { 59 | return new Promise(( resolve, reject ) => { 60 | 61 | const onDocumentLoadSuccess = ( doc ) => { 62 | console.log( `%cDocument for \`${data.name}\` Load Succeeded!`, 'color: blue' ); 63 | 64 | // Load model 65 | this.loadModelPromised( 66 | data, 67 | doc, 68 | onLoadModelSuccess, 69 | onLoadModelError 70 | ); 71 | } 72 | 73 | const onDocumentLoadFailure = ( error ) => { 74 | console.error( `Document for \`${data.name}\` Load Failure, error: \`${error}\`` ); 75 | } 76 | 77 | const onLoadModelSuccess = ( model ) => { 78 | console.log( `%cModel for \`${data.name}\` Load Succeeded!`, 'color: blue' ); 79 | 80 | //build a map from clashDocId with viewer model 81 | let docsMap = global_msSet._docsMap 82 | let filter = docsMap.filter(function(data){ 83 | return data.urn == 'urn:' + model.myData.urn 84 | }) 85 | //the document must exist in the docsMap 86 | //store the model with this clashDocId 87 | if(filter && filter.length>0) 88 | { 89 | global_forgeViewer._clashDocToModel[filter[0].clashDocId] ={} 90 | global_forgeViewer._clashDocToModel[filter[0].clashDocId].model = model 91 | global_forgeViewer._clashDocToModel[filter[0].clashDocId].name = filter[0].name 92 | } 93 | 94 | this.viewer.addEventListener( 95 | Autodesk.Viewing.GEOMETRY_LOADED_EVENT, 96 | onGeometriesLoaded 97 | ); 98 | } 99 | 100 | const onLoadModelError = ( error ) => { 101 | const msg = `Model for \`${data.name}\` Load Failure, error: \`${error}\``; 102 | console.error( msg ); 103 | 104 | reject( msg ); 105 | } 106 | 107 | const onGeometriesLoaded = ( event ) => { 108 | this.viewer.removeEventListener( 109 | Autodesk.Viewing.GEOMETRY_LOADED_EVENT, 110 | onGeometriesLoaded 111 | ); 112 | 113 | const msg = `Geometries for \`${data.name}\` Loaded`; 114 | 115 | console.log( `%c${msg}`, 'color: blue' ); 116 | resolve( { msg, model: event.model } ); 117 | } 118 | 119 | // Main: Load Forge derivative manifest 120 | Autodesk.Viewing.Document.load( 121 | data.urn, 122 | onDocumentLoadSuccess, 123 | onDocumentLoadFailure 124 | ); 125 | }); 126 | } 127 | 128 | /** 129 | * Promised function for loading model from the Forge derivative manifest. 130 | * By default, it loads the first model only. 131 | * @param {Document} doc Forge derivative manifest representing the model document 132 | * @param {Function} onLoadModelSuccess Success callback function that will be called while the model was loaded by the Forge Viewer. 133 | * @param {Function} onLoadModelError Error callback function that will be called while loading model was failed. 134 | */ 135 | loadModelPromised( data, doc, onLoadModelSuccess, onLoadModelError ) { 136 | const rootItem = doc.getRoot(); 137 | const filter = { type: 'geometry', role: '3d' }; 138 | const viewables = rootItem.search( filter ); 139 | 140 | if( viewables.length === 0 ) { 141 | return onLoadModelError( 'Document contains no viewables.' ); 142 | } 143 | 144 | // specific viewable as the loading target 145 | var initialViewable = (data.viewableId ? 146 | rootItem.findByGuid(data.viewableId) : 147 | doc.getRoot().getDefaultGeometry()); 148 | 149 | //const initialViewable = viewables[0]; 150 | 151 | const loadOptions = { 152 | modelNameOverride: data.name 153 | }; 154 | 155 | const viewer = this.viewer; 156 | 157 | // If no model was loaded, start the viewer and load model together 158 | if( !viewer.model && !viewer.started ) { 159 | return viewer.startWithDocumentNode( doc, initialViewable, loadOptions ) 160 | .then( onLoadModelSuccess ) 161 | .catch( onLoadModelError ); 162 | } 163 | 164 | if( viewer.model ) { 165 | loadOptions.globalOffset = viewer.model.getData().globalOffset; 166 | loadOptions.keepCurrentModels = true; 167 | loadOptions.applyScaling = viewer.getVisibleModels()[0].getDisplayUnit(); 168 | } 169 | 170 | viewer.loadDocumentNode( doc, initialViewable, loadOptions ) 171 | .then( onLoadModelSuccess ) 172 | .catch( onLoadModelError ); 173 | } 174 | } -------------------------------------------------------------------------------- /www/libs/bootstrap-notify.min.js: -------------------------------------------------------------------------------- 1 | /* Project: Bootstrap Growl = v3.1.3 | Description: Turns standard Bootstrap alerts into "Growl-like" notifications. | Author: Mouse0270 aka Robert McIntosh | License: MIT License | Website: https://github.com/mouse0270/bootstrap-growl */ 2 | !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t("object"==typeof exports?require("jquery"):jQuery)}(function(t){function e(e,i,n){var i={content:{message:"object"==typeof i?i.message:i,title:i.title?i.title:"",icon:i.icon?i.icon:"",url:i.url?i.url:"#",target:i.target?i.target:"-"}};n=t.extend(!0,{},i,n),this.settings=t.extend(!0,{},s,n),this._defaults=s,"-"==this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),this.init()}var s={element:"body",position:null,type:"info",allow_dismiss:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:''};String.format=function(){for(var t=arguments[0],e=1;e .progress-bar').removeClass("progress-bar-"+t.settings.type),t.settings.type=i[e],this.$ele.addClass("alert-"+i[e]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+i[e]);break;case"icon":var n=this.$ele.find('[data-notify="icon"]');"class"==t.settings.icon_type.toLowerCase()?n.removeClass(t.settings.content.icon).addClass(i[e]):(n.is("img")||n.find("img"),n.attr("src",i[e]));break;case"progress":var a=t.settings.delay-t.settings.delay*(i[e]/100);this.$ele.data("notify-delay",a),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i[e]).css("width",i[e]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",i[e]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",i[e]);break;default:this.$ele.find('[data-notify="'+e+'"]').html(i[e])}var o=this.$ele.outerHeight()+parseInt(t.settings.spacing)+parseInt(t.settings.offset.y);t.reposition(o)},close:function(){t.close()}}},buildNotify:function(){var e=this.settings.content;this.$ele=t(String.format(this.settings.template,this.settings.type,e.title,e.message,e.url,e.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append('Notify Icon')},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1}),this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},placement:function(){var e=this,s=this.settings.offset.y,i={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},n=!1,a=this.settings;switch(t('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return s=Math.max(s,parseInt(t(this).css(a.placement.from))+parseInt(t(this).outerHeight())+parseInt(a.spacing))}),1==this.settings.newest_on_top&&(s=this.settings.offset.y),i[this.settings.placement.from]=s+"px",this.settings.placement.align){case"left":case"right":i[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":i.left=0,i.right=0}this.$ele.css(i).addClass(this.settings.animate.enter),t.each(Array("webkit","moz","o","ms",""),function(t,s){e.$ele[0].style[s+"AnimationIterationCount"]=1}),t(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(s=parseInt(s)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(s)),t.isFunction(e.settings.onShow)&&e.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(){n=!0}).one(this.animations.end,function(){t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)}),setTimeout(function(){n||t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)},600)},bind:function(){var e=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){e.close()}),this.$ele.mouseover(function(){t(this).data("data-hover","true")}).mouseout(function(){t(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){e.$ele.data("notify-delay",e.settings.delay);var s=setInterval(function(){var t=parseInt(e.$ele.data("notify-delay"))-e.settings.timer;if("false"===e.$ele.data("data-hover")&&"pause"==e.settings.mouse_over||"pause"!=e.settings.mouse_over){var i=(e.settings.delay-t)/e.settings.delay*100;e.$ele.data("notify-delay",t),e.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i).css("width",i+"%")}t<=-e.settings.timer&&(clearInterval(s),e.close())},e.settings.timer)}},close:function(){var e=this,s=parseInt(this.$ele.css(this.settings.placement.from)),i=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),e.reposition(s),t.isFunction(e.settings.onClose)&&e.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(){i=!0}).one(this.animations.end,function(){t(this).remove(),t.isFunction(e.settings.onClosed)&&e.settings.onClosed.call(this)}),setTimeout(function(){i||(e.$ele.remove(),e.settings.onClosed&&e.settings.onClosed(e.$ele))},600)},reposition:function(e){var s=this,i='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',n=this.$ele.nextAll(i);1==this.settings.newest_on_top&&(n=this.$ele.prevAll(i)),n.each(function(){t(this).css(s.settings.placement.from,e),e=parseInt(e)+parseInt(s.settings.spacing)+t(this).outerHeight()})}}),t.notify=function(t,s){var i=new e(this,t,s);return i.notify},t.notifyDefaults=function(e){return s=t.extend(!0,{},s,e)},t.notifyClose=function(e){"undefined"==typeof e||"all"==e?t("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):t('[data-notify-position="'+e+'"]').find('[data-notify="dismiss"]').trigger("click")}}); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | This repository has been deprecated and migrated to https://github.com/autodesk-platform-services/aps-clash-data-view 3 | 4 | # BIM 360 Model Coordination: Clash View Basic Sample 5 | 6 | [![node](https://img.shields.io/badge/nodejs-v10+-yellow.svg)](https://nodejs.org) 7 | [![npm](https://img.shields.io/badge/npm-v6+-yellow.svg)](https://www.npmjs.com/) 8 | 9 | [![oAuth2](https://img.shields.io/badge/oAuth2-v1-green.svg)](https://forge.autodesk.com/en/docs/oauth/v2/overview/) 10 | [![Data-Management](https://img.shields.io/badge/Data%20Management-v1-green.svg)](https://forge.autodesk.com/en/docs/data/v2/developers_guide/overview/) 11 | [![Viewer](https://img.shields.io/badge/Viewer-v7.25-green.svg)](https://forge.autodesk.com/en/docs/viewer/v7/developers_guide/overview/) 12 | [![BIM-360](https://img.shields.io/badge/BIM%20360-v1-green.svg)](https://forge.autodesk.com/en/docs/bim360/v1/overview/introduction/) 13 | 14 | 15 | [![BIM 360 Model Set](https://img.shields.io/badge/BIM%20360%20Model%20Set-3.0.65-orange)](https://www.npmjs.com/package/forge-bim360-modelcoordination-modelset) 16 | [![BIM 360 Clash](https://img.shields.io/badge/BIM%20360%20Clash-3.3.27-orange)](https://www.npmjs.com/package/forge-bim360-modelcoordination-clash) 17 | 18 | [![License](http://img.shields.io/:license-MIT-red.svg)](http://opensource.org/licenses/MIT) 19 | [![Level](https://img.shields.io/badge/Level-Intermediate-blue.svg)](http://developer.autodesk.com/) 20 | 21 | 22 | ## Description 23 | This repository demonstrates basic viewing of clash raw data by Model Coordination API. It lists all clash instances data, and allows the user to click one instance to highlight within Forge Viewer. 24 | 25 | > Note: The logic of this sample works for ModelSet which are created after Oct 1st,2019 26 | 27 | ## Thumbnail 28 | 29 | ![thumbnail](/thumbnail.png) 30 | 31 | ## Live version 32 | 33 | [bim360-clash-basic-view.herokuapp.com](https://bim360-clash-basic-view.herokuapp.com) 34 | 35 | > To use this sample with your BIM 360 you need to "Enable Custom Integrations". At the app top-right, click **Config** to get detailed steps. 36 | 37 | Watch [this video](https://youtu.be/flP7aEJpHAU) to learn how to use this demo. 38 | 39 | ## Demonstration 40 | 41 | To work with the sample, firstly upload some source models to BIM 360 folder, then create model set in Model Coordination module with this folder. Please refer to [BIM 360 Model Coordination documentation](http://help.autodesk.com/view/BIM360D/ENU/?guid=GUID-38CC3A1C-92FF-4682-847F-9CFAFCC4CCCE) for details. Check [Model Coordination Sample Files](https://github.com/xiaodongliang/Demo-Test-Sample-Files/tree/master/Model%20Coordination%20API) for testing RVT files, it includes two versions of models set. 42 | 43 | 1. After logging in, on top left of navigation panel, select one hub, then select one project. 44 | 2. After selecting one project, the active modelsets in this activeproject will be listed. 45 | 3. Click one modelset, all clash instances will be displayed. All documents of this modelset will also be loaded in Forge viewer 46 | 4. Select one clash, the corresponding clash will be highlighted in Forge viewer. 47 | 48 | 49 | ## Technology Architecture 50 | 51 | The sample firstly downloads the model set data and clash data of the selected project. 52 | 53 | ![Workflow](/help/workflow.png) 54 | 55 | The relationship of the data are demoed in the figure below: 56 | 57 | ![Relationship](/help/relationship.png) 58 | 59 | Based on the relationship, the code analyzes the data to build the mapping among the clash document, version URN and viewable guid etc. The mapping is save to **docsMap.json** 60 | 61 | ![Document Map](/help/docmap.png) 62 | 63 | The mapping of clash instances data and the clash table in the sample: 64 | 65 | ![Table](/help/table.png) 66 | 67 | 68 | # Setup 69 | 70 | ## Prerequisites 71 | 72 | 1. **BIM 360 Account**: must be an Account Admin to add the app custom integration, or be invited by an admin of a BIM 360 account. [Learn about provisioning](https://forge.autodesk.com/blog/bim-360-docs-provisioning-forge-apps). 73 | 2. **Forge Account**: Learn how to create a Forge Account, activate subscription and create an app at [this tutorial](http://learnforge.autodesk.io/#/account/). Get _Forge client id_, _Forge client secret_ and _Forge callback url_ and input them to [config.js](./server/config.js) 74 | 3. Create some [modelsets of Model Coordination](https://knowledge.autodesk.com/support/bim-360/learn-explore/caas/CloudHelp/cloudhelp/ENU/BIM360D-Model-Coordination/files/GUID-38CC3A1C-92FF-4682-847F-9CFAFCC4CCCE-html.html) in BIM 360. 75 | 4. **Node.js**: basic knowledge with [**Node.js**](https://nodejs.org/en/). 76 | 5. **JavaScript** basic knowledge with **jQuery** and **Bootstrap** 77 | 78 | ## Running locally 79 | 80 | 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): 81 | 82 | git clone https://github.com/Autodesk-Forge/forge-bim360-clashview 83 | 84 | Open the project folder in **Visual Studio Code**. Install the required packages, set the environment variables with your client ID & secret and finally start it. Via the command line, navigate to the folder where this repository was cloned and use the following: 85 | 86 | Mac OSX/Linux (Terminal) 87 | 88 | npm install 89 | export FORGE_CLIENT_ID=<> 90 | export FORGE_CLIENT_SECRET=<> 91 | export FORGE_CALLBACK_URL=<> 92 | npm start 93 | 94 | Windows (use **Node.js command line** from Start menu) 95 | 96 | npm install 97 | set FORGE_CLIENT_ID=<> 98 | set FORGE_CLIENT_SECRET=<> 99 | set FORGE_CALLBACK_URL=<> 100 | npm start 101 | 102 | Open the browser: [http://localhost:3000](http://localhost:3000). 103 | 104 | ## Deployment 105 | 106 | To deploy this application to Heroku, the **Callback URL** for Forge must use your `.herokuapp.com` address. After clicking on the button below, at the Heroku Create New App page, set your Client ID, Secret and Callback URL for Forge. 107 | 108 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 109 | 110 | Watch [this video](https://www.youtube.com/watch?v=Oqa9O20Gj0c) on how deploy samples to Heroku. 111 | 112 | ## Further Reading 113 | - [Model Coordination](https://forge.autodesk.com/en/docs/bim360/v1/overview/field-guide/model-coordination/) 114 | - [BIM 360 API](https://forge.autodesk.com/en/docs/bim360/v1/overview/) and [App Provisioning](https://forge.autodesk.com/blog/bim-360-docs-provisioning-forge-apps) 115 | - [Data Management API](https://forge.autodesk.com/en/docs/data/v2/overview/) 116 | - [Viewer](https://forge.autodesk.com/en/docs/viewer/v7) 117 | 118 | ## Tutorials 119 | - [Model Coordination API](https://forge.autodesk.com/en/docs/bim360/v1/tutorials/model-coordination) 120 | - [View BIM 360 Models](http://learnforge.autodesk.io/#/tutorials/viewhubmodels) 121 | 122 | ## Blogs: 123 | 124 | - [Forge Blog](https://forge.autodesk.com/categories/bim-360-api) 125 | - [Field of View](https://fieldofviewblog.wordpress.com/), a BIM focused blog 126 | 127 | 128 | ## Tips & Tricks 129 | 130 | - Since the clash data might be large, don't pull the file locally and then process it. Decompressing and streaming the results on the fly would also be recommended, as showned in this sample [utility.js](./server/utility.js) 131 | - To make a simple demo, this sample does not use database to manage the clash data. 132 | - On client (browser) side, it may be more effifient to manage the data by IndexDB if the app requires to perform various analysis in different browser sessions. 133 | 134 | 135 | ## Troubleshooting 136 | 137 | - **Cannot see my BIM 360 projects**: Make sure to provision the Forge App Client ID within the BIM 360 Account, [learn more here](https://forge.autodesk.com/blog/bim-360-docs-provisioning-forge-apps). This requires the Account Admin permission. 138 | 139 | - The code of highlighting objects within Forge Viewer requires the corresponding documents of one clash instance have been loaded. If not, the highlighting will not work, try again when the loading is completed. 140 | 141 | ## License 142 | 143 | This sample is licensed under the terms of the [MIT License](http://opensource.org/licenses/MIT). Please see the [LICENSE](LICENSE) file for full details. 144 | 145 | ## Written by 146 | 147 | Xiaodong Liang [@coldwood](https://twitter.com/coldwood), [Forge Partner Development](http://forge.autodesk.com) 148 | -------------------------------------------------------------------------------- /server/endpoints/mc.modelset.endpoints.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | const express = require('express'); 22 | const router = express.Router(); 23 | const utility = require("../utility") 24 | const analyze = require('../analyze'); 25 | 26 | const UserSession = require('../services/userSession'); 27 | const mcMSServices = require('../services/mc.modelset.services'); 28 | 29 | router.get('/mc/modelset/getModelSets/:mc_container_id', async (req, res, next) => { 30 | 31 | try { 32 | let userSession = new UserSession(req.session); 33 | if (!userSession.isAuthorized()) { 34 | console.log('getModelSets: authorization failed!') 35 | res.status(401).end('Please login first'); 36 | return; 37 | } 38 | const mc_container_id = req.params['mc_container_id'] 39 | 40 | let input = { 41 | oAuth:userSession.getUserServerOAuth(), 42 | credentials:userSession.getUserServerCredentials(), 43 | mc_container_id:mc_container_id 44 | } 45 | 46 | let mssRaw = await mcMSServices.getModelSets(input) 47 | if(!mssRaw){ 48 | console.log('getModelSets: get model sets failed!') 49 | res.json([]) //we tell the client no modelset with the project 50 | return 51 | } 52 | 53 | console.log('getModelSets: get model sets succeeded!') 54 | 55 | let promiseArr = mssRaw.modelSets.map(async (element, index) => { 56 | let ms = [] 57 | input.ms_id = element.modelSetId 58 | let r = await mcMSServices.getModelSet(input) 59 | if(!r.isDisabled){ 60 | ms.push({ms_id:element.modelSetId, 61 | ms_name:element.name, 62 | tipVersion:r.tipVersion}) 63 | } 64 | return ms 65 | }); 66 | 67 | return Promise.all(promiseArr).then((resultsArray) => { 68 | console.log('getModelSets: get each modelset succeeded.') 69 | const msArray = utility.flatDeep(resultsArray,Infinity) 70 | res.json(msArray) 71 | }).catch(function (err) { 72 | console.log(`getModelSets: get each modelset failed.${err}`) 73 | res.json([]) 74 | }) 75 | } catch(e) { 76 | // here goes out error handler 77 | console.error('getModelSets failed: ') 78 | res.status(500).end() 79 | } 80 | }); 81 | 82 | router.get('/mc/modelset/getModelSet/:mc_container_id/:ms_id', async (req, res, next) => { 83 | 84 | try { 85 | let userSession = new UserSession(req.session); 86 | if (!userSession.isAuthorized()) { 87 | res.status(401).end('Please login first'); 88 | return; 89 | } 90 | const mc_container_id = req.params['mc_container_id'] 91 | const ms_id = req.params['ms_id'] 92 | 93 | 94 | let input = { 95 | oAuth:userSession.getUserServerOAuth(), 96 | credentials:userSession.getUserServerCredentials(), 97 | mc_container_id:mc_container_id, 98 | ms_id:ms_id 99 | } 100 | 101 | let msRes = await mcMSServices.getModelSet(input) 102 | res.json(msRes) 103 | 104 | } catch(e) { 105 | // here goes out error handler 106 | console.log('getModelSet failed: '+ e.message) 107 | res.status(500).end() 108 | } 109 | 110 | }); 111 | 112 | router.get('/mc/modelset/getModelSetVersion/:mc_container_id/:ms_id/:ms_v_id', async (req, res, next) => { 113 | 114 | try { 115 | let userSession = new UserSession(req.session); 116 | if (!userSession.isAuthorized()) { 117 | res.status(401).end('Please login first'); 118 | return; 119 | } 120 | const mc_container_id = req.params['mc_container_id'] 121 | const ms_id = req.params['ms_id'] 122 | const ms_v_id = req.params['ms_v_id'] 123 | 124 | let input = { 125 | oAuth:userSession.getUserServerOAuth(), 126 | credentials:userSession.getUserServerCredentials(), 127 | mc_container_id:mc_container_id, 128 | ms_id:ms_id, 129 | ms_v_id:ms_v_id 130 | } 131 | 132 | let msVsRes = await mcMSServices.getModelSetVersion(input) 133 | res.json(msVsRes) 134 | 135 | } catch(e) { 136 | // here goes out error handler 137 | console.log('getModelSetVersion failed: '+ e.message) 138 | res.status(500).end() 139 | } 140 | 141 | }); 142 | 143 | 144 | router.get('/mc/modelset/prepareClashData/:mc_container_id/:ms_id/:ms_v_id/:toRefresh', async (req, res, next) => { 145 | 146 | try { 147 | const userSession = new UserSession(req.session) 148 | if (!userSession.isAuthorized()) { 149 | console.log('no valid authorization!') 150 | res.status(401).end('Please login first') 151 | return 152 | } 153 | 154 | var jobId = utility.randomValueBase64(6) 155 | utility.storeStatus(jobId,'running') 156 | res.status(200).json({jobId:jobId}) 157 | 158 | var mc_container_id = req.params['mc_container_id'] 159 | var ms_id = req.params['ms_id'] 160 | var ms_v_id = req.params['ms_v_id'] 161 | var toRefresh = req.params['toRefresh'] == 'true' 162 | 163 | let input = { 164 | oAuth:userSession.getUserServerOAuth(), 165 | credentials:userSession.getUserServerCredentials(), 166 | mc_container_id:mc_container_id, 167 | ms_id:ms_id, 168 | ms_v_id:ms_v_id 169 | } 170 | analyze.prepareClashData(input,jobId,toRefresh) 171 | 172 | } catch(e) { 173 | console.log('prepareClashData failed: '+ e.message) 174 | res.status(500).end('prepareClashData failed!') 175 | } 176 | }) 177 | 178 | router.get('/mc/modelset/getPrepareStatus/:jobId', async (req, res, next) => { 179 | 180 | try { 181 | const jobId = req.params['jobId'] 182 | const status = utility.readStatus(jobId) 183 | 184 | if(status == 'succeeded') 185 | // now delete this status file 186 | utility.deleteStatus(jobId) 187 | 188 | if(status) 189 | res.status(200).json({status:status}); 190 | else 191 | res.status(500).json({status:'failed'}); 192 | } catch(e) { 193 | console.log('getPrepareStatus failed: '+ e.message) 194 | res.status(500).end('getPrepareStatus failed!') 195 | } 196 | }) 197 | 198 | router.get('/mc/modelset/getDocMap/:mc_container_id/:ms_id/:ms_v_id', async (req, res, next) => { 199 | 200 | try { 201 | const mc_container_id = req.params['mc_container_id'] 202 | const ms_id = req.params['ms_id'] 203 | const ms_v_id = req.params['ms_v_id'] 204 | 205 | const doc_map = analyze.getDocsMap(mc_container_id,ms_id, ms_v_id) 206 | if(!doc_map) 207 | res.status(500).end('doc map is null') 208 | else 209 | res.status(200).json(doc_map) 210 | } catch(e) { 211 | console.log('getDocMap failed: '+ e.message) 212 | res.status(500).end('getDocMap failed!') 213 | } 214 | }) 215 | 216 | router.get('/mc/modelset/getDocName/:mc_container_id/:ms_id/:ms_v_id/:clashDocId', async (req, res, next) => { 217 | 218 | try { 219 | const mc_container_id = req.params['mc_container_id'] 220 | const ms_id = req.params['ms_id'] 221 | const ms_v_id = req.params['ms_v_id'] 222 | const clashDocId = req.params['clashDocId'] 223 | 224 | const doc_map = analyze.getDocsMap(mc_container_id,ms_id, ms_v_id) 225 | if(!doc_map){ 226 | res.json({error:'doc map is null!'}) 227 | return 228 | } 229 | 230 | let filter = doc_map.filter(function(data){ 231 | return data.clashDocId == clashDocId 232 | }) 233 | 234 | if(filter && filter.length>0){ 235 | res.json({content:filter[0].name}) 236 | }else{ 237 | res.json({error:'no documnt!'}) 238 | } 239 | 240 | } catch(e) { 241 | console.log('getDocName failed: '+ e.message) 242 | res.json({error:'get doc name failed!'}) 243 | } 244 | }) 245 | 246 | 247 | module.exports = router 248 | 249 | 250 | -------------------------------------------------------------------------------- /server/analyze.js: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////// 2 | // Copyright (c) Autodesk, Inc. All rights reserved 3 | // Written by Forge 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 | 'use strict'; 20 | 21 | const fs = require("fs") 22 | const mkdir = require('mkdirp') 23 | const utility = require("./utility") 24 | const mcMSServices = require('./services/mc.modelset.services') 25 | const mcClashServices = require('./services/mc.clash.services'); 26 | 27 | 28 | const clashDataFolder = './ClashData/' 29 | var DataNameEnum = { 30 | MS_VERSIONS: 'modelset-version.json', 31 | CLASH_TESTS: 'clash-tests.json', 32 | SCOPE_INSTANCE: 'scope-version-clash-instance.2.0.0.json.gz', 33 | SCOPE_CLASH: 'scope-version-clash.2.0.0.json.gz', 34 | SCOPE_DOCUMENTS: 'scope-version-document.2.0.0.json.gz', 35 | CLASH_ISSUES: 'clash-issues.json', 36 | DOCUMENTS_MAP: 'documents-map.json' 37 | }; 38 | 39 | if (!fs.existsSync(clashDataFolder)) 40 | mkdir.mkdirp(clashDataFolder, (err) => { if (!err) console.log('folder ./ClashData/ is created') }) 41 | const statusFolder = './Status/' 42 | if (!fs.existsSync(statusFolder)) 43 | mkdir.mkdirp(statusFolder, (err) => { if (!err) console.log('folder ./Status/ is created') }) 44 | 45 | module.exports = { 46 | prepareClashData: prepareClashData, 47 | buildDocsMap: buildDocsMap, 48 | getRawClashData: getRawClashData, 49 | getDocsMap: getDocsMap 50 | } 51 | 52 | async function prepareClashData(input, jobId) { 53 | 54 | try { 55 | const mc_container_id = input.mc_container_id 56 | const ms_id = input.ms_id 57 | const ms_v_id = input.ms_v_id 58 | 59 | //create a folder to store the clash data for this modelset version 60 | const thisClashVersionFolder = clashDataFolder + mc_container_id + '/' + ms_id + '/' + ms_v_id + '/' 61 | if (!fs.existsSync(thisClashVersionFolder)) { 62 | fs.mkdirSync(thisClashVersionFolder, { recursive: true }) 63 | } 64 | 65 | //the data will be produced if it is missing.. 66 | await getModelSetVersionData(thisClashVersionFolder, input) 67 | await getClashData(thisClashVersionFolder, input) 68 | await buildDocsMap(thisClashVersionFolder) 69 | utility.storeStatus(jobId, 'succeeded') 70 | 71 | } 72 | catch (ex) { 73 | console.log(ex.toString()) 74 | utility.storeStatus(jobId, 'failed') 75 | } 76 | } 77 | 78 | async function getModelSetVersionData(folder, input) { 79 | 80 | if (fs.existsSync(folder + DataNameEnum.MS_VERSIONS)) { 81 | //model version info is available 82 | console.log(DataNameEnum.MS_VERSIONS + ' are available at' + folder) 83 | } else { 84 | //get versions info of one specific model set 85 | const msversions = await mcMSServices.getModelSetVersion(input) 86 | await utility.saveJsonObj(folder, DataNameEnum.MS_VERSIONS, msversions) 87 | console.log(DataNameEnum.MS_VERSIONS + ' downloaded at ' + folder) 88 | } 89 | } 90 | 91 | async function getClashData(folder, input) { 92 | 93 | if (fs.existsSync(folder + DataNameEnum.CLASH_TESTS) && 94 | fs.existsSync(folder + DataNameEnum.SCOPE_DOCUMENTS) && 95 | fs.existsSync(folder + DataNameEnum.SCOPE_INSTANCE) && 96 | fs.existsSync(folder + DataNameEnum.SCOPE_CLASH)) { 97 | //all clash data are available 98 | console.log(' all clash data are available at' + folder) 99 | 100 | return 101 | } 102 | 103 | const clashTestsRes = await mcClashServices.getClashTests(input) 104 | //one model set version with one clash test 105 | const oneTest = clashTestsRes.tests.filter(function (item) { 106 | return item.modelSetVersion === parseInt(input.ms_v_id); 107 | }) 108 | 109 | // one clash test data 110 | if (oneTest && oneTest.length > 0) { 111 | 112 | await utility.saveJsonObj(folder, 'clash-tests.json', oneTest) 113 | console.log(DataNameEnum.CLASH_TESTS + ' downloaded at ' + folder) 114 | 115 | let testid = oneTest[0].id 116 | input.testid = testid 117 | let testRes = await mcClashServices.getClashTestResources(input) 118 | for (let index in testRes.resources) { 119 | let resurl = testRes.resources[index].url 120 | let headers = testRes.resources[index].headers 121 | let filename = testRes.resources[index].type + '.' + testRes.resources[index].extension 122 | let downloadRes = await utility.downloadResources(resurl, headers, folder, filename) 123 | console.log(' Clash data downloaded at ' + folder) 124 | } 125 | } 126 | } 127 | 128 | //build map with document displayname, index string and clash document id 129 | async function buildDocsMap(folder) { 130 | 131 | if (fs.existsSync(folder + DataNameEnum.DOCUMENTS_MAP)) { 132 | //document map is available 133 | console.log(DataNameEnum.DOCUMENTS_MAP + ' are available at' + folder) 134 | return 135 | } 136 | 137 | const msversionsBuffer = fs.readFileSync(folder + DataNameEnum.MS_VERSIONS) 138 | const msversionsJson = JSON.parse(msversionsBuffer) 139 | 140 | const successDocs = msversionsJson.documentVersions.filter(function (data) { 141 | return data.documentStatus === 'Succeeded' 142 | }) 143 | 144 | const clashDocumentBuffer = fs.readFileSync(folder + DataNameEnum.SCOPE_DOCUMENTS) 145 | const clashDocumentJson = JSON.parse(clashDocumentBuffer).documents 146 | 147 | let doc_map = [] 148 | let successMap = true 149 | 150 | for (let i in successDocs) { 151 | 152 | let oneItem = {} 153 | //docNamePair is modelset version detail info 154 | //it contains display name, version urn and other data 155 | const docName = successDocs[i].displayName 156 | oneItem.name = docName 157 | oneItem.versionUrn = successDocs[i].versionUrn 158 | oneItem.viewableGuid = successDocs[i].viewableGuid 159 | oneItem.viewableId = successDocs[i].viewableId 160 | oneItem.lineageUrn = successDocs[i].documentLineage.lineageUrn 161 | 162 | const buff = new Buffer.from(successDocs[i].bubbleUrn); 163 | //remove padding = of based64 code 164 | oneItem.urn = 'urn:' + buff.toString('base64').replace('/', '_').trim('=').split('=').join('') 165 | //map clash doc id (in number) with the document 166 | let filter = clashDocumentJson.filter( 167 | function (data) { 168 | return data.urn === successDocs[i].versionUrn 169 | } 170 | ); 171 | if (filter && filter.length > 0) 172 | oneItem.clashDocId = filter[0].id 173 | else { 174 | console.log(docName + ' clash document id is not found') 175 | successMap = false 176 | break 177 | } 178 | 179 | doc_map.push(oneItem) 180 | } 181 | 182 | if (successMap) { 183 | await utility.saveJsonObj(folder, DataNameEnum.DOCUMENTS_MAP, doc_map) 184 | console.log(DataNameEnum.DOCUMENTS_MAP + ' downloaded at ' + folder) 185 | return doc_map 186 | } 187 | else 188 | console.log(DataNameEnum.DOCUMENTS_MAP + ' FAILED at ' + folder) 189 | return null 190 | } 191 | 192 | 193 | function getDocsMap(mc_container_id, ms_id, ms_v_id) { 194 | try { 195 | const thisClashVersionFolder = clashDataFolder + mc_container_id + '/' + ms_id + '/' + ms_v_id + '/' 196 | if (!fs.existsSync(thisClashVersionFolder)) 197 | return null 198 | 199 | const docsMapBuffer = fs.readFileSync(thisClashVersionFolder + DataNameEnum.DOCUMENTS_MAP) 200 | const docsMapObj = JSON.parse(docsMapBuffer) 201 | 202 | return docsMapObj 203 | } 204 | catch (ex) { 205 | return null 206 | } 207 | } 208 | 209 | function getRawClashData(mc_container_id, ms_id, ms_v_id) { 210 | try { 211 | const thisClashVersionFolder = clashDataFolder + mc_container_id + '/' + ms_id + '/' + ms_v_id + '/' 212 | if (!fs.existsSync(thisClashVersionFolder)) 213 | return null 214 | 215 | var clashInstanceBuffer = fs.readFileSync(thisClashVersionFolder + DataNameEnum.SCOPE_INSTANCE) 216 | var clashInsJsonObj = JSON.parse(clashInstanceBuffer) 217 | 218 | var clashBuffer = fs.readFileSync(thisClashVersionFolder + DataNameEnum.SCOPE_CLASH) 219 | var clashJsonObj = JSON.parse(clashBuffer) 220 | 221 | var testBuffer = fs.readFileSync(thisClashVersionFolder + DataNameEnum.CLASH_TESTS) 222 | var testJsonObj = JSON.parse(testBuffer) 223 | 224 | //send compressed data 225 | const inputJson = { testJsonObj: testJsonObj, clashInsJsonObj: clashInsJsonObj, clashJsonObj: clashJsonObj } 226 | const compressedStreaming = utility.compressStream(inputJson) 227 | 228 | return compressedStreaming 229 | } 230 | catch (ex) { 231 | return null 232 | } 233 | } 234 | 235 | 236 | --------------------------------------------------------------------------------