├── .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 | 
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 | 
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 |