├── .gitignore ├── LICENSE ├── README.md └── docs ├── app.js ├── gltf ├── output.0.bin ├── output.1.bin ├── output.2.bin ├── output.gltf ├── output.metadata.json └── props.db └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | .DS_Store 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 wallabyway 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # View Revit files inside MapBox 2 | View Revit models inside Mapbox, and click on Revit properties. 3 | 4 | ## DEMO: https://wallabyway.github.io/mapboxRevit/ 5 | 6 | 7 | 8 | ### Notes 9 | 10 | - Click on the model to view properties (via the Forge API. Alternatively, use props.db sqlite DB file instead) 11 | - Use svf-extract to convert Revit file to glTF (see glTF folder) 12 | 13 | 14 | ### Bugs 15 | - Currently the mapbox raycaster does not match correctly, so it cannot get the correct DBID from the gltf.node.name 16 | 17 | 18 | ![mapbox-static](https://user-images.githubusercontent.com/440241/65015291-ef8ade80-d8d5-11e9-99e9-d3a5d221f6ca.jpg) 19 | 20 | 21 | ### Other examples of MapBox with Revit data: 22 | 23 | - Perkins-Will : http://research.perkinswill.com 24 | - Archistar.AI : http://archistar.ai 25 | - Ridley-Willow : https://www.willowinc.com 26 | 27 | ### References 28 | 29 | - geo-location in LMV blog: https://forge.autodesk.com/blog/mini-map-geolocation-extension 30 | - design Automation for Revit (rooms and Spaces): https://github.com/wallabyway/rooms-spaces-revit-plugin 31 | - https://github.com/wallabyway/propertyServer/blob/master/pipeline.md 32 | 33 | ## Property SQLite Database usage 34 | 35 | The demo is currently querying Forge service to retrieve meta-data given the dbID selected. 36 | 37 | Alternatively, you can use the `props.db` sqlite database file, located in the folder `/gltf`. Load the file in sqlite and query for properties associated with the dbID you click on. 38 | 39 | ### Example 40 | 41 | We will replicate the property panel in Forge Viewer. 42 | 43 | ![properties](https://user-images.githubusercontent.com/440241/65016868-2a8f1100-d8da-11e9-9461-1f4905ca679b.jpg) 44 | 45 | 1. We selected the 'wall' and it's DBID is 2594. I used `NOP_VIEWER.getSelection()` to get the value. 46 | 47 | 48 | 2. Now open props.db file in a SQLite browser. 49 | 50 | 3. To filter on `ids.id = 2594` use this SQL command: 51 | 52 | ``` 53 | select ids.external_id, attrs.name, attrs.category, attrs.data_type, vals.value from _objects_eav eavs 54 | left join _objects_id ids on ids.id = eavs.entity_id 55 | left join _objects_attr attrs on attrs.id = eavs.attribute_id 56 | left join _objects_val vals on vals.id = eavs.value_id where ids.id = 2594 order by eavs.entity_id 57 | ``` 58 | 59 | 4. This returns the results: 60 | 61 | ``` 62 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 parent __parent__ 11 2107 63 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 instanceof_objid __instanceof__ 11 2107 64 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Level __internalref__ 11 5 65 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Location Line Constraints 20 Wall Centerline 66 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Base Constraint Constraints 20 Level 2 67 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Base Offset Constraints 3 -500.0 68 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Base is Attached Constraints 1 0.0 69 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Base Extension Distance Constraints 3 0.0 70 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Top Constraint Constraints 20 Up to level: Roof Line 71 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Unconnected Height Constraints 3 3500.0 72 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Top Offset Constraints 3 0.0 73 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Top is Attached Constraints 1 0.0 74 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Top Extension Distance Constraints 3 0.0 75 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Room Bounding Constraints 1 1 76 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Related to Mass Constraints 1 0.0 77 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Structural Structural 1 0.0 78 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Enable Analytical Model Structural 1 0.0 79 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Structural Usage Structural 20 Non-bearing 80 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Length Dimensions 3 19702.0 81 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Area Dimensions 3 43.2957000000004 82 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Volume Dimensions 3 8.15002486322177 83 | c85e5be0-d8d5-4148-836f-b55e711ef373-00068ac9 Type Name Identity Data 20 SIP 202mm Wall - conc clad 84 | 85 | ``` 86 | 87 | ...and matches what's in the Forge viewer's property panel (see above). 88 | 89 | Also note that the glTF converted files contain additional meta-data. 90 | 91 | - the gltf node 'name' is a string version of the dbID 92 | - the [metadata.json](https://github.com/wallabyway/mapboxRevit/blob/master/docs/gltf/output.metadata.json) file contains the Revit files [lat, long], which you can feed into Mapbox for positioning of the model. 93 | -------------------------------------------------------------------------------- /docs/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | mapboxgl.accessToken = 'pk.eyJ1Ijoid2FsbGFieXdheSIsImEiOiJjazBuaDQ5OGgxaHFwM2NvMm8wN2Ewb2xpIn0.1XKDCgUA5YKI_U9NGh4fqg'; 4 | 5 | // parameters to ensure the model is georeferenced correctly on the map 6 | let modelAltitude = 0; 7 | let modelRotate = [0, 0, -Math.PI / 10]; 8 | let modelOrigin = [-71.059505, 42.349448]; //use gltf/output.metadata.json -> metadata.georeference.positionLL84[lat,long] 9 | const modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(modelOrigin, modelAltitude); 10 | 11 | 12 | // Class to load a glTF file into mapBox 13 | // https://docs.mapbox.com/mapbox-gl-js/example/add-3d-model/ 14 | // ---------------------------------------------------------- 15 | 16 | class glTFLayer { 17 | constructor(modelAsMercatorCoordinate) { 18 | this.raycaster = new THREE.Raycaster(); 19 | this.mouse = new THREE.Vector2(); 20 | this.toast = document.getElementById('toast') 21 | 22 | this.id = '3d-model'; 23 | this.type = 'custom'; 24 | this.renderingMode = '3d'; 25 | 26 | // transformation parameters to position, rotate and scale the 3D model onto the map 27 | this.modelTransform = { 28 | translateX: modelAsMercatorCoordinate.x, 29 | translateY: modelAsMercatorCoordinate.y, 30 | translateZ: modelAsMercatorCoordinate.z, 31 | rotateX: modelRotate[0], 32 | rotateY: modelRotate[1], 33 | rotateZ: modelRotate[2], 34 | // Since our 3D model is in real world meters, a scale transform needs to be applied since the CustomLayerInterface expects units in MercatorCoordinates. 35 | // 0.2 is a scale factor that should come from LMV 36 | scale: 0.2 * modelAsMercatorCoordinate.meterInMercatorCoordinateUnits() 37 | }; 38 | } 39 | 40 | showPropertiesFromForge(id) { 41 | const forgeURL = 'https://developer.api.autodesk.com'; 42 | const urn = "dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dnJwYXJ0eTEvcmFjX2FsbHZpZXdzMy5ydnQ"; 43 | const guid = "6c9aa7c5-0799-1c3c-a715-3ed2aafa21ab"; 44 | const token = _adsk.token.access_token; 45 | fetch(`${forgeURL}/modelderivative/v2/designdata/${urn}/metadata/${guid}/properties`, 46 | { mode: 'cors', headers: { Accept: 'application/json', Authorization:`Bearer ${token}` }}) 47 | .then( res => res.json()) 48 | .then( res => { 49 | //let prop = (res.data.collection.filter(i=>{return i.objectid==id}))[0]; 50 | // Choose a random DBId, since the raycaster is broken :-( 51 | let prop = res.data.collection[Math.round(Math.random()*100)]; 52 | toast.innerHTML = `properties:[ ${prop.name} ]`; 53 | toast.classList.add('show'); 54 | clearTimeout(this.tmout) 55 | this.tmout = setTimeout(function(){ toast.classList.remove('show') }, 3000); 56 | }); 57 | } 58 | 59 | onClick(e) { 60 | if (!this.camera) return; 61 | this.mouse.x = ( e.originalEvent.clientX / window.innerWidth ) * 2 - 1; 62 | this.mouse.y = - ( e.originalEvent.clientY / window.innerHeight ) * 2 + 1; 63 | this.raycaster.setFromCamera( this.mouse, this.camera ); 64 | let id = this.raycaster.intersectObjects(this.scene.children); 65 | this.showPropertiesFromForge(2891); 66 | 67 | // set bbox as 5px reactangle area around clicked point 68 | //var bbox = [[e.point.x - 5, e.point.y - 5], [e.point.x + 5, e.point.y + 5]]; 69 | } 70 | 71 | onAdd(map, gl) { 72 | this.camera = new THREE.PerspectiveCamera(); 73 | this.scene = new THREE.Scene(); 74 | 75 | // create two three.js lights to illuminate the model 76 | var directionalLight = new THREE.DirectionalLight(0xffffff); 77 | directionalLight.position.set(0, -70, 100).normalize(); 78 | this.scene.add(directionalLight); 79 | 80 | var directionalLight2 = new THREE.DirectionalLight(0xffffff); 81 | directionalLight2.position.set(0, 70, 100).normalize(); 82 | this.scene.add(directionalLight2); 83 | 84 | // use the three.js GLTF loader to add the 3D model to the three.js scene 85 | var loader = new THREE.GLTFLoader(); 86 | loader.load('gltf/output.gltf', (function(gltf) { 87 | this.scene.add(gltf.scene); 88 | }).bind(this)); 89 | this.map = map; 90 | 91 | // use the Mapbox GL JS map canvas for three.js 92 | this.renderer = new THREE.WebGLRenderer({ 93 | canvas: map.getCanvas(), 94 | context: gl, 95 | antialias: true 96 | }); 97 | 98 | this.renderer.autoClear = false; 99 | } 100 | 101 | render(gl, matrix) { 102 | var rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), this.modelTransform.rotateX); 103 | var rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), this.modelTransform.rotateY); 104 | var rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), this.modelTransform.rotateZ); 105 | var m = new THREE.Matrix4().fromArray(matrix); 106 | var l = new THREE.Matrix4().makeTranslation(this.modelTransform.translateX, this.modelTransform.translateY, this.modelTransform.translateZ) 107 | .scale(new THREE.Vector3(this.modelTransform.scale, -this.modelTransform.scale, this.modelTransform.scale)) 108 | .multiply(rotationX) 109 | .multiply(rotationY) 110 | .multiply(rotationZ); 111 | 112 | this.camera.projectionMatrix.elements = matrix; 113 | this.camera.projectionMatrix = m.multiply(l); 114 | this.renderer.state.reset(); 115 | this.renderer.render(this.scene, this.camera); 116 | } 117 | } 118 | 119 | 120 | // Class to display 3D extruded buildings and fly to position 121 | // https://docs.mapbox.com/mapbox-gl-js/example/3d-buildings/ 122 | // https://docs.mapbox.com/mapbox-gl-js/example/flyto-options/ 123 | // ---------------------------------------------------------- 124 | 125 | class buildingsLayer { 126 | constructor() { 127 | this.id = '3d-buildings'; 128 | this.source = 'composite'; 129 | this['source-layer'] = 'building'; 130 | this.filter = ['==', 'extrude', 'true']; 131 | this.type = 'fill-extrusion', 132 | this.minzoom = 15, 133 | this.paint = { 134 | 'fill-extrusion-color': '#aaa', 135 | 136 | // use an 'interpolate' expression to add a smooth transition effect to the 137 | // buildings as the user zooms in 138 | 'fill-extrusion-height': [ 139 | "interpolate", ["linear"], 140 | ["zoom"], 141 | 15, 0, 142 | 15.05, ["get", "height"] 143 | ], 144 | 'fill-extrusion-base': [ 145 | "interpolate", ["linear"], 146 | ["zoom"], 147 | 15, 0, 148 | 15.05, ["get", "min_height"] 149 | ], 150 | 'fill-extrusion-opacity': .6 151 | }; 152 | } 153 | 154 | flyToHome() { 155 | map.flyTo({ center: modelOrigin, zoom: 21, speed: 0.7 }); 156 | } 157 | 158 | onAdd(map, gl) { 159 | // Insert the layer beneath any symbol layer. 160 | var layers = map.getStyle().layers; 161 | 162 | var labelLayerId; 163 | for (var i = 0; i < layers.length; i++) { 164 | if (layers[i].type === 'symbol' && layers[i].layout['text-field']) { 165 | labelLayerId = layers[i].id; 166 | break; 167 | } 168 | } 169 | } 170 | } 171 | 172 | 173 | 174 | // create our mapbox with our 3d model as center 175 | // ---------------------------------------------------------- 176 | 177 | const map = new mapboxgl.Map({ 178 | container: 'map', 179 | style: 'mapbox://styles/mapbox/streets-v11', 180 | zoom: 18, 181 | center: modelOrigin, 182 | pitch: 60, 183 | antialias: true // create the gl context with MSAA antialiasing, so custom layers are antialiased 184 | }); 185 | 186 | 187 | // Load 3D glTF model 188 | const gltf = new glTFLayer( modelAsMercatorCoordinate ); 189 | map.on('load', function() { map.addLayer(gltf) }); 190 | map.on('click', function(e) { gltf.onClick(e) }); 191 | 192 | 193 | // Load 3D extruded buildings as layer 194 | const buildings = new buildingsLayer(); 195 | map.on('load', function() { map.addLayer(buildings) }); 196 | -------------------------------------------------------------------------------- /docs/gltf/output.0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wallabyway/mapboxRevit/2990b13db97e13f097ad303c21e6c5c86a3b3fc7/docs/gltf/output.0.bin -------------------------------------------------------------------------------- /docs/gltf/output.1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wallabyway/mapboxRevit/2990b13db97e13f097ad303c21e6c5c86a3b3fc7/docs/gltf/output.1.bin -------------------------------------------------------------------------------- /docs/gltf/output.2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wallabyway/mapboxRevit/2990b13db97e13f097ad303c21e6c5c86a3b3fc7/docs/gltf/output.2.bin -------------------------------------------------------------------------------- /docs/gltf/output.metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "metadata": { 4 | "double sided geometry": { 5 | "value": false 6 | }, 7 | "navigation hint": { 8 | "value": "Turntable" 9 | }, 10 | "world bounding box": { 11 | "minXYZ": [ 12 | -84.249969, 13 | -164.01239, 14 | -27.288076 15 | ], 16 | "maxXYZ": [ 17 | 81.21241, 18 | 66.841797, 19 | 35.568898 20 | ] 21 | }, 22 | "world up vector": { 23 | "XYZ": [ 24 | 0, 25 | 0, 26 | 1 27 | ] 28 | }, 29 | "world front vector": { 30 | "XYZ": [ 31 | 0, 32 | -1, 33 | 0 34 | ] 35 | }, 36 | "world north vector": { 37 | "XYZ": [ 38 | 0, 39 | 1, 40 | 0 41 | ] 42 | }, 43 | "distance unit": { 44 | "value": "foot" 45 | }, 46 | "default camera": { 47 | "index": 0 48 | }, 49 | "view to model transform": { 50 | "type": 4 51 | }, 52 | "georeference": { 53 | "positionLL84": [ 54 | -71.0335, 55 | 42.213, 56 | 0 57 | ] 58 | }, 59 | "custom values": { 60 | "angleToTrueNorth": 323, 61 | "refPointTransform": [ 62 | 0.798636, 63 | 0.601815, 64 | 0, 65 | -0.601815, 66 | 0.798636, 67 | 0, 68 | 0, 69 | 0, 70 | 1, 71 | -65.038051, 72 | -61.70702, 73 | 0 74 | ] 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /docs/gltf/props.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wallabyway/mapboxRevit/2990b13db97e13f097ad303c21e6c5c86a3b3fc7/docs/gltf/props.db -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | MapBox with Forge Services 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 |
19 |
20 | 29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | --------------------------------------------------------------------------------