├── .classpath ├── .gitignore ├── .project ├── .settings ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.core.prefs ├── README.markdown ├── bimsurfer ├── lib │ ├── DRACOLoader.js │ ├── GLTFLoader.js │ ├── OrbitControls.js │ ├── StringView.js │ ├── arraybuffer-slice.js │ ├── domReady.js │ ├── draco_decoder.js │ ├── draco_decoder.wasm │ ├── draco_wasm_wrapper.js │ ├── es6-promise-3.2.2.min.js │ ├── require.js │ ├── svg-pan-zoom.js │ ├── text.js │ ├── three.js │ ├── xeogl.js │ └── xeogl.min.js └── src │ ├── AnnotationRenderer.js │ ├── Assets.js │ ├── BimServerGeometryLoader.js │ ├── BimServerModel.js │ ├── BimServerModelLoader.js │ ├── BimSurfer.js │ ├── DataInputStreamReader.js │ ├── DefaultMaterials.js │ ├── EventHandler.js │ ├── MeasurementCanvas.js │ ├── MetaDataRenderer.js │ ├── MultiModal.js │ ├── Notifier.js │ ├── PreloadQuery.js │ ├── Request.js │ ├── StaticTreeRenderer.js │ ├── Utils.js │ ├── svgViewer │ └── svgViewer.js │ ├── threeViewer │ └── threeViewer.js │ └── xeoViewer │ ├── controls │ ├── bimCameraControl.js │ └── cursors │ │ └── rotate.png │ ├── effects │ └── highlightEffect.js │ ├── entities │ ├── bimModel.js │ └── bimObject.js │ ├── geometry │ └── vectorTextGeometry.js │ ├── helpers │ └── bimBoundaryHelper.js │ ├── utils │ └── collection.js │ └── xeoViewer.js ├── css ├── metadata.css └── tree.css ├── examples ├── README.md ├── bimserver.html ├── build-gltf_app.js ├── css │ ├── apiref.css │ └── demo.css ├── gltf.html ├── gltf_app.js ├── gltf_app.min.js ├── js │ ├── app.js │ └── utils.js └── models │ ├── Duplex_A_20110907_optimized.bin │ ├── Duplex_A_20110907_optimized.glb │ ├── Duplex_A_20110907_optimized.gltf │ ├── Duplex_A_20110907_optimized.xml │ ├── Duplex_A_20110907_optimized0FS.glsl │ ├── Duplex_A_20110907_optimized0VS.glsl │ ├── Duplex_A_20110907_optimized1FS.glsl │ └── Duplex_A_20110907_optimized1VS.glsl ├── files └── gltf │ └── gearbox │ ├── gearbox_assy.bin │ └── gearbox_assy.gltf ├── index.html ├── license.txt ├── plugin ├── icon.png ├── plugin.xml └── version.properties └── pom.xml /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | BIMsurferV2 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.ui.externaltools.ExternalToolBuilder 10 | full,incremental, 11 | 12 | 13 | LaunchConfigHandle 14 | <project>/.externalToolBuilders/org.eclipse.jdt.core.javabuilder (2).launch 15 | 16 | 17 | 18 | 19 | org.eclipse.wst.common.project.facet.core.builder 20 | 21 | 22 | 23 | 24 | org.eclipse.m2e.core.maven2Builder 25 | 26 | 27 | 28 | 29 | 30 | org.eclipse.jdt.core.javanature 31 | org.eclipse.m2e.core.maven2Nature 32 | org.eclipse.wst.common.project.facet.core.nature 33 | 34 | 35 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | encoding/bimsurfer=UTF-8 4 | encoding/css=UTF-8 5 | encoding/plugin=UTF-8 6 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 3 | org.eclipse.jdt.core.compiler.compliance=1.5 4 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 5 | org.eclipse.jdt.core.compiler.release=disabled 6 | org.eclipse.jdt.core.compiler.source=1.5 7 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | - [Introduction](#introduction) 4 | - [Usage](#usage) 5 | - [BIMSurfer](#bimsurfer) 6 | - [Objects](#objects) 7 | - [Selecting and deselecting objects](#selecting-and-deselecting-objects) 8 | - [Showing and hiding objects](#showing-and-hiding-objects) 9 | - [Changing color and transparency of objects](#changing-color-and-transparency-of-objects) 10 | - [Camera](#camera) 11 | - [Controlling the camera](#controlling-the-camera) 12 | - [Fitting objects in view](#fitting-objects-in-view) 13 | - [Resetting](#resetting) 14 | - [Camera](#camera-1) 15 | - [Objects](#objects-1) 16 | 17 | # Introduction 18 | 19 | BIMSurfer2 is a WebGL-based IFC model viewer for BIMServer and IfcOpenShell built on xeogl, ThreeJS and SVG. 20 | 21 | ## Versions 22 | 23 | Over time there have been various versions of BIMsurfer. You are now looking at the v2 version of BIMSurfer. 24 | 25 | |BIMSufer|Model loader|Technologies used| 26 | |---|---|---| 27 | |v1|BIMServer|XeoEngine| 28 | |v2|IfcOpenShell/glTF, BIMServer|ThreeJS, xeogl, SVG| 29 | |v3|BIMServer|Custom webgl2| 30 | 31 | Visit BIMSurfer3 here https://github.com/opensourceBIM/BIMsurfer/ 32 | 33 | ### Selecting which version of BIMSurfer to use 34 | 35 | Usage of v1 is not recommended in new projects, because of the dependency on outdated libraries and lack of a stable API. Choose v3 for highest performance, but note that is webgl2 only, which is not universally supported (54% at the time of writing [source](https://webglstats.com/webgl2)). v2 can be used solely on static files generated by IfcOpenShell and is entirely built around open standards such as glTF. v3 has an interesting set of additional features such as partial support for 3D Tiles, measurements and up to 6 section planes. 36 | 37 | # Usage 38 | 39 | ## BIMSurfer 40 | 41 | Creating a [BIMSurfer](bimsurfer/src/BimSurfer.js): 42 | 43 | ````javascript 44 | var bimSurfer = new BimSurfer({ 45 | domNode: "viewerContainer" 46 | }); 47 | ```` 48 | 49 | Loading a model from BIMServer: 50 | 51 | ````javascript 52 | bimSurfer.load({ 53 | bimserver: ADDRESS, 54 | username: USERNAME, 55 | password: PASSWORD, 56 | poid: 131073, 57 | roid: 65539, 58 | schema: "ifc2x3tc1" // < TODO: Deduce automatically 59 | }) 60 | .then(function (model) { 61 | 62 | // Model is now loaded and rendering. 63 | // The following sections show what you can do with BIMSurfer at this point. 64 | //... 65 | }); 66 | ```` 67 | 68 | Generate a random test model if you want to test BIMSurfer without loading anything from BIMServer: 69 | 70 | ````javascript 71 | bimSurfer.loadRandom(); 72 | ```` 73 | 74 | The following usage examples in this guide will refer to objects from the generated test model. 75 | 76 | ## Objects 77 | 78 | ### Selecting and deselecting objects 79 | 80 | Selecting four objects: 81 | 82 | ````javascript 83 | bimSurfer.setSelection({ids: ["object3", "object2", "object4", "object6"], selected: true }); 84 | ```` 85 | 86 | then querying which objects are selected: 87 | 88 | ````javascript 89 | bimSurfer.getSelection() 90 | ```` 91 | 92 | The result shows that those four objects are currently selected: 93 | 94 | ````json 95 | ["object3", "object2", "object4", "object6"] 96 | ```` 97 | 98 | If we then deselect two objects, then query the selection again: 99 | 100 | ````javascript 101 | bimSurfer.setSelection({ids: ["object3", "object6"], selected: false }); 102 | bimSurfer.getSelection() 103 | ```` 104 | 105 | The result shows that only two objects are now selected: 106 | 107 | ````json 108 | ["object2", "object4"] 109 | ```` 110 | 111 | Subscribing to selection updates: 112 | 113 | ````javascript 114 | bimSurfer.on("selection-changed", 115 | function() { 116 | var selected = bimSurfer.getSelection(); 117 | console.log("selection = " + JSON.stringify(selected)); 118 | }); 119 | ```` 120 | 121 | ### Showing and hiding objects 122 | 123 | Hiding three objects by ID: 124 | 125 | ````javascript 126 | bimSurfer.setVisibility({ids: ["object3", "object1", "object6"], visible: false }); 127 | ```` 128 | 129 | Setting two objects visible by ID: 130 | 131 | ````javascript 132 | bimSurfer.setVisibility({ids: ["object1", "object6"], visible: true }); 133 | ```` 134 | 135 | Hiding all objects of IFC types "IfcSlab" and "IfcWall": 136 | 137 | ````javascript 138 | bimSurfer.setVisibility({types: ["IfcSlab", "IfcWall"], visible: false }); 139 | ```` 140 | 141 | ### Changing color and transparency of objects 142 | 143 | Making two objects pink: 144 | 145 | ````javascript 146 | bimSurfer.setColor({ids: ["object3", "object6"], color: [1, 0, 1] }) 147 | ```` 148 | 149 | An optional fourth element may be specified in the color to set opacity: 150 | 151 | ````javascript 152 | bimSurfer.setColor({ids: ["object3", "object6"], color: [1, 0, 1, 0.5] }) 153 | ```` 154 | 155 | ## Camera 156 | 157 | ### Controlling the camera 158 | 159 | Setting the camera position: 160 | 161 | ````javascript 162 | bimSurfer.setCamera({ 163 | eye: [-20,0,20], 164 | target: [0,10,0], 165 | up: [0,1,0] 166 | }); 167 | ```` 168 | 169 | Then "target" will then be the position we'll orbit about with the mouse or arrow keys (until we double-click an object to 170 | select a different orbit position). 171 | 172 | Setting the camera projection to orthographic: 173 | 174 | ````javascript 175 | bimSurfer.setCamera({ 176 | type:"ortho" 177 | }); 178 | ```` 179 | 180 | Setting the view volume size for orthographic, switching to orthographic projection first if necessary: 181 | 182 | ````javascript 183 | bimSurfer.setCamera({ 184 | type:"ortho", 185 | scale: 100 186 | }); 187 | ```` 188 | This uses the same technique as Blender, where the scale argument relates to the "real world" size of the model, meaning 189 | that if you set scale to 100, then your view would at most encompass an element of 100 units size. 190 | 191 | Setting the camera projection to perspective: 192 | 193 | ````javascript 194 | bimSurfer.setCamera({ 195 | type:"persp" 196 | }); 197 | ```` 198 | 199 | Setting the FOV on Y-axis for perspective, switching to perspective projection first if necessary: 200 | 201 | ````javascript 202 | bimSurfer.setCamera({ 203 | type:"persp", 204 | fovy: 65 205 | }); 206 | ```` 207 | 208 | Querying camera state: 209 | 210 | ````javascript 211 | var camera = bimSurfer.getCamera(); 212 | ```` 213 | 214 | The returned value would be: 215 | 216 | ````json 217 | { 218 | "type": "persp", 219 | "eye": [-20,0,20], 220 | "target": [0,10,0], 221 | "up": [0,1,0], 222 | "fovy": 65 223 | } 224 | ```` 225 | 226 | Subscribing to camera updates: 227 | 228 | ````javascript 229 | bimSurfer.on("camera-changed", 230 | function() { 231 | var camera = bimSurfer.getCamera(); 232 | console.log(JSON.stringify(camera)); 233 | }); 234 | ```` 235 | 236 | ### Fitting objects in view 237 | 238 | Flying the camera to fit the specified objects in view: 239 | 240 | ````javascript 241 | bimSurfer.viewFit({ ids: ["object3", "object1", "object6"], animate: true }); 242 | ```` 243 | 244 | Jumping the camera to fit the specified objects in view: 245 | 246 | ````javascript 247 | bimSurfer.viewFit({ids: ["object1", "object6"], animate: false }); 248 | ```` 249 | 250 | Flying to fit all objects in view: 251 | 252 | ````javascript 253 | bimSurfer.viewFit({ animate: true }); 254 | ```` 255 | 256 | Jumping to fit all objects in view: 257 | 258 | ````javascript 259 | bimSurfer.viewFit({ animate: false }); 260 | ```` 261 | 262 | ## Resetting 263 | 264 | ### Camera 265 | 266 | Resetting the camera to initial position: 267 | 268 | ````javascript 269 | bimSurfer.reset({ cameraPosition: true }); 270 | ```` 271 | 272 | ### Objects 273 | 274 | Resetting all objects to initial visibilities: 275 | 276 | ````javascript 277 | bimSurfer.reset({ visibility: true }); 278 | ```` 279 | 280 | Resetting two objects to their initial visibilities: 281 | 282 | ````javascript 283 | bimSurfer.reset({ ids: ["object3", "object6"], visibility: true }); 284 | ```` 285 | 286 | Resetting all objects to their initial colors: 287 | 288 | ````javascript 289 | bimSurfer.reset({ elementColors: true }); 290 | ```` 291 | 292 | Resetting two objects to their initial colors: 293 | 294 | ````javascript 295 | bimSurfer.reset({ ids: ["object3", "object6"], elementColors: true }); 296 | ```` 297 | 298 | Deselecting all objects: 299 | 300 | ````javascript 301 | bimSurfer.reset({ selectionState: true }); 302 | ```` 303 | 304 | # Acknowledgements 305 | 306 | BIM Surfer is licensed under the MIT License. 307 | 308 | Copyright 2020 BIM Surfer contributors 309 | 310 | For this version thanks to TNO and Bimforce and developed by @aothms @rubendel @xeolabs and @johltn 311 | -------------------------------------------------------------------------------- /bimsurfer/lib/DRACOLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Don McCurdy / https://www.donmccurdy.com 3 | */ 4 | 5 | THREE.DRACOLoader = function ( manager ) { 6 | 7 | THREE.Loader.call( this, manager ); 8 | 9 | this.decoderPath = ''; 10 | this.decoderConfig = {}; 11 | this.decoderBinary = null; 12 | this.decoderPending = null; 13 | 14 | this.workerLimit = 4; 15 | this.workerPool = []; 16 | this.workerNextTaskID = 1; 17 | this.workerSourceURL = ''; 18 | 19 | this.defaultAttributeIDs = { 20 | position: 'POSITION', 21 | normal: 'NORMAL', 22 | color: 'COLOR', 23 | uv: 'TEX_COORD' 24 | }; 25 | this.defaultAttributeTypes = { 26 | position: 'Float32Array', 27 | normal: 'Float32Array', 28 | color: 'Float32Array', 29 | uv: 'Float32Array' 30 | }; 31 | 32 | }; 33 | 34 | THREE.DRACOLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), { 35 | 36 | constructor: THREE.DRACOLoader, 37 | 38 | setDecoderPath: function ( path ) { 39 | 40 | this.decoderPath = path; 41 | 42 | return this; 43 | 44 | }, 45 | 46 | setDecoderConfig: function ( config ) { 47 | 48 | this.decoderConfig = config; 49 | 50 | return this; 51 | 52 | }, 53 | 54 | setWorkerLimit: function ( workerLimit ) { 55 | 56 | this.workerLimit = workerLimit; 57 | 58 | return this; 59 | 60 | }, 61 | 62 | /** @deprecated */ 63 | setVerbosity: function () { 64 | 65 | console.warn( 'THREE.DRACOLoader: The .setVerbosity() method has been removed.' ); 66 | 67 | }, 68 | 69 | /** @deprecated */ 70 | setDrawMode: function () { 71 | 72 | console.warn( 'THREE.DRACOLoader: The .setDrawMode() method has been removed.' ); 73 | 74 | }, 75 | 76 | /** @deprecated */ 77 | setSkipDequantization: function () { 78 | 79 | console.warn( 'THREE.DRACOLoader: The .setSkipDequantization() method has been removed.' ); 80 | 81 | }, 82 | 83 | load: function ( url, onLoad, onProgress, onError ) { 84 | 85 | var loader = new THREE.FileLoader( this.manager ); 86 | 87 | loader.setPath( this.path ); 88 | loader.setResponseType( 'arraybuffer' ); 89 | 90 | if ( this.crossOrigin === 'use-credentials' ) { 91 | 92 | loader.setWithCredentials( true ); 93 | 94 | } 95 | 96 | loader.load( url, ( buffer ) => { 97 | 98 | var taskConfig = { 99 | attributeIDs: this.defaultAttributeIDs, 100 | attributeTypes: this.defaultAttributeTypes, 101 | useUniqueIDs: false 102 | }; 103 | 104 | this.decodeGeometry( buffer, taskConfig ) 105 | .then( onLoad ) 106 | .catch( onError ); 107 | 108 | }, onProgress, onError ); 109 | 110 | }, 111 | 112 | /** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */ 113 | decodeDracoFile: function ( buffer, callback, attributeIDs, attributeTypes ) { 114 | 115 | var taskConfig = { 116 | attributeIDs: attributeIDs || this.defaultAttributeIDs, 117 | attributeTypes: attributeTypes || this.defaultAttributeTypes, 118 | useUniqueIDs: !! attributeIDs 119 | }; 120 | 121 | this.decodeGeometry( buffer, taskConfig ).then( callback ); 122 | 123 | }, 124 | 125 | decodeGeometry: function ( buffer, taskConfig ) { 126 | 127 | var worker; 128 | var taskID = this.workerNextTaskID ++; 129 | var taskCost = buffer.byteLength; 130 | 131 | // TODO: For backward-compatibility, support 'attributeTypes' objects containing 132 | // references (rather than names) to typed array constructors. These must be 133 | // serialized before sending them to the worker. 134 | for ( var attribute in taskConfig.attributeTypes ) { 135 | 136 | var type = taskConfig.attributeTypes[ attribute ]; 137 | 138 | if ( type.BYTES_PER_ELEMENT !== undefined ) { 139 | 140 | taskConfig.attributeTypes[ attribute ] = type.name; 141 | 142 | } 143 | 144 | } 145 | 146 | // Obtain a worker and assign a task, and construct a geometry instance 147 | // when the task completes. 148 | var geometryPending = this._getWorker( taskID, taskCost ) 149 | .then( ( _worker ) => { 150 | 151 | worker = _worker; 152 | 153 | return new Promise( ( resolve, reject ) => { 154 | 155 | worker._callbacks[ taskID ] = { resolve, reject }; 156 | 157 | worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] ); 158 | 159 | // this.debug(); 160 | 161 | } ); 162 | 163 | } ) 164 | .then( ( message ) => this._createGeometry( message.geometry ) ); 165 | 166 | // Remove task from the task list. 167 | geometryPending 168 | .finally( () => { 169 | 170 | if ( worker && taskID ) { 171 | 172 | this._releaseTask( worker, taskID ); 173 | 174 | // this.debug(); 175 | 176 | } 177 | 178 | } ); 179 | 180 | return geometryPending; 181 | 182 | }, 183 | 184 | _createGeometry: function ( geometryData ) { 185 | 186 | var geometry = new THREE.BufferGeometry(); 187 | 188 | if ( geometryData.index ) { 189 | 190 | geometry.setIndex( new THREE.BufferAttribute( geometryData.index.array, 1 ) ); 191 | 192 | } 193 | 194 | for ( var i = 0; i < geometryData.attributes.length; i ++ ) { 195 | 196 | var attribute = geometryData.attributes[ i ]; 197 | var name = attribute.name; 198 | var array = attribute.array; 199 | var itemSize = attribute.itemSize; 200 | 201 | geometry.addAttribute( name, new THREE.BufferAttribute( array, itemSize ) ); 202 | 203 | } 204 | 205 | return geometry; 206 | 207 | }, 208 | 209 | _loadLibrary: function ( url, responseType ) { 210 | 211 | var loader = new THREE.FileLoader( this.manager ); 212 | loader.setPath( this.decoderPath ); 213 | loader.setResponseType( responseType ); 214 | 215 | return new Promise( ( resolve, reject ) => { 216 | 217 | loader.load( url, resolve, undefined, reject ); 218 | 219 | } ); 220 | 221 | }, 222 | 223 | _initDecoder: function () { 224 | 225 | if ( this.decoderPending ) return this.decoderPending; 226 | 227 | var useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'; 228 | var librariesPending = []; 229 | 230 | if ( useJS ) { 231 | 232 | librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) ); 233 | 234 | } else { 235 | 236 | librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) ); 237 | librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) ); 238 | 239 | } 240 | 241 | this.decoderPending = Promise.all( librariesPending ) 242 | .then( ( libraries ) => { 243 | 244 | var jsContent = libraries[ 0 ]; 245 | 246 | if ( ! useJS ) { 247 | 248 | this.decoderConfig.wasmBinary = libraries[ 1 ]; 249 | 250 | } 251 | 252 | var fn = THREE.DRACOLoader.DRACOWorker.toString(); 253 | 254 | var body = [ 255 | '/* draco decoder */', 256 | jsContent, 257 | '', 258 | '/* worker */', 259 | fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) 260 | ].join( '\n' ); 261 | 262 | this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); 263 | 264 | } ); 265 | 266 | return this.decoderPending; 267 | 268 | }, 269 | 270 | _getWorker: function ( taskID, taskCost ) { 271 | 272 | return this._initDecoder().then( () => { 273 | 274 | if ( this.workerPool.length < this.workerLimit ) { 275 | 276 | var worker = new Worker( this.workerSourceURL ); 277 | 278 | worker._callbacks = {}; 279 | worker._taskCosts = {}; 280 | worker._taskLoad = 0; 281 | 282 | worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } ); 283 | 284 | worker.onmessage = function ( e ) { 285 | 286 | var message = e.data; 287 | 288 | switch ( message.type ) { 289 | 290 | case 'decode': 291 | worker._callbacks[ message.id ].resolve( message ); 292 | break; 293 | 294 | case 'error': 295 | worker._callbacks[ message.id ].reject( message ); 296 | break; 297 | 298 | default: 299 | console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' ); 300 | 301 | } 302 | 303 | }; 304 | 305 | this.workerPool.push( worker ); 306 | 307 | } else { 308 | 309 | this.workerPool.sort( function ( a, b ) { 310 | 311 | return a._taskLoad > b._taskLoad ? - 1 : 1; 312 | 313 | } ); 314 | 315 | } 316 | 317 | var worker = this.workerPool[ this.workerPool.length - 1 ]; 318 | worker._taskCosts[ taskID ] = taskCost; 319 | worker._taskLoad += taskCost; 320 | return worker; 321 | 322 | } ); 323 | 324 | }, 325 | 326 | _releaseTask: function ( worker, taskID ) { 327 | 328 | worker._taskLoad -= worker._taskCosts[ taskID ]; 329 | delete worker._callbacks[ taskID ]; 330 | delete worker._taskCosts[ taskID ]; 331 | 332 | }, 333 | 334 | debug: function () { 335 | 336 | console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); 337 | 338 | }, 339 | 340 | dispose: function () { 341 | 342 | for ( var i = 0; i < this.workerPool.length; ++ i ) { 343 | 344 | this.workerPool[ i ].terminate(); 345 | 346 | } 347 | 348 | this.workerPool.length = 0; 349 | 350 | return this; 351 | 352 | } 353 | 354 | } ); 355 | 356 | /* WEB WORKER */ 357 | 358 | THREE.DRACOLoader.DRACOWorker = function () { 359 | 360 | var decoderConfig; 361 | var decoderPending; 362 | 363 | onmessage = function ( e ) { 364 | 365 | var message = e.data; 366 | 367 | switch ( message.type ) { 368 | 369 | case 'init': 370 | decoderConfig = message.decoderConfig; 371 | decoderPending = new Promise( function ( resolve/*, reject*/ ) { 372 | 373 | decoderConfig.onModuleLoaded = function ( draco ) { 374 | 375 | // Module is Promise-like. Wrap before resolving to avoid loop. 376 | resolve( { draco: draco } ); 377 | 378 | }; 379 | 380 | DracoDecoderModule( decoderConfig ); 381 | 382 | } ); 383 | break; 384 | 385 | case 'decode': 386 | var buffer = message.buffer; 387 | var taskConfig = message.taskConfig; 388 | decoderPending.then( ( module ) => { 389 | 390 | var draco = module.draco; 391 | var decoder = new draco.Decoder(); 392 | var decoderBuffer = new draco.DecoderBuffer(); 393 | decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength ); 394 | 395 | try { 396 | 397 | var geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig ); 398 | 399 | var buffers = geometry.attributes.map( ( attr ) => attr.array.buffer ); 400 | 401 | if ( geometry.index ) buffers.push( geometry.index.array.buffer ); 402 | 403 | self.postMessage( { type: 'decode', id: message.id, geometry }, buffers ); 404 | 405 | } catch ( error ) { 406 | 407 | console.error( error ); 408 | 409 | self.postMessage( { type: 'error', id: message.id, error: error.message } ); 410 | 411 | } finally { 412 | 413 | draco.destroy( decoderBuffer ); 414 | draco.destroy( decoder ); 415 | 416 | } 417 | 418 | } ); 419 | break; 420 | 421 | } 422 | 423 | }; 424 | 425 | function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) { 426 | 427 | var attributeIDs = taskConfig.attributeIDs; 428 | var attributeTypes = taskConfig.attributeTypes; 429 | 430 | var dracoGeometry; 431 | var decodingStatus; 432 | 433 | var geometryType = decoder.GetEncodedGeometryType( decoderBuffer ); 434 | 435 | if ( geometryType === draco.TRIANGULAR_MESH ) { 436 | 437 | dracoGeometry = new draco.Mesh(); 438 | decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry ); 439 | 440 | } else if ( geometryType === draco.POINT_CLOUD ) { 441 | 442 | dracoGeometry = new draco.PointCloud(); 443 | decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry ); 444 | 445 | } else { 446 | 447 | throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' ); 448 | 449 | } 450 | 451 | if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) { 452 | 453 | throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() ); 454 | 455 | } 456 | 457 | var geometry = { index: null, attributes: [] }; 458 | 459 | // Gather all vertex attributes. 460 | for ( var attributeName in attributeIDs ) { 461 | 462 | var attributeType = self[ attributeTypes[ attributeName ] ]; 463 | 464 | var attribute; 465 | var attributeID; 466 | 467 | // A Draco file may be created with default vertex attributes, whose attribute IDs 468 | // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively, 469 | // a Draco file may contain a custom set of attributes, identified by known unique 470 | // IDs. glTF files always do the latter, and `.drc` files typically do the former. 471 | if ( taskConfig.useUniqueIDs ) { 472 | 473 | attributeID = attributeIDs[ attributeName ]; 474 | attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID ); 475 | 476 | } else { 477 | 478 | attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] ); 479 | 480 | if ( attributeID === - 1 ) continue; 481 | 482 | attribute = decoder.GetAttribute( dracoGeometry, attributeID ); 483 | 484 | } 485 | 486 | geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) ); 487 | 488 | } 489 | 490 | // Add index. 491 | if ( geometryType === draco.TRIANGULAR_MESH ) { 492 | 493 | // Generate mesh faces. 494 | var numFaces = dracoGeometry.num_faces(); 495 | var numIndices = numFaces * 3; 496 | var index = new Uint32Array( numIndices ); 497 | var indexArray = new draco.DracoInt32Array(); 498 | 499 | for ( var i = 0; i < numFaces; ++ i ) { 500 | 501 | decoder.GetFaceFromMesh( dracoGeometry, i, indexArray ); 502 | 503 | for ( var j = 0; j < 3; ++ j ) { 504 | 505 | index[ i * 3 + j ] = indexArray.GetValue( j ); 506 | 507 | } 508 | 509 | } 510 | 511 | geometry.index = { array: index, itemSize: 1 }; 512 | 513 | draco.destroy( indexArray ); 514 | 515 | } 516 | 517 | draco.destroy( dracoGeometry ); 518 | 519 | return geometry; 520 | 521 | } 522 | 523 | function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) { 524 | 525 | var numComponents = attribute.num_components(); 526 | var numPoints = dracoGeometry.num_points(); 527 | var numValues = numPoints * numComponents; 528 | var dracoArray; 529 | 530 | var array; 531 | 532 | switch ( attributeType ) { 533 | 534 | case Float32Array: 535 | dracoArray = new draco.DracoFloat32Array(); 536 | decoder.GetAttributeFloatForAllPoints( dracoGeometry, attribute, dracoArray ); 537 | array = new Float32Array( numValues ); 538 | break; 539 | 540 | case Int8Array: 541 | dracoArray = new draco.DracoInt8Array(); 542 | decoder.GetAttributeInt8ForAllPoints( dracoGeometry, attribute, dracoArray ); 543 | array = new Int8Array( numValues ); 544 | break; 545 | 546 | case Int16Array: 547 | dracoArray = new draco.DracoInt16Array(); 548 | decoder.GetAttributeInt16ForAllPoints( dracoGeometry, attribute, dracoArray ); 549 | array = new Int16Array( numValues ); 550 | break; 551 | 552 | case Int32Array: 553 | dracoArray = new draco.DracoInt32Array(); 554 | decoder.GetAttributeInt32ForAllPoints( dracoGeometry, attribute, dracoArray ); 555 | array = new Int32Array( numValues ); 556 | break; 557 | 558 | case Uint8Array: 559 | dracoArray = new draco.DracoUInt8Array(); 560 | decoder.GetAttributeUInt8ForAllPoints( dracoGeometry, attribute, dracoArray ); 561 | array = new Uint8Array( numValues ); 562 | break; 563 | 564 | case Uint16Array: 565 | dracoArray = new draco.DracoUInt16Array(); 566 | decoder.GetAttributeUInt16ForAllPoints( dracoGeometry, attribute, dracoArray ); 567 | array = new Uint16Array( numValues ); 568 | break; 569 | 570 | case Uint32Array: 571 | dracoArray = new draco.DracoUInt32Array(); 572 | decoder.GetAttributeUInt32ForAllPoints( dracoGeometry, attribute, dracoArray ); 573 | array = new Uint32Array( numValues ); 574 | break; 575 | 576 | default: 577 | throw new Error( 'THREE.DRACOLoader: Unexpected attribute type.' ); 578 | 579 | } 580 | 581 | for ( var i = 0; i < numValues; i ++ ) { 582 | 583 | array[ i ] = dracoArray.GetValue( i ); 584 | 585 | } 586 | 587 | draco.destroy( dracoArray ); 588 | 589 | return { 590 | name: attributeName, 591 | array: array, 592 | itemSize: numComponents 593 | }; 594 | 595 | } 596 | 597 | }; 598 | 599 | /** Deprecated static methods */ 600 | 601 | /** @deprecated */ 602 | THREE.DRACOLoader.setDecoderPath = function () { 603 | 604 | console.warn( 'THREE.DRACOLoader: The .setDecoderPath() method has been removed. Use instance methods.' ); 605 | 606 | }; 607 | 608 | /** @deprecated */ 609 | THREE.DRACOLoader.setDecoderConfig = function () { 610 | 611 | console.warn( 'THREE.DRACOLoader: The .setDecoderConfig() method has been removed. Use instance methods.' ); 612 | 613 | }; 614 | 615 | /** @deprecated */ 616 | THREE.DRACOLoader.releaseDecoderModule = function () { 617 | 618 | console.warn( 'THREE.DRACOLoader: The .releaseDecoderModule() method has been removed. Use instance methods.' ); 619 | 620 | }; 621 | 622 | /** @deprecated */ 623 | THREE.DRACOLoader.getDecoderModule = function () { 624 | 625 | console.warn( 'THREE.DRACOLoader: The .getDecoderModule() method has been removed. Use instance methods.' ); 626 | 627 | }; 628 | -------------------------------------------------------------------------------- /bimsurfer/lib/arraybuffer-slice.js: -------------------------------------------------------------------------------- 1 | // https://github.com/ttaubert/node-arraybuffer-slice 2 | // (c) 2014 Tim Taubert 3 | // arraybuffer-slice may be freely distributed under the MIT license. 4 | 5 | (function (undefined) { 6 | "use strict"; 7 | 8 | function clamp(val, length) { 9 | val = (val|0) || 0; 10 | 11 | if (val < 0) { 12 | return Math.max(val + length, 0); 13 | } 14 | 15 | return Math.min(val, length); 16 | } 17 | 18 | if (!ArrayBuffer.prototype.slice) { 19 | ArrayBuffer.prototype.slice = function (from, to) { 20 | var length = this.byteLength; 21 | var begin = clamp(from, length); 22 | var end = length; 23 | 24 | if (to !== undefined) { 25 | end = clamp(to, length); 26 | } 27 | 28 | if (begin > end) { 29 | return new ArrayBuffer(0); 30 | } 31 | 32 | var num = end - begin; 33 | var target = new ArrayBuffer(num); 34 | var targetArray = new Uint8Array(target); 35 | 36 | var sourceArray = new Uint8Array(this, begin, num); 37 | targetArray.set(sourceArray); 38 | 39 | return target; 40 | }; 41 | } 42 | })(); 43 | 44 | 45 | if (typeof Int8Array !== 'undefined') { 46 | if (!Int8Array.prototype.fill) Int8Array.prototype.fill = Array.prototype.fill; 47 | if (!Int8Array.prototype.slice) Int8Array.prototype.slice = Array.prototype.slice; 48 | } 49 | if (typeof Uint8Array !== 'undefined') { 50 | if (!Uint8Array.prototype.fill) Uint8Array.prototype.fill = Array.prototype.fill; 51 | if (!Uint8Array.prototype.slice) Uint8Array.prototype.slice = Array.prototype.slice; 52 | } 53 | if (typeof Uint8ClampedArray !== 'undefined') { 54 | if (!Uint8ClampedArray.prototype.fill) Uint8ClampedArray.prototype.fill = Array.prototype.fill; 55 | if (!Uint8ClampedArray.prototype.slice) Uint8ClampedArray.prototype.slice = Array.prototype.slice; 56 | } 57 | if (typeof Int16Array !== 'undefined') { 58 | if (!Int16Array.prototype.fill) Int16Array.prototype.fill = Array.prototype.fill; 59 | if (!Int16Array.prototype.slice) Int16Array.prototype.slice = Array.prototype.slice; 60 | } 61 | if (typeof Uint16Array !== 'undefined') { 62 | if (!Uint16Array.prototype.fill) Uint16Array.prototype.fill = Array.prototype.fill; 63 | if (!Uint16Array.prototype.slice) Uint16Array.prototype.slice = Array.prototype.slice; 64 | } 65 | if (typeof Int32Array !== 'undefined') { 66 | if (!Int32Array.prototype.fill) Int32Array.prototype.fill = Array.prototype.fill; 67 | if (!Int32Array.prototype.slice) Int32Array.prototype.slice = Array.prototype.slice; 68 | } 69 | if (typeof Uint32Array !== 'undefined') { 70 | if (!Uint32Array.prototype.fill) Uint32Array.prototype.fill = Array.prototype.fill; 71 | if (!Uint32Array.prototype.slice) Uint32Array.prototype.slice = Array.prototype.slice; 72 | } 73 | if (typeof Float32Array !== 'undefined') { 74 | if (!Float32Array.prototype.fill) Float32Array.prototype.fill = Array.prototype.fill; 75 | if (!Float32Array.prototype.slice) Float32Array.prototype.slice = Array.prototype.slice; 76 | } 77 | if (typeof Float64Array !== 'undefined') { 78 | if (!Float64Array.prototype.fill) Float64Array.prototype.fill = Array.prototype.fill; 79 | if (!Float64Array.prototype.slice) Float64Array.prototype.slice = Array.prototype.slice; 80 | } 81 | if (typeof TypedArray !== 'undefined') { 82 | if (!TypedArray.prototype.fill) TypedArray.prototype.fill = Array.prototype.fill; 83 | if (!TypedArray.prototype.slice) TypedArray.prototype.slice = Array.prototype.slice; 84 | } -------------------------------------------------------------------------------- /bimsurfer/lib/domReady.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/requirejs/domReady for details 5 | */ 6 | /*jslint */ 7 | /*global require: false, define: false, requirejs: false, 8 | window: false, clearInterval: false, document: false, 9 | self: false, setInterval: false */ 10 | 11 | 12 | define(function () { 13 | 'use strict'; 14 | 15 | var isTop, testDiv, scrollIntervalId, 16 | isBrowser = typeof window !== "undefined" && window.document, 17 | isPageLoaded = !isBrowser, 18 | doc = isBrowser ? document : null, 19 | readyCalls = []; 20 | 21 | function runCallbacks(callbacks) { 22 | var i; 23 | for (i = 0; i < callbacks.length; i += 1) { 24 | callbacks[i](doc); 25 | } 26 | } 27 | 28 | function callReady() { 29 | var callbacks = readyCalls; 30 | 31 | if (isPageLoaded) { 32 | //Call the DOM ready callbacks 33 | if (callbacks.length) { 34 | readyCalls = []; 35 | runCallbacks(callbacks); 36 | } 37 | } 38 | } 39 | 40 | /** 41 | * Sets the page as loaded. 42 | */ 43 | function pageLoaded() { 44 | if (!isPageLoaded) { 45 | isPageLoaded = true; 46 | if (scrollIntervalId) { 47 | clearInterval(scrollIntervalId); 48 | } 49 | 50 | callReady(); 51 | } 52 | } 53 | 54 | if (isBrowser) { 55 | if (document.addEventListener) { 56 | //Standards. Hooray! Assumption here that if standards based, 57 | //it knows about DOMContentLoaded. 58 | document.addEventListener("DOMContentLoaded", pageLoaded, false); 59 | window.addEventListener("load", pageLoaded, false); 60 | } else if (window.attachEvent) { 61 | window.attachEvent("onload", pageLoaded); 62 | 63 | testDiv = document.createElement('div'); 64 | try { 65 | isTop = window.frameElement === null; 66 | } catch (e) {} 67 | 68 | //DOMContentLoaded approximation that uses a doScroll, as found by 69 | //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, 70 | //but modified by other contributors, including jdalton 71 | if (testDiv.doScroll && isTop && window.external) { 72 | scrollIntervalId = setInterval(function () { 73 | try { 74 | testDiv.doScroll(); 75 | pageLoaded(); 76 | } catch (e) {} 77 | }, 30); 78 | } 79 | } 80 | 81 | //Check if document already complete, and if so, just trigger page load 82 | //listeners. Latest webkit browsers also use "interactive", and 83 | //will fire the onDOMContentLoaded before "interactive" but not after 84 | //entering "interactive" or "complete". More details: 85 | //http://dev.w3.org/html5/spec/the-end.html#the-end 86 | //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded 87 | //Hmm, this is more complicated on further use, see "firing too early" 88 | //bug: https://github.com/requirejs/domReady/issues/1 89 | //so removing the || document.readyState === "interactive" test. 90 | //There is still a window.onload binding that should get fired if 91 | //DOMContentLoaded is missed. 92 | if (document.readyState === "complete") { 93 | pageLoaded(); 94 | } 95 | } 96 | 97 | /** START OF PUBLIC API **/ 98 | 99 | /** 100 | * Registers a callback for DOM ready. If DOM is already ready, the 101 | * callback is called immediately. 102 | * @param {Function} callback 103 | */ 104 | function domReady(callback) { 105 | if (isPageLoaded) { 106 | callback(doc); 107 | } else { 108 | readyCalls.push(callback); 109 | } 110 | return domReady; 111 | } 112 | 113 | domReady.version = '2.0.1'; 114 | 115 | /** 116 | * Loader Plugin API method 117 | */ 118 | domReady.load = function (name, req, onLoad, config) { 119 | if (config.isBuild) { 120 | onLoad(null); 121 | } else { 122 | domReady(onLoad); 123 | } 124 | }; 125 | 126 | /** END OF PUBLIC API **/ 127 | 128 | return domReady; 129 | }); 130 | -------------------------------------------------------------------------------- /bimsurfer/lib/draco_decoder.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AECgeeks/BIMsurfer2/926d1fcc08a373c879a2416110bc3107be1d654c/bimsurfer/lib/draco_decoder.wasm -------------------------------------------------------------------------------- /bimsurfer/lib/es6-promise-3.2.2.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * @overview es6-promise - a tiny implementation of Promises/A+. 3 | * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) 4 | * @license Licensed under MIT license 5 | * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE 6 | * @version 3.2.2+35df15ea 7 | */ 8 | 9 | (function(){"use strict";function t(t){return"function"==typeof t||"object"==typeof t&&null!==t}function e(t){return"function"==typeof t}function n(t){G=t}function r(t){Q=t}function o(){return function(){process.nextTick(a)}}function i(){return function(){B(a)}}function s(){var t=0,e=new X(a),n=document.createTextNode("");return e.observe(n,{characterData:!0}),function(){n.data=t=++t%2}}function u(){var t=new MessageChannel;return t.port1.onmessage=a,function(){t.port2.postMessage(0)}}function c(){return function(){setTimeout(a,1)}}function a(){for(var t=0;J>t;t+=2){var e=tt[t],n=tt[t+1];e(n),tt[t]=void 0,tt[t+1]=void 0}J=0}function f(){try{var t=require,e=t("vertx");return B=e.runOnLoop||e.runOnContext,i()}catch(n){return c()}}function l(t,e){var n=this,r=new this.constructor(p);void 0===r[rt]&&k(r);var o=n._state;if(o){var i=arguments[o-1];Q(function(){x(o,r,i,n._result)})}else E(n,r,t,e);return r}function h(t){var e=this;if(t&&"object"==typeof t&&t.constructor===e)return t;var n=new e(p);return g(n,t),n}function p(){}function _(){return new TypeError("You cannot resolve a promise with itself")}function d(){return new TypeError("A promises callback cannot return that same promise.")}function v(t){try{return t.then}catch(e){return ut.error=e,ut}}function y(t,e,n,r){try{t.call(e,n,r)}catch(o){return o}}function m(t,e,n){Q(function(t){var r=!1,o=y(n,e,function(n){r||(r=!0,e!==n?g(t,n):S(t,n))},function(e){r||(r=!0,j(t,e))},"Settle: "+(t._label||" unknown promise"));!r&&o&&(r=!0,j(t,o))},t)}function b(t,e){e._state===it?S(t,e._result):e._state===st?j(t,e._result):E(e,void 0,function(e){g(t,e)},function(e){j(t,e)})}function w(t,n,r){n.constructor===t.constructor&&r===et&&constructor.resolve===nt?b(t,n):r===ut?j(t,ut.error):void 0===r?S(t,n):e(r)?m(t,n,r):S(t,n)}function g(e,n){e===n?j(e,_()):t(n)?w(e,n,v(n)):S(e,n)}function A(t){t._onerror&&t._onerror(t._result),T(t)}function S(t,e){t._state===ot&&(t._result=e,t._state=it,0!==t._subscribers.length&&Q(T,t))}function j(t,e){t._state===ot&&(t._state=st,t._result=e,Q(A,t))}function E(t,e,n,r){var o=t._subscribers,i=o.length;t._onerror=null,o[i]=e,o[i+it]=n,o[i+st]=r,0===i&&t._state&&Q(T,t)}function T(t){var e=t._subscribers,n=t._state;if(0!==e.length){for(var r,o,i=t._result,s=0;si;i++)e.resolve(t[i]).then(n,r)}:function(t,e){e(new TypeError("You must pass an array to race."))})}function F(t){var e=this,n=new e(p);return j(n,t),n}function D(){throw new TypeError("You must pass a resolver function as the first argument to the promise constructor")}function K(){throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.")}function L(t){this[rt]=O(),this._result=this._state=void 0,this._subscribers=[],p!==t&&("function"!=typeof t&&D(),this instanceof L?C(this,t):K())}function N(t,e){this._instanceConstructor=t,this.promise=new t(p),this.promise[rt]||k(this.promise),I(e)?(this._input=e,this.length=e.length,this._remaining=e.length,this._result=new Array(this.length),0===this.length?S(this.promise,this._result):(this.length=this.length||0,this._enumerate(),0===this._remaining&&S(this.promise,this._result))):j(this.promise,U())}function U(){return new Error("Array Methods must be provided an Array")}function W(){var t;if("undefined"!=typeof global)t=global;else if("undefined"!=typeof self)t=self;else try{t=Function("return this")()}catch(e){throw new Error("polyfill failed because global object is unavailable in this environment")}var n=t.Promise;(!n||"[object Promise]"!==Object.prototype.toString.call(n.resolve())||n.cast)&&(t.Promise=pt)}var z;z=Array.isArray?Array.isArray:function(t){return"[object Array]"===Object.prototype.toString.call(t)};var B,G,H,I=z,J=0,Q=function(t,e){tt[J]=t,tt[J+1]=e,J+=2,2===J&&(G?G(a):H())},R="undefined"!=typeof window?window:void 0,V=R||{},X=V.MutationObserver||V.WebKitMutationObserver,Z="undefined"==typeof self&&"undefined"!=typeof process&&"[object process]"==={}.toString.call(process),$="undefined"!=typeof Uint8ClampedArray&&"undefined"!=typeof importScripts&&"undefined"!=typeof MessageChannel,tt=new Array(1e3);H=Z?o():X?s():$?u():void 0===R&&"function"==typeof require?f():c();var et=l,nt=h,rt=Math.random().toString(36).substring(16),ot=void 0,it=1,st=2,ut=new M,ct=new M,at=0,ft=Y,lt=q,ht=F,pt=L;L.all=ft,L.race=lt,L.resolve=nt,L.reject=ht,L._setScheduler=n,L._setAsap=r,L._asap=Q,L.prototype={constructor:L,then:et,"catch":function(t){return this.then(null,t)}};var _t=N;N.prototype._enumerate=function(){for(var t=this.length,e=this._input,n=0;this._state===ot&&t>n;n++)this._eachEntry(e[n],n)},N.prototype._eachEntry=function(t,e){var n=this._instanceConstructor,r=n.resolve;if(r===nt){var o=v(t);if(o===et&&t._state!==ot)this._settledAt(t._state,e,t._result);else if("function"!=typeof o)this._remaining--,this._result[e]=t;else if(n===pt){var i=new n(p);w(i,t,o),this._willSettleAt(i,e)}else this._willSettleAt(new n(function(e){e(t)}),e)}else this._willSettleAt(r(t),e)},N.prototype._settledAt=function(t,e,n){var r=this.promise;r._state===ot&&(this._remaining--,t===st?j(r,n):this._result[e]=n),0===this._remaining&&S(r,this._result)},N.prototype._willSettleAt=function(t,e){var n=this;E(t,void 0,function(t){n._settledAt(it,e,t)},function(t){n._settledAt(st,e,t)})};var dt=W,vt={Promise:pt,polyfill:dt};"function"==typeof define&&define.amd?define(function(){return vt}):"undefined"!=typeof module&&module.exports?module.exports=vt:"undefined"!=typeof this&&(this.ES6Promise=vt),dt()}).call(this); -------------------------------------------------------------------------------- /bimsurfer/lib/require.js: -------------------------------------------------------------------------------- 1 | /* 2 | RequireJS 2.1.20 Copyright (c) 2010-2015, The Dojo Foundation All Rights Reserved. 3 | Available via the MIT or new BSD license. 4 | see: http://github.com/jrburke/requirejs for details 5 | */ 6 | var requirejs,require,define; 7 | (function(ba){function G(b){return"[object Function]"===K.call(b)}function H(b){return"[object Array]"===K.call(b)}function v(b,c){if(b){var d;for(d=0;dthis.depCount&&!this.defined){if(G(l)){if(this.events.error&&this.map.isDefine||e.onError!==ca)try{f=h.execCb(c,l,b,f)}catch(d){a=d}else f=h.execCb(c,l,b,f);this.map.isDefine&& 19 | void 0===f&&((b=this.module)?f=b.exports:this.usingExports&&(f=this.exports));if(a)return a.requireMap=this.map,a.requireModules=this.map.isDefine?[this.map.id]:null,a.requireType=this.map.isDefine?"define":"require",w(this.error=a)}else f=l;this.exports=f;if(this.map.isDefine&&!this.ignore&&(q[c]=f,e.onResourceLoad))e.onResourceLoad(h,this.map,this.depMaps);y(c);this.defined=!0}this.defining=!1;this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit("defined",this.exports),this.defineEmitComplete= 20 | !0)}}else t(h.defQueueMap,c)||this.fetch()}},callPlugin:function(){var a=this.map,b=a.id,d=i(a.prefix);this.depMaps.push(d);s(d,"defined",u(this,function(f){var l,d;d=n(aa,this.map.id);var g=this.map.name,P=this.map.parentMap?this.map.parentMap.name:null,p=h.makeRequire(a.parentMap,{enableBuildCallback:!0});if(this.map.unnormalized){if(f.normalize&&(g=f.normalize(g,function(a){return c(a,P,!0)})||""),f=i(a.prefix+"!"+g,this.map.parentMap),s(f,"defined",u(this,function(a){this.init([],function(){return a}, 21 | null,{enabled:!0,ignore:!0})})),d=n(m,f.id)){this.depMaps.push(f);if(this.events.error)d.on("error",u(this,function(a){this.emit("error",a)}));d.enable()}}else d?(this.map.url=h.nameToUrl(d),this.load()):(l=u(this,function(a){this.init([],function(){return a},null,{enabled:!0})}),l.error=u(this,function(a){this.inited=!0;this.error=a;a.requireModules=[b];A(m,function(a){0===a.map.id.indexOf(b+"_unnormalized")&&y(a.map.id)});w(a)}),l.fromText=u(this,function(f,c){var d=a.name,g=i(d),P=M;c&&(f=c);P&& 22 | (M=!1);r(g);t(k.config,b)&&(k.config[d]=k.config[b]);try{e.exec(f)}catch(m){return w(B("fromtexteval","fromText eval for "+b+" failed: "+m,m,[b]))}P&&(M=!0);this.depMaps.push(g);h.completeLoad(d);p([d],l)}),f.load(a.name,p,l,k))}));h.enable(d,this);this.pluginMaps[d.id]=d},enable:function(){V[this.map.id]=this;this.enabling=this.enabled=!0;v(this.depMaps,u(this,function(a,b){var c,f;if("string"===typeof a){a=i(a,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap);this.depMaps[b]=a;if(c= 23 | n(L,a.id)){this.depExports[b]=c(this);return}this.depCount+=1;s(a,"defined",u(this,function(a){this.undefed||(this.defineDep(b,a),this.check())}));this.errback?s(a,"error",u(this,this.errback)):this.events.error&&s(a,"error",u(this,function(a){this.emit("error",a)}))}c=a.id;f=m[c];!t(L,c)&&(f&&!f.enabled)&&h.enable(a,this)}));A(this.pluginMaps,u(this,function(a){var b=n(m,a.id);b&&!b.enabled&&h.enable(a,this)}));this.enabling=!1;this.check()},on:function(a,b){var c=this.events[a];c||(c=this.events[a]= 24 | []);c.push(b)},emit:function(a,b){v(this.events[a],function(a){a(b)});"error"===a&&delete this.events[a]}};h={config:k,contextName:b,registry:m,defined:q,urlFetched:S,defQueue:C,defQueueMap:{},Module:Z,makeModuleMap:i,nextTick:e.nextTick,onError:w,configure:function(a){a.baseUrl&&"/"!==a.baseUrl.charAt(a.baseUrl.length-1)&&(a.baseUrl+="/");var b=k.shim,c={paths:!0,bundles:!0,config:!0,map:!0};A(a,function(a,b){c[b]?(k[b]||(k[b]={}),U(k[b],a,!0,!0)):k[b]=a});a.bundles&&A(a.bundles,function(a,b){v(a, 25 | function(a){a!==b&&(aa[a]=b)})});a.shim&&(A(a.shim,function(a,c){H(a)&&(a={deps:a});if((a.exports||a.init)&&!a.exportsFn)a.exportsFn=h.makeShimExports(a);b[c]=a}),k.shim=b);a.packages&&v(a.packages,function(a){var b,a="string"===typeof a?{name:a}:a;b=a.name;a.location&&(k.paths[b]=a.location);k.pkgs[b]=a.name+"/"+(a.main||"main").replace(ha,"").replace(Q,"")});A(m,function(a,b){!a.inited&&!a.map.unnormalized&&(a.map=i(b,null,!0))});if(a.deps||a.callback)h.require(a.deps||[],a.callback)},makeShimExports:function(a){return function(){var b; 26 | a.init&&(b=a.init.apply(ba,arguments));return b||a.exports&&da(a.exports)}},makeRequire:function(a,j){function g(c,d,p){var k,n;j.enableBuildCallback&&(d&&G(d))&&(d.__requireJsBuild=!0);if("string"===typeof c){if(G(d))return w(B("requireargs","Invalid require call"),p);if(a&&t(L,c))return L[c](m[a.id]);if(e.get)return e.get(h,c,a,g);k=i(c,a,!1,!0);k=k.id;return!t(q,k)?w(B("notloaded",'Module name "'+k+'" has not been loaded yet for context: '+b+(a?"":". Use require([])"))):q[k]}J();h.nextTick(function(){J(); 27 | n=r(i(null,a));n.skipMap=j.skipMap;n.init(c,d,p,{enabled:!0});D()});return g}j=j||{};U(g,{isBrowser:z,toUrl:function(b){var d,e=b.lastIndexOf("."),j=b.split("/")[0];if(-1!==e&&(!("."===j||".."===j)||1g.attachEvent.toString().indexOf("[native code"))&&!Y?(M=!0,g.attachEvent("onreadystatechange",b.onScriptLoad)):(g.addEventListener("load",b.onScriptLoad,!1),g.addEventListener("error",b.onScriptError,!1));g.src=d;J=g;D?y.insertBefore(g,D):y.appendChild(g);J=null;return g}if(ea)try{importScripts(d),b.completeLoad(c)}catch(i){b.onError(B("importscripts", 35 | "importScripts failed for "+c+" at "+d,i,[c]))}};z&&!s.skipDataMain&&T(document.getElementsByTagName("script"),function(b){y||(y=b.parentNode);if(I=b.getAttribute("data-main"))return r=I,s.baseUrl||(E=r.split("/"),r=E.pop(),O=E.length?E.join("/")+"/":"./",s.baseUrl=O),r=r.replace(Q,""),e.jsExtRegExp.test(r)&&(r=I),s.deps=s.deps?s.deps.concat(r):[r],!0});define=function(b,c,d){var e,g;"string"!==typeof b&&(d=c,c=b,b=null);H(c)||(d=c,c=null);!c&&G(d)&&(c=[],d.length&&(d.toString().replace(ja,"").replace(ka, 36 | function(b,d){c.push(d)}),c=(1===d.length?["require"]:["require","exports","module"]).concat(c)));if(M){if(!(e=J))N&&"interactive"===N.readyState||T(document.getElementsByTagName("script"),function(b){if("interactive"===b.readyState)return N=b}),e=N;e&&(b||(b=e.getAttribute("data-requiremodule")),g=F[e.getAttribute("data-requirecontext")])}g?(g.defQueue.push([b,c,d]),g.defQueueMap[b]=!0):R.push([b,c,d])};define.amd={jQuery:!0};e.exec=function(b){return eval(b)};e(s)}})(this); 37 | -------------------------------------------------------------------------------- /bimsurfer/lib/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license RequireJS text 2.0.14 Copyright (c) 2010-2014, The Dojo Foundation All Rights Reserved. 3 | * Available via the MIT or new BSD license. 4 | * see: http://github.com/requirejs/text for details 5 | */ 6 | /*jslint regexp: true */ 7 | /*global require, XMLHttpRequest, ActiveXObject, 8 | define, window, process, Packages, 9 | java, location, Components, FileUtils */ 10 | 11 | define(['module'], function (module) { 12 | 'use strict'; 13 | 14 | var text, fs, Cc, Ci, xpcIsWindows, 15 | progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], 16 | xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, 17 | bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, 18 | hasLocation = typeof location !== 'undefined' && location.href, 19 | defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), 20 | defaultHostName = hasLocation && location.hostname, 21 | defaultPort = hasLocation && (location.port || undefined), 22 | buildMap = {}, 23 | masterConfig = (module.config && module.config()) || {}; 24 | 25 | text = { 26 | version: '2.0.14', 27 | 28 | strip: function (content) { 29 | //Strips declarations so that external SVG and XML 30 | //documents can be added to a document without worry. Also, if the string 31 | //is an HTML document, only the part inside the body tag is returned. 32 | if (content) { 33 | content = content.replace(xmlRegExp, ""); 34 | var matches = content.match(bodyRegExp); 35 | if (matches) { 36 | content = matches[1]; 37 | } 38 | } else { 39 | content = ""; 40 | } 41 | return content; 42 | }, 43 | 44 | jsEscape: function (content) { 45 | return content.replace(/(['\\])/g, '\\$1') 46 | .replace(/[\f]/g, "\\f") 47 | .replace(/[\b]/g, "\\b") 48 | .replace(/[\n]/g, "\\n") 49 | .replace(/[\t]/g, "\\t") 50 | .replace(/[\r]/g, "\\r") 51 | .replace(/[\u2028]/g, "\\u2028") 52 | .replace(/[\u2029]/g, "\\u2029"); 53 | }, 54 | 55 | createXhr: masterConfig.createXhr || function () { 56 | //Would love to dump the ActiveX crap in here. Need IE 6 to die first. 57 | var xhr, i, progId; 58 | if (typeof XMLHttpRequest !== "undefined") { 59 | return new XMLHttpRequest(); 60 | } else if (typeof ActiveXObject !== "undefined") { 61 | for (i = 0; i < 3; i += 1) { 62 | progId = progIds[i]; 63 | try { 64 | xhr = new ActiveXObject(progId); 65 | } catch (e) {} 66 | 67 | if (xhr) { 68 | progIds = [progId]; // so faster next time 69 | break; 70 | } 71 | } 72 | } 73 | 74 | return xhr; 75 | }, 76 | 77 | /** 78 | * Parses a resource name into its component parts. Resource names 79 | * look like: module/name.ext!strip, where the !strip part is 80 | * optional. 81 | * @param {String} name the resource name 82 | * @returns {Object} with properties "moduleName", "ext" and "strip" 83 | * where strip is a boolean. 84 | */ 85 | parseName: function (name) { 86 | var modName, ext, temp, 87 | strip = false, 88 | index = name.lastIndexOf("."), 89 | isRelative = name.indexOf('./') === 0 || 90 | name.indexOf('../') === 0; 91 | 92 | if (index !== -1 && (!isRelative || index > 1)) { 93 | modName = name.substring(0, index); 94 | ext = name.substring(index + 1); 95 | } else { 96 | modName = name; 97 | } 98 | 99 | temp = ext || modName; 100 | index = temp.indexOf("!"); 101 | if (index !== -1) { 102 | //Pull off the strip arg. 103 | strip = temp.substring(index + 1) === "strip"; 104 | temp = temp.substring(0, index); 105 | if (ext) { 106 | ext = temp; 107 | } else { 108 | modName = temp; 109 | } 110 | } 111 | 112 | return { 113 | moduleName: modName, 114 | ext: ext, 115 | strip: strip 116 | }; 117 | }, 118 | 119 | xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, 120 | 121 | /** 122 | * Is an URL on another domain. Only works for browser use, returns 123 | * false in non-browser environments. Only used to know if an 124 | * optimized .js version of a text resource should be loaded 125 | * instead. 126 | * @param {String} url 127 | * @returns Boolean 128 | */ 129 | useXhr: function (url, protocol, hostname, port) { 130 | var uProtocol, uHostName, uPort, 131 | match = text.xdRegExp.exec(url); 132 | if (!match) { 133 | return true; 134 | } 135 | uProtocol = match[2]; 136 | uHostName = match[3]; 137 | 138 | uHostName = uHostName.split(':'); 139 | uPort = uHostName[1]; 140 | uHostName = uHostName[0]; 141 | 142 | return (!uProtocol || uProtocol === protocol) && 143 | (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && 144 | ((!uPort && !uHostName) || uPort === port); 145 | }, 146 | 147 | finishLoad: function (name, strip, content, onLoad) { 148 | content = strip ? text.strip(content) : content; 149 | if (masterConfig.isBuild) { 150 | buildMap[name] = content; 151 | } 152 | onLoad(content); 153 | }, 154 | 155 | load: function (name, req, onLoad, config) { 156 | //Name has format: some.module.filext!strip 157 | //The strip part is optional. 158 | //if strip is present, then that means only get the string contents 159 | //inside a body tag in an HTML string. For XML/SVG content it means 160 | //removing the declarations so the content can be inserted 161 | //into the current doc without problems. 162 | 163 | // Do not bother with the work if a build and text will 164 | // not be inlined. 165 | if (config && config.isBuild && !config.inlineText) { 166 | onLoad(); 167 | return; 168 | } 169 | 170 | masterConfig.isBuild = config && config.isBuild; 171 | 172 | var parsed = text.parseName(name), 173 | nonStripName = parsed.moduleName + 174 | (parsed.ext ? '.' + parsed.ext : ''), 175 | url = req.toUrl(nonStripName), 176 | useXhr = (masterConfig.useXhr) || 177 | text.useXhr; 178 | 179 | // Do not load if it is an empty: url 180 | if (url.indexOf('empty:') === 0) { 181 | onLoad(); 182 | return; 183 | } 184 | 185 | //Load the text. Use XHR if possible and in a browser. 186 | if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { 187 | text.get(url, function (content) { 188 | text.finishLoad(name, parsed.strip, content, onLoad); 189 | }, function (err) { 190 | if (onLoad.error) { 191 | onLoad.error(err); 192 | } 193 | }); 194 | } else { 195 | //Need to fetch the resource across domains. Assume 196 | //the resource has been optimized into a JS module. Fetch 197 | //by the module name + extension, but do not include the 198 | //!strip part to avoid file system issues. 199 | req([nonStripName], function (content) { 200 | text.finishLoad(parsed.moduleName + '.' + parsed.ext, 201 | parsed.strip, content, onLoad); 202 | }); 203 | } 204 | }, 205 | 206 | write: function (pluginName, moduleName, write, config) { 207 | if (buildMap.hasOwnProperty(moduleName)) { 208 | var content = text.jsEscape(buildMap[moduleName]); 209 | write.asModule(pluginName + "!" + moduleName, 210 | "define(function () { return '" + 211 | content + 212 | "';});\n"); 213 | } 214 | }, 215 | 216 | writeFile: function (pluginName, moduleName, req, write, config) { 217 | var parsed = text.parseName(moduleName), 218 | extPart = parsed.ext ? '.' + parsed.ext : '', 219 | nonStripName = parsed.moduleName + extPart, 220 | //Use a '.js' file name so that it indicates it is a 221 | //script that can be loaded across domains. 222 | fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; 223 | 224 | //Leverage own load() method to load plugin value, but only 225 | //write out values that do not have the strip argument, 226 | //to avoid any potential issues with ! in file names. 227 | text.load(nonStripName, req, function (value) { 228 | //Use own write() method to construct full module value. 229 | //But need to create shell that translates writeFile's 230 | //write() to the right interface. 231 | var textWrite = function (contents) { 232 | return write(fileName, contents); 233 | }; 234 | textWrite.asModule = function (moduleName, contents) { 235 | return write.asModule(moduleName, fileName, contents); 236 | }; 237 | 238 | text.write(pluginName, nonStripName, textWrite, config); 239 | }, config); 240 | } 241 | }; 242 | 243 | if (masterConfig.env === 'node' || (!masterConfig.env && 244 | typeof process !== "undefined" && 245 | process.versions && 246 | !!process.versions.node && 247 | !process.versions['node-webkit'] && 248 | !process.versions['atom-shell'])) { 249 | //Using special require.nodeRequire, something added by r.js. 250 | fs = require.nodeRequire('fs'); 251 | 252 | text.get = function (url, callback, errback) { 253 | try { 254 | var file = fs.readFileSync(url, 'utf8'); 255 | //Remove BOM (Byte Mark Order) from utf8 files if it is there. 256 | if (file[0] === '\uFEFF') { 257 | file = file.substring(1); 258 | } 259 | callback(file); 260 | } catch (e) { 261 | if (errback) { 262 | errback(e); 263 | } 264 | } 265 | }; 266 | } else if (masterConfig.env === 'xhr' || (!masterConfig.env && 267 | text.createXhr())) { 268 | text.get = function (url, callback, errback, headers) { 269 | var xhr = text.createXhr(), header; 270 | xhr.open('GET', url, true); 271 | 272 | //Allow plugins direct access to xhr headers 273 | if (headers) { 274 | for (header in headers) { 275 | if (headers.hasOwnProperty(header)) { 276 | xhr.setRequestHeader(header.toLowerCase(), headers[header]); 277 | } 278 | } 279 | } 280 | 281 | //Allow overrides specified in config 282 | if (masterConfig.onXhr) { 283 | masterConfig.onXhr(xhr, url); 284 | } 285 | 286 | xhr.onreadystatechange = function (evt) { 287 | var status, err; 288 | //Do not explicitly handle errors, those should be 289 | //visible via console output in the browser. 290 | if (xhr.readyState === 4) { 291 | status = xhr.status || 0; 292 | if (status > 399 && status < 600) { 293 | //An http 4xx or 5xx error. Signal an error. 294 | err = new Error(url + ' HTTP status: ' + status); 295 | err.xhr = xhr; 296 | if (errback) { 297 | errback(err); 298 | } 299 | } else { 300 | callback(xhr.responseText); 301 | } 302 | 303 | if (masterConfig.onXhrComplete) { 304 | masterConfig.onXhrComplete(xhr, url); 305 | } 306 | } 307 | }; 308 | xhr.send(null); 309 | }; 310 | } else if (masterConfig.env === 'rhino' || (!masterConfig.env && 311 | typeof Packages !== 'undefined' && typeof java !== 'undefined')) { 312 | //Why Java, why is this so awkward? 313 | text.get = function (url, callback) { 314 | var stringBuffer, line, 315 | encoding = "utf-8", 316 | file = new java.io.File(url), 317 | lineSeparator = java.lang.System.getProperty("line.separator"), 318 | input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), 319 | content = ''; 320 | try { 321 | stringBuffer = new java.lang.StringBuffer(); 322 | line = input.readLine(); 323 | 324 | // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 325 | // http://www.unicode.org/faq/utf_bom.html 326 | 327 | // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: 328 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 329 | if (line && line.length() && line.charAt(0) === 0xfeff) { 330 | // Eat the BOM, since we've already found the encoding on this file, 331 | // and we plan to concatenating this buffer with others; the BOM should 332 | // only appear at the top of a file. 333 | line = line.substring(1); 334 | } 335 | 336 | if (line !== null) { 337 | stringBuffer.append(line); 338 | } 339 | 340 | while ((line = input.readLine()) !== null) { 341 | stringBuffer.append(lineSeparator); 342 | stringBuffer.append(line); 343 | } 344 | //Make sure we return a JavaScript string and not a Java string. 345 | content = String(stringBuffer.toString()); //String 346 | } finally { 347 | input.close(); 348 | } 349 | callback(content); 350 | }; 351 | } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env && 352 | typeof Components !== 'undefined' && Components.classes && 353 | Components.interfaces)) { 354 | //Avert your gaze! 355 | Cc = Components.classes; 356 | Ci = Components.interfaces; 357 | Components.utils['import']('resource://gre/modules/FileUtils.jsm'); 358 | xpcIsWindows = ('@mozilla.org/windows-registry-key;1' in Cc); 359 | 360 | text.get = function (url, callback) { 361 | var inStream, convertStream, fileObj, 362 | readData = {}; 363 | 364 | if (xpcIsWindows) { 365 | url = url.replace(/\//g, '\\'); 366 | } 367 | 368 | fileObj = new FileUtils.File(url); 369 | 370 | //XPCOM, you so crazy 371 | try { 372 | inStream = Cc['@mozilla.org/network/file-input-stream;1'] 373 | .createInstance(Ci.nsIFileInputStream); 374 | inStream.init(fileObj, 1, 0, false); 375 | 376 | convertStream = Cc['@mozilla.org/intl/converter-input-stream;1'] 377 | .createInstance(Ci.nsIConverterInputStream); 378 | convertStream.init(inStream, "utf-8", inStream.available(), 379 | Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); 380 | 381 | convertStream.readString(inStream.available(), readData); 382 | convertStream.close(); 383 | inStream.close(); 384 | callback(readData.value); 385 | } catch (e) { 386 | throw new Error((fileObj && fileObj.path || '') + ': ' + e); 387 | } 388 | }; 389 | } 390 | return text; 391 | }); 392 | -------------------------------------------------------------------------------- /bimsurfer/src/AnnotationRenderer.js: -------------------------------------------------------------------------------- 1 | define(["./Utils"], function (Utils) { 2 | "use strict"; 3 | 4 | var AnnotationRenderer = function(args) { 5 | 6 | var v = args.viewer; 7 | var m = args.model; 8 | var a = args.assets; 9 | 10 | var aabb, modelExtent, use_xeogl; 11 | 12 | if (use_xeogl = !!v.scene.worldBoundary) { 13 | aabb = v.scene.worldBoundary.aabb; 14 | var max = aabb.subarray(3); 15 | var min = aabb.subarray(0, 3); 16 | var diag = xeogl.math.subVec3(max, min, xeogl.math.vec3()); 17 | modelExtent = xeogl.math.lenVec3(diag); 18 | } else { 19 | aabb = new THREE.Box3(); 20 | aabb.setFromObject(v.scene); 21 | modelExtent = aabb.getSize(new THREE.Vector3()).length(); 22 | } 23 | 24 | var visit = function(n, fn) { 25 | fn(n); 26 | (n.children || []).forEach(function(c) {visit(c, fn);}); 27 | }; 28 | 29 | var traverse = function(types, p, n) { 30 | var li = []; 31 | var _ = function(p, n) { 32 | var t = n["xlink:href"]; 33 | if (t) t = types[t.substr(1)]; 34 | if (t) li.push([p, t]); 35 | (n.children || []).forEach(function(c) {_(n, c);}); 36 | } 37 | _(p, n); 38 | return li; 39 | }; 40 | 41 | var makeMatrix = use_xeogl 42 | ? function(s, lengthUnit) { 43 | if (arguments.length == 0) { 44 | return xeogl.math.mat4(); 45 | } 46 | 47 | var m = xeogl.math.mat4(s.split(" ")); 48 | m[12] *= lengthUnit; 49 | m[13] *= lengthUnit; 50 | m[14] *= lengthUnit; 51 | return m; 52 | } 53 | : function(s, lengthUnit) { 54 | var m = new THREE.Matrix4; 55 | if (arguments.length == 0) return m; 56 | 57 | var f = new Float32Array(s.split(" ")); 58 | f[12] *= lengthUnit; 59 | f[13] *= lengthUnit; 60 | f[14] *= lengthUnit; 61 | m.set.apply(m, f); 62 | m.transpose(); 63 | return m; 64 | }; 65 | 66 | var invert = use_xeogl ? xeogl.math.inverseMat4 : function(m, n) { return m.getInverse(n); } 67 | 68 | var scaleMatrix = use_xeogl ? xeogl.math.scalingMat4s : function(v) { 69 | return makeMatrix().scale(new THREE.Vector3(v,v,v)); 70 | }; 71 | 72 | var makeVec4 = use_xeogl ? function() { return xeogl.math.vec4(arguments); } : function(x,y,z,w) { return new THREE.Vector4(x,y,z,w); }; 73 | 74 | var m4v4 = use_xeogl ? xeogl.math.mulMat4v4 : function(m, v) { return v.clone().applyMatrix4(m); }; 75 | 76 | var m4m4_inplace = use_xeogl ? function(m, n) { xeogl.math.mulMat4(m, n, m); } : function(m, n) { m.multiply(n); };; 77 | 78 | var logMatrix = function(n, m) { 79 | if (m.elements) { 80 | m = m.elements;; 81 | } 82 | console.log(n, ...m); 83 | }; 84 | 85 | this.render = function() { 86 | var typelist = Utils.FindNodeOfType(m, "types")[0].children; 87 | var decomposition = Utils.FindNodeOfType(m, "decomposition")[0]; 88 | var units = Utils.FindNodeOfType(m, "units")[0].children; 89 | 90 | var types = {}; 91 | typelist.forEach(function(t) { 92 | types[t.guid] = t; 93 | }); 94 | 95 | var lengthUnit = 1.; 96 | units.forEach(function(u) { 97 | if (u.UnitType === "LENGTHUNIT") { 98 | lengthUnit = parseFloat(u.SI_equivalent); 99 | } 100 | }); 101 | 102 | var elementsWithType = traverse(types, null, decomposition); 103 | 104 | elementsWithType.forEach(function(l) { 105 | var elem = l[0]; 106 | var type = l[1]; 107 | 108 | var m1 = makeMatrix(elem.ObjectPlacement, lengthUnit); 109 | var m1i = makeMatrix(); 110 | invert(m1, m1i); 111 | 112 | v.createModel("Annotations"); 113 | 114 | var s = scaleMatrix(Math.sqrt(modelExtent) / 100.); 115 | var z0 = makeVec4(0,0,0,1); 116 | var z1 = makeVec4(0,0,1,1); 117 | 118 | visit(type, function(c) { 119 | if (!c.ObjectPlacement) { 120 | return; 121 | } 122 | 123 | var m2 = makeMatrix(c.ObjectPlacement, lengthUnit); 124 | m4m4_inplace(m2, s); 125 | m4m4_inplace(m2, m1); 126 | 127 | var symbol = null; 128 | 129 | if (c.type === "IfcDistributionPort") { 130 | if (c.FlowDirection == "SINK") { 131 | symbol = a.ArrowOut(); 132 | } else if (c.FlowDirection == "SOURCE") { 133 | symbol = a.ArrowIn(); 134 | } else if (c.FlowDirection == "SOURCEANDSINK") { 135 | symbol = a.ArrowInOut(); 136 | } 137 | } 138 | 139 | if (symbol === null) { 140 | symbol = a.Sphere(); 141 | } 142 | 143 | symbol.register(v); 144 | symbol.render(v, c.guid, c.type, m2); 145 | }); 146 | }); 147 | } 148 | 149 | }; 150 | 151 | return AnnotationRenderer; 152 | 153 | }); -------------------------------------------------------------------------------- /bimsurfer/src/BimServerModel.js: -------------------------------------------------------------------------------- 1 | define(["../lib/text"], function(text) { 2 | "use strict"; 3 | 4 | /* 5 | var getElementName = function(elem) { 6 | var name = null; 7 | // TODO: This is synchronous right? 8 | ["LongName", "Name"].forEach(function(attr) { 9 | if (name === null && elem["get" + attr] && elem["get" + attr]()) { 10 | name = elem["get" + attr](); 11 | } 12 | }); 13 | return name; 14 | }; 15 | */ 16 | 17 | function BimServerModel(apiModel) { 18 | 19 | this.api = apiModel.bimServerApi; 20 | this.apiModel = apiModel; 21 | this.tree = null; 22 | this.treePromise = null; 23 | 24 | this.getTree = function(args) { 25 | 26 | /* 27 | // TODO: This is rather tricky. Never know when the list of Projects is exhausted. 28 | // Luckily a valid IFC contains one and only one. Let's assume there is just one. 29 | var projectEncountered = false; 30 | 31 | this.model.getAllOfType("IfcProject", false, function(project) { 32 | if (projectEncountered) { 33 | throw new Error("More than a single project encountered, bleh!"); 34 | } 35 | console.log('project', project); 36 | }); 37 | 38 | */ 39 | 40 | var self = this; 41 | 42 | return self.treePromise || (self.treePromise = new Promise(function(resolve, reject) { 43 | 44 | if (self.tree) { 45 | resolve(self.tree); 46 | } 47 | 48 | var query = 49 | { 50 | defines:{ 51 | Representation:{ 52 | type: "IfcProduct", 53 | field: "Representation" 54 | }, 55 | ContainsElementsDefine: { 56 | type: "IfcSpatialStructureElement", 57 | field:"ContainsElements", 58 | include:{ 59 | type:"IfcRelContainedInSpatialStructure", 60 | field:"RelatedElements", 61 | includes:[ 62 | "IsDecomposedByDefine", 63 | "ContainsElementsDefine", 64 | "Representation" 65 | ] 66 | } 67 | }, 68 | IsDecomposedByDefine: { 69 | type: "IfcObjectDefinition", 70 | field:"IsDecomposedBy", 71 | include: { 72 | type:"IfcRelDecomposes", 73 | field:"RelatedObjects", 74 | includes:[ 75 | "IsDecomposedByDefine", 76 | "ContainsElementsDefine", 77 | "Representation" 78 | ] 79 | } 80 | }, 81 | }, 82 | queries:[{ 83 | type:"IfcProject", 84 | includes:[ 85 | "IsDecomposedByDefine", 86 | "ContainsElementsDefine" 87 | ] 88 | }, { 89 | type:"IfcRepresentation", 90 | includeAllSubtypes: true 91 | },{ 92 | type:"IfcProductRepresentation" 93 | },{ 94 | type:"IfcPresentationLayerWithStyle" 95 | },{ 96 | type:"IfcProduct", 97 | includeAllSubtypes: true 98 | }, { 99 | type:"IfcProductDefinitionShape" 100 | }, { 101 | type:"IfcPresentationLayerAssignment" 102 | }, { 103 | type:"IfcRelAssociatesClassification", 104 | includes:[{ 105 | type:"IfcRelAssociatesClassification", 106 | field:"RelatedObjects" 107 | }, { 108 | type:"IfcRelAssociatesClassification", 109 | field:"RelatingClassification" 110 | }] 111 | }, { 112 | type:"IfcSIUnit" 113 | }, { 114 | type:"IfcPresentationLayerAssignment" 115 | }] 116 | }; 117 | 118 | // Perform the download 119 | // apiModel.query(query, function(o) {}).done(function(){ 120 | 121 | // A list of entities that define parent-child relationships 122 | var entities = { 123 | 'IfcRelDecomposes': 1, 124 | 'IfcRelAggregates': 1, 125 | 'IfcRelContainedInSpatialStructure': 1, 126 | 'IfcRelFillsElement': 1, 127 | 'IfcRelVoidsElement': 1 128 | } 129 | 130 | // Create a mapping from id->instance 131 | var instance_by_id = {}; 132 | var objects = []; 133 | 134 | for (var e in apiModel.objects) { 135 | // The root node in a dojo store should have its parent 136 | // set to null, not just something that evaluates to false 137 | var o = apiModel.objects[e].object; 138 | o.parent = null; 139 | instance_by_id[o._i] = o; 140 | objects.push(o); 141 | }; 142 | 143 | // Filter all instances based on relationship entities 144 | var relationships = objects.filter(function (o) { 145 | return entities[o._t]; 146 | }); 147 | 148 | // Construct a tuple of {parent, child} ids 149 | var parents = relationships.map(function (o) { 150 | var ks = Object.keys(o); 151 | var related = ks.filter(function (k) { 152 | return k.indexOf('Related') !== -1; 153 | }); 154 | var relating = ks.filter(function (k) { 155 | return k.indexOf('Relating') !== -1; 156 | }); 157 | return [o[relating[0]], o[related[0]]]; 158 | }); 159 | 160 | var is_array = function (o) { 161 | return Object.prototype.toString.call(o) === '[object Array]'; 162 | } 163 | 164 | var data = []; 165 | var visited = {}; 166 | parents.forEach(function (a) { 167 | // Relationships in IFC can be one to one/many 168 | var ps = is_array(a[0]) ? a[0] : [a[0]]; 169 | var cs = is_array(a[1]) ? a[1] : [a[1]]; 170 | for (var i = 0; i < ps.length; ++i) { 171 | for (var j = 0; j < cs.length; ++j) { 172 | // Lookup the instance ids in the mapping 173 | var p = instance_by_id[ps[i]._i]; 174 | var c = instance_by_id[cs[j]._i]; 175 | 176 | // parent, id, hasChildren are significant attributes in a dojo store 177 | c.parent = p.id = p._i; 178 | c.id = c._i; 179 | p.hasChildren = true; 180 | 181 | // Make sure to only add instances once 182 | if (!visited[c.id]) { 183 | data.push(c); 184 | } 185 | if (!visited[p.id]) { 186 | data.push(p); 187 | } 188 | visited[p.id] = visited[c.id] = true; 189 | } 190 | } 191 | }); 192 | 193 | var make_element = function (o) { 194 | return {name: o.Name, id: o.id, guid: o.GlobalId, parent: o.parent, gid: (o._rgeometry == null ? null : o._rgeometry._i)}; 195 | }; 196 | 197 | var fold = (function() { 198 | var root = null; 199 | return function(li) { 200 | var by_oid = {}; 201 | li.forEach(function(elem) { 202 | by_oid[elem.id] = elem; 203 | }); 204 | li.forEach(function(elem) { 205 | if (elem.parent === null) { 206 | root = elem; 207 | } else { 208 | var p = by_oid[elem.parent]; 209 | (p.children || (p.children = [])).push(elem); 210 | } 211 | }); 212 | return root; 213 | }})(); 214 | 215 | resolve(self.tree = fold(data.map(make_element))); 216 | // }); 217 | })); 218 | }; 219 | 220 | } 221 | 222 | return BimServerModel; 223 | 224 | }); -------------------------------------------------------------------------------- /bimsurfer/src/BimServerModelLoader.js: -------------------------------------------------------------------------------- 1 | define(["./BimServerModel", "./PreloadQuery", "./BimServerGeometryLoader", "./BimSurfer"], function(BimServerModel, PreloadQuery, BimServerGeometryLoader, BimSufer) { 2 | "use strict"; 3 | 4 | function BimServerModelLoader(bimServerClient, bimSurfer) { 5 | 6 | var o = this; 7 | 8 | this.loadFullModel = function(apiModel){ 9 | return new Promise(function(resolve, reject) { 10 | var model = new BimServerModel(apiModel); 11 | 12 | apiModel.query(PreloadQuery, function () {}).done(function(){ 13 | var oids = []; 14 | apiModel.getAllOfType("IfcProduct", true, function(object){ 15 | oids.push(object.oid); 16 | }); 17 | o.loadOids(model, oids); 18 | resolve(model); 19 | }); 20 | }); 21 | }; 22 | 23 | this.loadObjects = function(apiModel, objects){ 24 | return new Promise(function(resolve, reject) { 25 | var model = new BimServerModel(apiModel); 26 | 27 | var oids = []; 28 | objects.forEach(function(object){ 29 | oids.push(object.oid); 30 | }); 31 | o.loadOids(model, oids); 32 | resolve(model); 33 | }); 34 | }; 35 | 36 | this.loadOids = function(model, oids){ 37 | var oidToGuid = {}; 38 | var guidToOid = {}; 39 | 40 | var oidGid = {}; 41 | 42 | oids.forEach(function(oid){ 43 | model.apiModel.get(oid, function(object){ 44 | if (object.object._rgeometry != null) { 45 | var gid = object.object._rgeometry._i; 46 | var guid = object.object.GlobalId; 47 | oidToGuid[oid] = guid; 48 | guidToOid[guid] = oid; 49 | oidGid[gid] = oid; 50 | } 51 | }); 52 | }); 53 | 54 | bimSurfer._idMapping.toGuid.push(oidToGuid); 55 | bimSurfer._idMapping.toId.push(guidToOid); 56 | 57 | var viewer = bimSurfer.viewer; 58 | viewer.taskStarted(); 59 | 60 | viewer.createModel(model.apiModel.roid); 61 | 62 | var loader = new BimServerGeometryLoader(model.api, viewer, model, model.apiModel.roid, o.globalTransformationMatrix); 63 | 64 | loader.addProgressListener(function (progress, nrObjectsRead, totalNrObjects) { 65 | if (progress == "start") { 66 | console.log("Started loading geometries"); 67 | // self.fire("loading-started"); 68 | } else if (progress == "done") { 69 | console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); 70 | // self.fire("loading-finished"); 71 | viewer.taskFinished(); 72 | } 73 | }); 74 | 75 | loader.setLoadOids(oidGid); 76 | 77 | // viewer.clear(); // For now, until we support multiple models through the API 78 | 79 | viewer.on("tick", function () { // TODO: Fire "tick" event from xeoViewer 80 | loader.process(); 81 | }); 82 | 83 | loader.start(); 84 | } 85 | 86 | this.setGlobalTransformationMatrix = function(globalTransformationMatrix) { 87 | o.globalTransformationMatrix = globalTransformationMatrix; 88 | } 89 | } 90 | 91 | BimServerModelLoader.prototype = Object.create(BimServerModelLoader.prototype); 92 | 93 | return BimServerModelLoader; 94 | }); -------------------------------------------------------------------------------- /bimsurfer/src/BimSurfer.js: -------------------------------------------------------------------------------- 1 | window.BIMSERVER_VERSION = "1.5"; 2 | 3 | define([/*"./Notifier", "./BimServerModel", "./PreloadQuery", "./BimServerGeometryLoader", "./xeoViewer/xeoViewer",*/ "./EventHandler", "./svgViewer/svgViewer", "./threeViewer/threeViewer"], function (/*Notifier, Model, PreloadQuery, GeometryLoader, XeoViewer,*/ EventHandler, SvgViewer, ThreeViewer, _BimServerApi) { 4 | "use strict"; 5 | 6 | // Backwards compatibility 7 | /* 8 | var BimServerApi; 9 | if (_BimServerApi) { 10 | BimServerApi = _BimServerApi; 11 | } else { 12 | BimServerApi = window.BimServerClient; 13 | } 14 | */ 15 | 16 | function BimSurfer(cfg) { 17 | 18 | var self = this; 19 | 20 | EventHandler.call(this); 21 | 22 | cfg = cfg || {}; 23 | 24 | self.engine = (cfg.engine || "threejs").toLowerCase(); 25 | var engine = { 26 | svg: SvgViewer, 27 | xeogl: window.XeoViewer, 28 | threejs: ThreeViewer 29 | }[self.engine]; 30 | 31 | var viewer = this.viewer = new engine(Object.assign(cfg, {app: this})); 32 | 33 | /** 34 | * Fired whenever this BIMSurfer's camera changes. 35 | * @event camera-changed 36 | */ 37 | viewer.on("camera-changed", function() { 38 | self.fire("camera-changed", arguments); 39 | }); 40 | 41 | /** 42 | * Fired whenever this BIMSurfer's selection changes. 43 | * @event selection-changed 44 | */ 45 | viewer.on("selection-changed", function() { 46 | self.fire("selection-changed", arguments); 47 | }); 48 | 49 | // This are arrays as multiple models might be loaded or unloaded. 50 | this._idMapping = { 51 | 'toGuid': [], 52 | 'toId' : [] 53 | }; 54 | 55 | /** 56 | * Loads a model into this BIMSurfer. 57 | * @param params 58 | */ 59 | this.load = function (params) { 60 | 61 | if (params.test) { 62 | viewer.loadRandom(params); 63 | return null; 64 | 65 | } else if (params.bimserver) { 66 | return this._loadFromServer(params); 67 | 68 | } else if (params.api) { 69 | return this._loadFromAPI(params); 70 | 71 | } else if (params.src && ((window.XeoViewer && self.viewer instanceof XeoViewer) || self.viewer instanceof ThreeViewer)) { 72 | return this._loadFrom_glTF(params); 73 | } else if (params.src && self.viewer instanceof SvgViewer) { 74 | return this._loadFrom_SVG(params); 75 | } 76 | }; 77 | 78 | this._loadFromServer = function (params) { 79 | 80 | var notifier = new Notifier(); 81 | var bimServerApi = new BimServerApi(params.bimserver, notifier); 82 | 83 | params.api = bimServerApi; // TODO: Make copy of params 84 | 85 | return self._initApi(params) 86 | .then(self._loginToServer) 87 | .then(self._getRevisionFromServer) 88 | .then(self._loadFromAPI); 89 | }; 90 | 91 | this._initApi = function(params) { 92 | return new Promise(function(resolve, reject) { 93 | params.api.init(function () { 94 | resolve(params); 95 | }); 96 | }); 97 | }; 98 | 99 | this._loginToServer = function (params) { 100 | return new Promise(function(resolve, reject) { 101 | if (params.token) { 102 | params.api.setToken(params.token, function() { 103 | resolve(params) 104 | }, reject); 105 | } else { 106 | params.api.login(params.username, params.password, function() { 107 | resolve(params) 108 | }, reject); 109 | } 110 | }); 111 | }; 112 | 113 | this.shouldClearSelection = function(evt) { 114 | return !evt.shiftKey; 115 | }; 116 | 117 | this._getRevisionFromServer = function (params) { 118 | return new Promise(function(resolve, reject) { 119 | if (params.roid) { 120 | resolve(params); 121 | } else { 122 | params.api.call("ServiceInterface", "getAllRelatedProjects", {poid: params.poid}, function(data) { 123 | var resolved = false; 124 | 125 | data.forEach(function(projectData) { 126 | if (projectData.oid == params.poid) { 127 | params.roid = projectData.lastRevisionId; 128 | params.schema = projectData.schema; 129 | if (!self.models) { 130 | self.models = []; 131 | } 132 | self.models.push(projectData); 133 | resolved = true; 134 | resolve(params); 135 | } 136 | }); 137 | 138 | if (!resolved) { 139 | reject(); 140 | } 141 | }, reject); 142 | } 143 | }); 144 | }; 145 | 146 | this._loadFrom_SVG = function (params) { 147 | if (params.src) { 148 | return viewer.load(params.src + ".svg"); 149 | } 150 | }; 151 | 152 | this._loadFrom_glTF = function (params) { 153 | if (params.src) { 154 | var maxActiveProcessesEncountered = 0; 155 | var oldProgress = 0; 156 | return new Promise(function (resolve, reject) { 157 | var m = viewer.loadglTF(params.src); 158 | 159 | if (window.XeoViewer && self.viewer instanceof XeoViewer) { 160 | m.on("loaded", function() { 161 | viewer.scene.canvas.spinner.on('processes', function(n) { 162 | if (n === 0) { 163 | viewer.viewFit({}); 164 | resolve(m); 165 | } 166 | if (n > maxActiveProcessesEncountered) { 167 | maxActiveProcessesEncountered = n; 168 | } 169 | var progress = parseInt((maxActiveProcessesEncountered - n) * 100 / maxActiveProcessesEncountered); 170 | if (oldProgress != progress) { 171 | self.fire("progress", [progress]); 172 | } 173 | oldProgress = progress; 174 | }); 175 | }); 176 | } else { 177 | viewer.on("loaded", function() { 178 | resolve(viewer); 179 | 180 | if (cfg.initiallyInvisible) { 181 | viewer.setVisibility({ids: viewer.getObjectIds(), visible:false}); 182 | } 183 | }); 184 | } 185 | }); 186 | } 187 | }; 188 | 189 | this._loadFromAPI = function (params) { 190 | 191 | return new Promise(function (resolve, reject) { 192 | 193 | params.api.getModel(params.poid, params.roid, params.schema, false, 194 | function (model) { 195 | 196 | // TODO: Preload not necessary combined with the bruteforce tree 197 | var fired = false; 198 | 199 | model.query(PreloadQuery, 200 | function () { 201 | if (!fired) { 202 | fired = true; 203 | var vmodel = new Model(params.api, model); 204 | 205 | self._loadModel(vmodel); 206 | 207 | resolve(vmodel); 208 | } 209 | }); 210 | }); 211 | }); 212 | }; 213 | 214 | this._loadModel = function (model) { 215 | 216 | model.getTree().then(function (tree) { 217 | 218 | var oids = []; 219 | var oidToGuid = {}; 220 | var guidToOid = {}; 221 | 222 | var visit = function (n) { 223 | if (BIMSERVER_VERSION == "1.4") { 224 | oids.push(n.id); 225 | } else { 226 | oids[n.gid] = n.id; 227 | } 228 | oidToGuid[n.id] = n.guid; 229 | guidToOid[n.guid] = n.id; 230 | 231 | for (var i = 0; i < (n.children || []).length; ++i) { 232 | visit(n.children[i]); 233 | } 234 | }; 235 | 236 | visit(tree); 237 | 238 | self._idMapping.toGuid.push(oidToGuid); 239 | self._idMapping.toId.push(guidToOid); 240 | 241 | var models = {}; 242 | 243 | // TODO: Ugh. Undecorate some of the newly created classes 244 | models[model.model.roid] = model.model; 245 | 246 | // Notify viewer that things are loading, so viewer can 247 | // reduce rendering speed and show a spinner. 248 | viewer.taskStarted(); 249 | 250 | viewer.createModel(model.model.roid); 251 | 252 | var loader = new GeometryLoader(model.api, models, viewer); 253 | 254 | loader.addProgressListener(function (progress, nrObjectsRead, totalNrObjects) { 255 | if (progress == "start") { 256 | console.log("Started loading geometries"); 257 | self.fire("loading-started"); 258 | } else if (progress == "done") { 259 | console.log("Finished loading geometries (" + totalNrObjects + " objects received)"); 260 | self.fire("loading-finished"); 261 | viewer.taskFinished(); 262 | } 263 | }); 264 | 265 | loader.setLoadOids([model.model.roid], oids); 266 | 267 | // viewer.clear(); // For now, until we support multiple models through the API 268 | 269 | viewer.on("tick", function () { // TODO: Fire "tick" event from XeoViewer 270 | loader.process(); 271 | }); 272 | 273 | loader.start(); 274 | }); 275 | }; 276 | 277 | // Helper function to traverse over the mappings for individually loaded models 278 | var _traverseMappings = function(mappings) { 279 | return function(k) { 280 | for (var i = 0; i < mappings.length; ++i) { 281 | var v = mappings[i][k]; 282 | if (v) return v; 283 | } 284 | return null; 285 | } 286 | }; 287 | 288 | /** 289 | * Returns a list of object ids (oid) for the list of guids (GlobalId) 290 | * 291 | * @param guids List of globally unique identifiers from the IFC model 292 | */ 293 | this.toId = function(guids) { 294 | return guids.map(_traverseMappings(self._idMapping.toId)); 295 | }; 296 | 297 | /** 298 | * Returns a list of guids (GlobalId) for the list of object ids (oid) 299 | * 300 | * @param ids List of internal object ids from the BIMserver / glTF file 301 | */ 302 | this.toGuid = function(ids) { 303 | return ids.map(_traverseMappings(self._idMapping.toGuid)); 304 | }; 305 | 306 | /** 307 | * Shows/hides objects specified by id or entity type, e.g IfcWall. 308 | * 309 | * When recursive is set to true, hides children (aggregates, spatial structures etc) or 310 | * subtypes (IfcWallStandardCase ⊆ IfcWall). 311 | * 312 | * @param params 313 | */ 314 | this.setVisibility = function (params) { 315 | viewer.setVisibility(params); 316 | }; 317 | 318 | /** 319 | * Selects/deselects objects specified by id. 320 | ** 321 | * @param params 322 | */ 323 | this.setSelection = function (params) { 324 | if (cfg.initiallyInvisible) { 325 | return viewer.setVisibility(Object.assign({}, params, {visible: params.selected})); 326 | } else { 327 | return viewer.setSelection(params); 328 | } 329 | }; 330 | 331 | /** 332 | * Gets a list of selected elements. 333 | */ 334 | this.getSelection = function () { 335 | return viewer.getSelection(); 336 | }; 337 | 338 | /** 339 | * Sets color of objects specified by ids or entity type, e.g IfcWall. 340 | ** 341 | * @param params 342 | */ 343 | this.setColor = function (params) { 344 | viewer.setColor(params); 345 | }; 346 | 347 | /** 348 | * Sets opacity of objects specified by ids or entity type, e.g IfcWall. 349 | ** 350 | * @param params 351 | */ 352 | this.setOpacity = function (params) { 353 | viewer.setOpacity(params); 354 | }; 355 | 356 | /** 357 | * Fits the elements into view. 358 | * 359 | * Fits the entire model into view if ids is an empty array, null or undefined. 360 | * Animate allows to specify a transition period in milliseconds in which the view is altered. 361 | * 362 | * @param params 363 | */ 364 | this.viewFit = function (params) { 365 | viewer.viewFit(params); 366 | }; 367 | 368 | /** 369 | * 370 | */ 371 | this.getCamera = function () { 372 | return viewer.getCamera(); 373 | }; 374 | 375 | /** 376 | * 377 | * @param params 378 | */ 379 | this.setCamera = function (params) { 380 | viewer.setCamera(params); 381 | }; 382 | 383 | /** 384 | * Redefines light sources. 385 | * 386 | * @param params Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} 387 | * See http://xeoengine.org/docs/classes/Lights.html for possible params for each light type 388 | */ 389 | this.setLights = function (params) { 390 | viewer.setLights(params); 391 | }; 392 | 393 | 394 | /** 395 | * Returns light sources. 396 | * 397 | * @returns Array of lights {type: "ambient"|"dir"|"point", params: {[...]}} 398 | */ 399 | this.getLights = function () { 400 | return viewer.getLights; 401 | }; 402 | 403 | /** 404 | * 405 | * @param params 406 | */ 407 | this.reset = function (params) { 408 | viewer.reset(params); 409 | } 410 | 411 | /** 412 | * Returns a list of loaded IFC entity types in the model. 413 | * 414 | * @method getTypes 415 | * @returns {Array} List of loaded IFC entity types, with visibility flag 416 | */ 417 | this.getTypes = function() { 418 | return viewer.getTypes(); 419 | }; 420 | 421 | /** 422 | * Sets the default behaviour of mouse and touch drag input 423 | * 424 | * @method setDefaultDragAction 425 | * @param {String} action ("pan" | "orbit") 426 | */ 427 | this.setDefaultDragAction = function (action) { 428 | viewer.setDefaultDragAction(action); 429 | }; 430 | 431 | /** 432 | * Returns the world boundary of an object 433 | * 434 | * @method getWorldBoundary 435 | * @param {String} objectId id of object 436 | * @param {Object} result Existing boundary object 437 | * @returns {Object} World boundary of object, containing {obb, aabb, center, sphere} properties. See xeogl.Boundary3D 438 | */ 439 | this.getWorldBoundary = function(objectId, result) { 440 | return viewer.getWorldBoundary(objectId, result); 441 | }; 442 | 443 | /** 444 | * Destroys the BIMSurfer 445 | */ 446 | this.destroy = function() { 447 | viewer.destroy(); 448 | }; 449 | 450 | this.resize = function() { 451 | viewer.resize(); 452 | }; 453 | } 454 | 455 | BimSurfer.prototype = Object.create(EventHandler.prototype); 456 | 457 | return BimSurfer; 458 | 459 | }); 460 | -------------------------------------------------------------------------------- /bimsurfer/src/DataInputStreamReader.js: -------------------------------------------------------------------------------- 1 | define(["../lib/StringView"], function(StringView) { 2 | "use strict"; 3 | 4 | function DataInputStreamReader(arrayBuffer) { 5 | 6 | this.arrayBuffer = arrayBuffer; 7 | this.dataView = new DataView(this.arrayBuffer); 8 | this.pos = 0; 9 | 10 | this.readUTF8 = function() { 11 | var length = this.dataView.getInt16(this.pos); 12 | this.pos += 2; 13 | var view = this.arrayBuffer.slice(this.pos, this.pos + length); 14 | var result = new StringView(view).toString(); 15 | this.pos += length; 16 | return result; 17 | }; 18 | 19 | this.remaining = function() { 20 | return this.arrayBuffer.byteLength - this.pos; 21 | }; 22 | 23 | this.align4 = function() { 24 | // Skips to the next alignment of 4 (source should have done the same!) 25 | var skip = 4 - (this.pos % 4); 26 | if(skip > 0 && skip != 4) { 27 | // console.log("Skip", skip); 28 | this.pos += skip; 29 | } 30 | }; 31 | 32 | this.align8 = function() { 33 | // Skips to the next alignment of 4 (source should have done the same!) 34 | var skip = 8 - (this.pos % 8); 35 | if(skip > 0 && skip != 8) { 36 | // console.log("Skip", skip); 37 | this.pos += skip; 38 | } 39 | }; 40 | 41 | this.readDoubleArray = function(length) { 42 | var result = new Float64Array(this.arrayBuffer, this.pos, length); 43 | this.pos += length * 8; 44 | return result; 45 | }, 46 | 47 | this.readFloat = function() { 48 | var value = this.dataView.getFloat32(this.pos, true); 49 | this.pos += 4; 50 | return value; 51 | }; 52 | 53 | this.readInt = function() { 54 | var value = this.dataView.getInt32(this.pos, true); 55 | this.pos += 4; 56 | return value; 57 | }; 58 | 59 | this.readByte = function() { 60 | var value = this.dataView.getInt8(this.pos); 61 | this.pos += 1; 62 | return value; 63 | }; 64 | 65 | this.readLong = function() { 66 | var value = this.dataView.getUint32(this.pos, true) + 0x100000000 * this.dataView.getUint32(this.pos + 4, true); 67 | this.pos += 8; 68 | return value; 69 | }; 70 | 71 | this.readFloatArray2 = function(length) { 72 | var results = []; 73 | for (var i=0; i= -1) { 18 | h.splice(i, 1); 19 | found = true; 20 | } 21 | } 22 | if (!found) { 23 | throw new Error("Handler not found"); 24 | } 25 | }; 26 | 27 | EventHandler.prototype.fire = function(evt, args) { 28 | var h = this.handlers[evt]; 29 | if (!h) { 30 | return; 31 | } 32 | for (var i = 0; i < h.length; ++i) { 33 | h[i].apply(this, args); 34 | } 35 | }; 36 | 37 | return EventHandler; 38 | 39 | }); -------------------------------------------------------------------------------- /bimsurfer/src/MetaDataRenderer.js: -------------------------------------------------------------------------------- 1 | define(["./EventHandler", "./Request", "./Utils"], function(EventHandler, Request, Utils) { 2 | "use strict"; 3 | 4 | function Row(args) { 5 | var self = this; 6 | var num_names = 0; 7 | var num_values = 0; 8 | 9 | this.setName = function(name) { 10 | if (num_names++ > 0) { 11 | args.name.appendChild(document.createTextNode(" ")); 12 | } 13 | args.name.appendChild(document.createTextNode(name)); 14 | } 15 | 16 | this.setValue = function(value) { 17 | if (num_values++ > 0) { 18 | args.value.appendChild(document.createTextNode(", ")); 19 | } 20 | args.value.appendChild(document.createTextNode(value)); 21 | } 22 | } 23 | 24 | function identity(x) { return x; } 25 | 26 | function Section(args) { 27 | var self = this; 28 | 29 | var div = self.div = document.createElement("div"); 30 | var nameh = document.createElement("h3"); 31 | var table = document.createElement("table"); 32 | 33 | var tr = document.createElement("tr"); 34 | table.appendChild(tr); 35 | var nameth = document.createElement("th"); 36 | var valueth = document.createElement("th"); 37 | nameth.appendChild(document.createTextNode("Name")); 38 | valueth.appendChild(document.createTextNode("Value")); 39 | tr.appendChild(nameth); 40 | tr.appendChild(valueth); 41 | 42 | div.appendChild(nameh); 43 | div.appendChild(table); 44 | 45 | args.domNode.appendChild(div); 46 | 47 | this.setName = function(name) { 48 | nameh.appendChild(document.createTextNode(name)); 49 | } 50 | 51 | this.addRow = function() { 52 | var tr = document.createElement("tr"); 53 | table.appendChild(tr); 54 | var nametd = document.createElement("td"); 55 | var valuetd = document.createElement("td"); 56 | tr.appendChild(nametd); 57 | tr.appendChild(valuetd); 58 | return new Row({name:nametd, value:valuetd}); 59 | } 60 | }; 61 | 62 | function loadModelFromSource(src) { 63 | return Request.Make({url: src}).then(function(xml) { 64 | var json = Utils.XmlToJson(xml, {'Name': 'name', 'id': 'guid'}); 65 | return loadModelFromJson(json); 66 | }); 67 | } 68 | 69 | 70 | function loadModelFromJson(json) { 71 | return new Promise(function (resolve, reject) { 72 | var psets = Utils.FindNodeOfType(json, "properties")[0]; 73 | var project = Utils.FindNodeOfType(json, "decomposition")[0].children[0]; 74 | var types = Utils.FindNodeOfType(json, "types")[0]; 75 | 76 | var objects = {}; 77 | var typeObjects = {}; 78 | var properties = {}; 79 | (psets.children || []).forEach(function(pset) { 80 | properties[pset.guid] = pset; 81 | }); 82 | 83 | var visitObject = function(parent, node) { 84 | var props = []; 85 | var o = (parent && parent.ObjectPlacement) ? objects : typeObjects; 86 | 87 | if (node["xlink:href"]) { 88 | if (!o[parent.guid]) { 89 | var p = Utils.Clone(parent); 90 | p.GlobalId = p.guid; 91 | o[p.guid] = p; 92 | o[p.guid].properties = [] 93 | } 94 | var g = node["xlink:href"].substr(1); 95 | var p = properties[g]; 96 | if (p) { 97 | o[parent.guid].properties.push(p); 98 | } else if (typeObjects[g]) { 99 | // If not a pset, it is a type, so concatenate type props 100 | o[parent.guid].properties = o[parent.guid].properties.concat(typeObjects[g].properties); 101 | } 102 | } 103 | (node.children || []).forEach(function(n) { 104 | visitObject(node, n); 105 | }); 106 | }; 107 | 108 | visitObject(null, types); 109 | var numTypes = Object.keys(objects).length; 110 | visitObject(null, project); 111 | var numTotal = Object.keys(objects).length; 112 | var productCount = numTotal - numTypes; 113 | 114 | resolve({model: {objects: objects, productCount: productCount, source: 'XML'}}); 115 | }); 116 | } 117 | 118 | function MetaDataRenderer(args) { 119 | 120 | var self = this; 121 | EventHandler.call(this); 122 | 123 | var models = {}; 124 | var domNode = document.getElementById(args['domNode']); 125 | 126 | this.addModel = function(args) { 127 | 128 | return new Promise(function (resolve, reject) { 129 | if (args.model) { 130 | models[args.id] = args.model; 131 | resolve(args.model); 132 | } else { 133 | var fn = args.src ? loadModelFromSource : loadModelFromJson; 134 | fn(args.src ? args.src : args.json).then(function(m) { 135 | models[args.id] = m; 136 | resolve(m); 137 | }); 138 | } 139 | }); 140 | 141 | }; 142 | 143 | var renderAttributes = function(elem) { 144 | var s = new Section({domNode:domNode}); 145 | s.setName(elem.type || elem.getType()); 146 | ["GlobalId", "Name", "OverallWidth", "OverallHeight", "Tag", "PredefinedType", "FlowDirection"].forEach(function(k) { 147 | var v = elem[k]; 148 | if (typeof(v) === 'undefined') { 149 | var fn = elem["get"+k]; 150 | if (fn) { 151 | v = fn.apply(elem); 152 | } 153 | } 154 | if (typeof(v) !== 'undefined') { 155 | var r = s.addRow(); 156 | r.setName(k); 157 | r.setValue(v); 158 | } 159 | }); 160 | return s; 161 | }; 162 | 163 | var renderPSet = function(pset) { 164 | var s = new Section({domNode:domNode}); 165 | if (pset.name && pset.children) { 166 | s.setName(pset.name); 167 | pset.children.forEach(function(v) { 168 | var r = s.addRow(); 169 | r.setName(v.name); 170 | r.setValue(v.NominalValue); 171 | }); 172 | } else { 173 | pset.getName(function(name) { 174 | s.setName(name); 175 | }); 176 | pset.getHasProperties(function(prop) { 177 | var r = s.addRow(); 178 | prop.getName(function(name) { 179 | r.setName(name); 180 | }); 181 | prop.getNominalValue(function(value) { 182 | r.setValue(value._v); 183 | }); 184 | }); 185 | } 186 | return s; 187 | }; 188 | 189 | var queryPSet = function(resolve, pset, psetName, propName) { 190 | if (pset.name && pset.children) { 191 | // based on XML 192 | if (pset.name !== psetName) { 193 | return false; 194 | } 195 | return pset.children.map(function(v) { 196 | if (v.name !== propName) { 197 | return false; 198 | } 199 | resolve(v.NominalValue); 200 | return true; 201 | }).some(identity); 202 | } else { 203 | // based on BIMserver 204 | pset.getName(function(name) { 205 | if (name !== psetName) { 206 | return; 207 | } 208 | pset.getHasProperties(function(prop) { 209 | prop.getName(function(name) { 210 | if (name !== propName) { 211 | return; 212 | } 213 | prop.getNominalValue(function(value) { 214 | resolve(value._v); 215 | }); 216 | }); 217 | 218 | }); 219 | }); 220 | 221 | } 222 | return s; 223 | }; 224 | 225 | this.query = function(oid, psetName, propName) { 226 | return new Promise(function(resolve, reject) { 227 | oid = oid.split(':'); 228 | if (oid.length == 1) { 229 | oid = [Object.keys(models)[0], oid]; 230 | } 231 | var model = models[oid[0]].model || models[oid[0]].apiModel; 232 | var ob = model.objects[oid[1]]; 233 | 234 | if (model.source === 'XML') { 235 | let containedInPset = ob.properties.map(function(pset) { 236 | return queryPSet(resolve, pset, psetName, propName); 237 | }); 238 | console.log(containedInPset); 239 | if (!containedInPset.some(identity)) { 240 | reject(); 241 | } 242 | } else { 243 | ob.getIsDefinedBy(function(isDefinedBy){ 244 | if (isDefinedBy.getType() == "IfcRelDefinesByProperties") { 245 | isDefinedBy.getRelatingPropertyDefinition(function(pset){ 246 | if (pset.getType() == "IfcPropertySet") { 247 | queryPSet(resolve, pset, propsetName, propName); 248 | } 249 | }); 250 | } 251 | }); 252 | } 253 | }); 254 | }; 255 | 256 | let selectedParent = null; 257 | let selectedElement = null; 258 | let selectedElements = []; 259 | 260 | this.setSelectedParent = function(oid) { 261 | selectedParent = oid; 262 | this.processSelection(); 263 | } 264 | 265 | this.setSelected = function(oid) { 266 | // @todo this should take into account the clear flag, to detect 267 | // multiple selection events from the 3d viewer, but it's not 268 | // available in this handler. 269 | 270 | if (oid.length === 1) { 271 | selectedElement = oid[0]; 272 | } else { 273 | selectedElement = null; 274 | } 275 | this.processSelection(); 276 | } 277 | 278 | this.processSelection = function () { 279 | 280 | selectedElements = [selectedParent, selectedElement].filter(x => x); 281 | 282 | if (self.highlightMode) { 283 | 284 | (self.selectedSections || []).forEach(function(s) { 285 | s.div.className = ""; 286 | }); 287 | 288 | if (selectedElement) { 289 | self.sections[selectedElement].forEach(function(s) { 290 | s.div.className = "selected"; 291 | }); 292 | 293 | self.selectedSections = self.sections[selectedElement]; 294 | } else { 295 | self.selectedSections = []; 296 | } 297 | 298 | } else { 299 | 300 | domNode.innerHTML = ""; 301 | 302 | selectedElements.forEach(oid => { 303 | 304 | oid = oid.split(':'); 305 | if (oid.length == 1) { 306 | oid = [Object.keys(models)[0], oid]; 307 | } 308 | 309 | var idModel; 310 | 311 | for(var i =0; i { 6 | let parts = id.split('-'); 7 | parts = parts.slice(parts.lastIndexOf('product')); 8 | 9 | const id2 = parts.filter(s => s !== 'product' && s !== 'body' && s !== 'storey').join('-'); 10 | return Utils.CompressGuid(id2); 11 | } 12 | 13 | function createElem(tag, attrs, NS) { 14 | const ob = NS ? document.createElementNS(NS, tag) : document.createElement(tag); 15 | for(let [k,v] of Object.entries(attrs || {})) { 16 | if (NS) { 17 | ob.setAttribute(k, v); 18 | } else { 19 | ob.setAttributeNS(null, k, v); 20 | } 21 | } 22 | return ob; 23 | } 24 | 25 | function children(node) { 26 | return Array.from(node.childNodes).filter(n => n.nodeType === 1); 27 | } 28 | 29 | function testOverlap(text, path) { 30 | var Mt = text.getScreenCTM().inverse(); 31 | 32 | var P1 = text.ownerSVGElement.createSVGPoint(); 33 | var P2 = text.ownerSVGElement.createSVGPoint(); 34 | var bb = text.getBoundingClientRect(); 35 | 36 | if (typeof(bb) === 'undefined') { 37 | return false; 38 | } 39 | 40 | P1.x = bb.x; 41 | P1.y = bb.y; 42 | P2.x = bb.x + bb.width; 43 | P2.y = bb.y + bb.height; 44 | 45 | P1 = P1.matrixTransform(Mt); 46 | P2 = P2.matrixTransform(Mt); 47 | 48 | var bb_width = P2.x - P1.x; 49 | var bb_height = P2.y - P1.y; 50 | 51 | var len = path.getTotalLength(); 52 | var u = 0.; 53 | var step = (bb_width < bb_height ? bb_width : bb_height) / 2.; 54 | 55 | while (u < len) { 56 | // Sample some points over the bath when contained in the AABB rectangle 57 | // for the text we know text and path intersect and text should be hidden 58 | var p = path.getPointAtLength(u); 59 | if (p.x >= P1.x && p.y >= P1.y && p.x <= P2.x && p.y <= P2.y) { 60 | return true; 61 | } 62 | u += step; 63 | } 64 | 65 | return false; 66 | } 67 | 68 | function SvgViewer(cfg) { 69 | let self = this; 70 | 71 | EventHandler.call(this); 72 | 73 | self.selected = new Set(); 74 | self.lineMapping = new Map(); 75 | 76 | self.svg = null; 77 | 78 | var xmlns = "http://www.w3.org/2000/svg"; 79 | 80 | var elem = document.getElementById(cfg.domNode); 81 | 82 | self.load = function(src) { 83 | 84 | self.select = createElem("select"); 85 | /*self.obj = createElem("object", { 86 | type : "image/svg+xml", 87 | data : src 88 | });*/ 89 | self.obj = createElem("div"); 90 | elem.appendChild(self.obj); 91 | var d = createElem("div", { 92 | class: "selectcontainer" 93 | }); 94 | 95 | elem.appendChild(d); 96 | d.appendChild(self.select); 97 | 98 | self.obj.style.width = elem.offsetWidth + 'px'; 99 | self.obj.style.height = (elem.offsetHeight - d.offsetHeight) + 'px'; 100 | 101 | return fetch(src) 102 | .then(response => { 103 | if (!response.ok) { 104 | throw new Error("HTTP status " + response.status); 105 | } else { 106 | return response.text(); 107 | } 108 | }).then(text => { 109 | self.obj.innerHTML = text; 110 | var svg = self.obj.getElementsByTagName('svg')[0]; 111 | svg.style.width = svg.style.height = '100%'; 112 | }).catch(exc => { 113 | self.error = true; 114 | }).then(() => { 115 | self._onload(); 116 | }); 117 | } 118 | 119 | self.resize = function() {} 120 | 121 | self._updateState = function(n, parentState) { 122 | if (parentState || self.selected.has(n)) { 123 | if (!self.lineMapping.has(n)) { 124 | for (let c of children(n)) { 125 | self._updateState(c, true); 126 | } 127 | if (n.tagName == 'path') { 128 | const line = n.cloneNode(false); 129 | line.style.cssText = "fill: none; stroke: lime; stroke-width: 3px"; 130 | self.lineMapping.set(n, line); 131 | // children[0] is the pan-zoom viewport 132 | self.rootGroup.appendChild(line); 133 | } 134 | } 135 | } else { 136 | if (self.lineMapping.has(n)) { 137 | // the groups do not get a line 138 | self.rootGroup.removeChild(self.lineMapping.get(n)); 139 | self.lineMapping.delete(n); 140 | } 141 | for (let c of children(n)) { 142 | self._updateState(c, false); 143 | } 144 | } 145 | } 146 | 147 | self.setSelection = function(params) { 148 | const updateState = (n) => {self._updateState(n)}; 149 | if (params.clear) { 150 | const previous = Array.from(self.selected); 151 | self.selected.clear(); 152 | previous.forEach(updateState); 153 | } 154 | const fn = params.selected ? self.selected.add : self.selected.delete; 155 | 156 | const convertGuidOrIdentity = (s) => (self.guidToIdMap.get(s) || [s]); 157 | 158 | var nodes = null; 159 | if (params.nodes) { 160 | nodes = params.nodes; 161 | } else if (params.ids) { 162 | nodes = params.ids.flatMap(convertGuidOrIdentity).map(self.svg.getElementById.bind(self.svg)).filter((s) => (s !== null)); 163 | } 164 | 165 | nodes.forEach(fn.bind(self.selected)); 166 | nodes.forEach(updateState); 167 | 168 | const getStoreyIdx = (n) => { 169 | while (n) { 170 | let idx = self.storeys.indexOf(n); 171 | if (idx !== -1) { 172 | return idx; 173 | } 174 | n = n.parentElement; 175 | } 176 | } 177 | 178 | const sids = new Set(nodes.map(getStoreyIdx)); 179 | if (params.clear && sids.size && !sids.has(self.select.selectedIndex)) { 180 | self.select.selectedIndex = Array.from(sids).sort()[0]; 181 | self.toggleStorey(self.select.selectedIndex, false); 182 | } 183 | 184 | if (params.nodes) { 185 | const ids = params.nodes.map((n) => (n.getAttribute("id"))).map(convertId); 186 | self.fire("selection-changed", [{objects: ids}]); 187 | } 188 | } 189 | 190 | self.toggleStorey = function(i, clearSelection) { 191 | if (clearSelection !== false) { 192 | self.setSelection({clear: true, nodes:[], selected: true}); 193 | } 194 | self.storeys.forEach((s, j)=>{ 195 | s.style.visibility = (i == j) ? 'visible' : 'hidden'; 196 | }); 197 | self.updateTextVisibility(); 198 | }; 199 | 200 | self.reset = function(args) { 201 | if (args.colors) { 202 | for (let p of Array.from(self.svg.getElementsByTagName("path"))) { 203 | /* 204 | if (p.parentNode.className.baseVal == 'IfcDoor') { 205 | // @todo this is mainly for the door arcs, but we need to annotate closed areas and line annotations better in the IfcConvert binary 206 | p.style.fill = 'none'; 207 | } else { 208 | p.style.fill = '#444'; 209 | if (p.parentNode.className.baseVal == 'IfcSpace') { 210 | p.style.fillOpacity = '.2'; 211 | } 212 | } 213 | p.style.stroke = '#222'; 214 | */ 215 | } 216 | } 217 | } 218 | 219 | self.setColor = function(args) { 220 | const convertGuidOrIdentity = (s) => (self.guidToIdMap.get(s) || s); 221 | var nodes = args.ids.map(convertGuidOrIdentity).map((s)=>`product-product-${s}-body`).map(self.svg.getElementById.bind(self.svg)).filter((s) => (s !== null)); 222 | var color = "#" + args.color.map((f) => (("0" + parseInt(f * 255.).toString(16)).substr(-2))).join(""); 223 | 224 | nodes.forEach((n) => { 225 | n.style.fill = n.style.stroke = color; 226 | Array.from(n.getElementsByTagName("path")).forEach((n) => { 227 | n.style.fill = n.style.stroke = color; 228 | }); 229 | }); 230 | } 231 | 232 | self.destroy = function() { 233 | self.spz.destroy(); 234 | while(elem.lastChild) { 235 | elem.removeChild(elem.lastChild); 236 | } 237 | } 238 | 239 | self.updateTextVisibility = function() { 240 | if (!self.textNodes) return; 241 | self.textNodes.forEach(t => { 242 | let n = t; 243 | var storeyVisible; 244 | while (n) { 245 | if (self.storeys.indexOf(n) !== -1) { 246 | storeyVisible = n.style.visibility !== 'hidden'; 247 | break; 248 | } 249 | n = n.parentElement; 250 | } 251 | // Dimensions and storey elevations are always visible 252 | let cls = t.parentElement.className.baseVal 253 | var visible = storeyVisible && (cls.includes("Dimension") || cls.includes("IfcBuildingStorey") || !Array.from(t.parentElement.querySelectorAll('path')).some((path) => { 254 | return testOverlap(t, path) 255 | })); 256 | t.style.visibility = visible ? 'visible' : 'hidden'; 257 | }); 258 | } 259 | 260 | self._onload = function() { 261 | if (self.error) { 262 | return; 263 | } 264 | 265 | var svgDoc = self.obj.contentDocument || self.obj.getElementsByTagName('svg')[0]; 266 | self.svg = self.obj.contentDocument ? children(svgDoc)[0] : svgDoc; 267 | self.reset({colors:true}); 268 | self.storeys = children(self.svg).filter(n => n.tagName == 'g'); 269 | 270 | if (self.storeys.length === 0) { 271 | return; 272 | } 273 | 274 | self.guidToIdMap = new Map(); 275 | const traverse = (e) => { 276 | const id = e.getAttribute('id'); 277 | if (id !== null) { 278 | const guid = convertId(id); 279 | if (!self.guidToIdMap.has(guid)) { 280 | self.guidToIdMap.set(guid, []); 281 | } 282 | self.guidToIdMap.get(guid).push(id); 283 | } 284 | for (const c of children(e)) { 285 | traverse(c); 286 | } 287 | }; 288 | traverse(self.svg); 289 | self.toggleStorey(0); 290 | self.select.onchange = function(evt) { 291 | self.toggleStorey(evt.target.selectedIndex); 292 | } 293 | self.storeys.forEach((s, i) => { 294 | const opt = document.createElement('option'); 295 | 296 | var N; 297 | if (s.hasAttribute('data-name')) { 298 | N = s.getAttribute('data-name') 299 | } else if (s.hasAttribute("ifc:name")) { 300 | N = s.getAttribute("ifc:name") 301 | } else { 302 | N = `storey ${i}`; 303 | } 304 | opt.setAttribute("value", N); 305 | opt.appendChild(document.createTextNode(N)); 306 | self.select.appendChild(opt); 307 | }); 308 | self.textNodes = Array.from(self.svg.querySelectorAll('text')); 309 | const updateZoom = (scale) => { 310 | self.svg.style.fontSize = 10 / self.rootGroup.transform.baseVal.getItem(0).matrix.a + "pt"; 311 | self.updateTextVisibility(); 312 | }; 313 | self.spz = svgPanZoom(self.obj.contentDocument ? self.obj : self.obj.getElementsByTagName('svg')[0], { 314 | zoomEnabled: true, 315 | preventMouseEventsDefault: true, 316 | controlIconsEnabled: false, 317 | onZoom: updateZoom 318 | }); 319 | self.rootGroup = children(self.svg).filter(n => n.tagName == 'g')[0]; 320 | updateZoom(); 321 | svgDoc.onclick = function(evt) { 322 | let n = evt.target; 323 | const nodes = [] 324 | if (n.tagName !== 'svg') { 325 | while (n.tagName !== 'g' && n.parentNode) { 326 | n = n.parentNode; 327 | } 328 | nodes.push(n); 329 | } 330 | self.setSelection({ 331 | selected: true, 332 | clear: cfg.app.shouldClearSelection(evt), 333 | nodes: nodes 334 | }); 335 | } 336 | }; 337 | } 338 | 339 | SvgViewer.prototype = Object.create(EventHandler.prototype); 340 | 341 | return SvgViewer; 342 | }); 343 | -------------------------------------------------------------------------------- /bimsurfer/src/xeoViewer/controls/cursors/rotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AECgeeks/BIMsurfer2/926d1fcc08a373c879a2416110bc3107be1d654c/bimsurfer/src/xeoViewer/controls/cursors/rotate.png -------------------------------------------------------------------------------- /bimsurfer/src/xeoViewer/effects/highlightEffect.js: -------------------------------------------------------------------------------- 1 | define(["../../../lib/xeogl"], function () { 2 | 3 | "use strict"; 4 | 5 | xeogl.HighlightEffect = xeogl.Component.extend({ 6 | 7 | type: "xeogl.HighlightEffect", 8 | 9 | _init: function (cfg) { 10 | 11 | this._modes = this.create({ 12 | type: "xeogl.Modes", 13 | transparent: true, 14 | collidable: false // Has no collision boundary of its own 15 | }); 16 | 17 | this._stage = this.create({ 18 | type: "xeogl.Stage", 19 | priority: 2 20 | }); 21 | 22 | this._depthBuf = this.create({ 23 | type: "xeogl.DepthBuf", 24 | active: false 25 | }); 26 | 27 | this._emissiveColor = (cfg.color || [0.2, 0.9, 0.2]).slice(0,3); 28 | this._opacity = cfg.color && cfg.color.length > 3 ? cfg.color[3] : 0.25; 29 | 30 | this._helpers = {}; 31 | this._freeHelpers = []; 32 | }, 33 | 34 | add: function (bimObject) { 35 | var entities = bimObject.entities; 36 | if (entities) { 37 | var entity; 38 | for (var i = 0, len = entities.length; i < len; i++) { 39 | entity = entities[i]; 40 | this._createHelper(entity); 41 | } 42 | } else { 43 | this._createHelper(bimObject); 44 | } 45 | }, 46 | 47 | _createHelper: function (entity) { 48 | var helper = this._freeHelpers.pop(); 49 | if (!helper) { 50 | helper = this.create({ 51 | type: "xeogl.Entity", 52 | geometry: entity.geometry, 53 | transform: entity.transform, 54 | material: this.create({ 55 | type: "xeogl.PhongMaterial", 56 | emissive: this._emissiveColor, 57 | specular: [0, 0, 0], 58 | diffuse: [0, 0, 0], 59 | ambient: [0, 0, 0], 60 | opacity: this._opacity 61 | }), 62 | modes: this._modes, 63 | stage: this._stage, 64 | depthBuf: this._depthBuf, 65 | visibility: this.create({ 66 | type: "xeogl.Visibility", 67 | visible: true 68 | }), 69 | meta: { 70 | entityId: entity.id 71 | } 72 | }); 73 | } else { 74 | helper.geometry = entity.geometry; 75 | helper.material.diffuse = entity.material.diffuse; 76 | helper.material.ambient = entity.material.ambient; 77 | helper.transform = entity.transform; 78 | helper.visibility.visible = true; 79 | helper.meta.entityId = entity.id; 80 | } 81 | this._helpers[entity.id] = helper; 82 | }, 83 | 84 | clear: function () { 85 | var helper; 86 | for (var id in this._helpers) { 87 | if (this._helpers.hasOwnProperty(id)) { 88 | helper = this._helpers[id]; 89 | this._destroyHelper(helper); 90 | } 91 | } 92 | }, 93 | 94 | remove: function (bimObject) { 95 | var entities = bimObject.entities; 96 | var entity; 97 | for (var i = 0, len = entities.length; i < len; i++) { 98 | entity = entities[i]; 99 | var helper = this._helpers[entity.id]; 100 | if (helper) { 101 | this._destroyHelper(helper); 102 | } 103 | } 104 | }, 105 | 106 | _destroyHelper: function (helper) { 107 | helper.visibility.visible = false; 108 | this._freeHelpers.push(helper); 109 | delete this._helpers[helper.meta.entityId]; 110 | } 111 | 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /bimsurfer/src/xeoViewer/entities/bimModel.js: -------------------------------------------------------------------------------- 1 | define(["../../../lib/xeogl"], function () { 2 | 3 | "use strict"; 4 | 5 | /** 6 | Custom xeoEngine component that represents a BIMSurfer model within a xeoEngine scene. 7 | 8 | @class BIMModel 9 | @module XEO 10 | @constructor 11 | @param [scene] {Scene} Parent {{#crossLink "Scene"}}{{/crossLink}}. 12 | @param [cfg] {*} Configs 13 | @param [cfg.id] {String} Optional ID, unique among all components in the parent scene, generated automatically when omitted. 14 | @param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this xeogl.BIMModel. 15 | @extends Component 16 | */ 17 | xeogl.BIMModel = xeogl.Component.extend({ 18 | 19 | // JavaScript class name for this xeogl.BIMModel. 20 | type: "xeogl.BIMModel", 21 | 22 | // Constructor 23 | _init: function (cfg) { 24 | this.collection = this.create({ 25 | type: "xeogl.Collection"// http://xeoengine.org/docs/classes/Collection.html 26 | }); 27 | }, 28 | 29 | // Adds a BIMObject to this BIMModel 30 | addObject: function (object) { 31 | this.collection.add(object); 32 | } 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /bimsurfer/src/xeoViewer/entities/bimObject.js: -------------------------------------------------------------------------------- 1 | define(["../../../lib/xeogl"], function () { 2 | 3 | "use strict"; 4 | 5 | /** 6 | Custom xeoEngine component that represents a BIMSurfer object within a xeoEngine scene. 7 | 8 | An object consists of a set of xeogl.Entity's that share components between them. 9 | 10 | The components control functionality for the Entity's as a group, while the Entity's 11 | themselves each have their own xeogl.Geometry. 12 | 13 | This way, we are able to have BIM objects containing multiple geometries. 14 | 15 | @class BIMObject 16 | @module XEO 17 | @constructor 18 | @param [scene] {Scene} Parent {{#crossLink "Scene"}}{{/crossLink}}. 19 | @param [cfg] {*} Configs 20 | @param [cfg.id] {String} Optional ID, unique among all components in the parent scene, generated automatically when omitted. 21 | @param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this xeogl.BIMObject. 22 | @extends Component 23 | */ 24 | xeogl.BIMObject = xeogl.Component.extend({ 25 | 26 | /** 27 | JavaScript class name for this xeogl.BIMObject. 28 | 29 | @property type 30 | @type String 31 | @final 32 | */ 33 | type: "xeogl.BIMObject", 34 | 35 | // Constructor 36 | 37 | _init: function (cfg) { 38 | 39 | // Model this object belongs to, will be null when no model 40 | this.model = cfg.model; // xeogl.BIMModel 41 | 42 | // Modelling transform component 43 | this.transform = this.create({ 44 | type: "xeogl.Transform",// http://xeoengine.org/docs/classes/Transform.html 45 | matrix: cfg.matrix 46 | }); 47 | 48 | // Visibility control component. 49 | this.visibility = this.create({ 50 | type: "xeogl.Visibility", // http://xeoengine.org/docs/classes/Visibility.html 51 | visible: true 52 | }); 53 | 54 | // Material component 55 | this.material = this.create({ 56 | type: "xeogl.PhongMaterial", // http://xeoengine.org/docs/classes/Material.html 57 | emissive: [0, 0, 0], 58 | diffuse: [Math.random(), Math.random(), Math.random()], // Random color until we set for type 59 | opacity: 1.0 60 | }); 61 | 62 | // Rendering modes component 63 | this.modes = this.create({ 64 | type: "xeogl.Modes", // http://xeoengine.org/docs/classes/Modes.html 65 | transparent: false, 66 | backfaces: true 67 | }); 68 | 69 | // When highlighting, causes this object to render after non-highlighted objects 70 | this.stage = this.create({ 71 | type: "xeogl.Stage", 72 | priority: 0 73 | }); 74 | 75 | // When highlighting, we use this component to disable depth-testing so that this object 76 | // appears to "float" over non-highlighted objects 77 | this.depthBuf = this.create({ 78 | type: "xeogl.DepthBuf", 79 | active: true 80 | }); 81 | 82 | // Create a xeogl.Entity for each xeogl.Geometry 83 | // Each xeogl.Entity shares the components defined above 84 | 85 | // TODO: If all geometries are of same primitive, then we can combine them 86 | 87 | this.entities = []; 88 | var entity; 89 | 90 | for (var i = 0, len = cfg.geometryIds.length; i < len; i++) { 91 | 92 | entity = this.create({ // http://xeoengine.org/docs/classes/Entity.html 93 | type: "xeogl.Entity", 94 | meta: { 95 | objectId: this.id 96 | }, 97 | geometry: "geometry." + cfg.geometryIds[i], 98 | transform: this.transform, 99 | visibility: this.visibility, 100 | material: this.material, 101 | modes: this.modes, 102 | stage: this.stage, 103 | depthBuf: this.depthBuf 104 | }); 105 | 106 | this.entities.push(entity); 107 | } 108 | }, 109 | 110 | add: function(geometryId){ 111 | var entity = this.create({ // http://xeoengine.org/docs/classes/Entity.html 112 | type: "xeogl.Entity", 113 | meta: { 114 | objectId: this.id 115 | }, 116 | geometry: "geometry." + geometryId, 117 | transform: this.transform, 118 | visibility: this.visibility, 119 | material: this.material, 120 | modes: this.modes, 121 | stage: this.stage, 122 | depthBuf: this.depthBuf 123 | }); 124 | 125 | this.entities.push(entity); 126 | }, 127 | 128 | // Define read-only properties of xeogl.BIMObject 129 | 130 | _props: { 131 | 132 | // World-space bounding volume 133 | worldBoundary: { 134 | get: function () { 135 | return this.entities[0].worldBoundary 136 | } 137 | }, 138 | 139 | // View-space bounding volume 140 | viewBoundary: { 141 | get: function () { 142 | return this.entities[0].viewBoundary 143 | } 144 | }, 145 | 146 | // Canvas-space bounding volume 147 | canvasBoundary: { 148 | get: function () { 149 | return this.entities[0].viewBoundary 150 | } 151 | }, 152 | 153 | // Whether or not this object is highlighted 154 | highlighted: { 155 | set: function (highlight) { 156 | this.depthBuf.active = !highlight; 157 | this.stage.priority = highlight ? 2 : 0; 158 | this.material.emissive = highlight ? [0.5, 0.5, 0.5] : [0, 0, 0]; 159 | } 160 | } 161 | } 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /bimsurfer/src/xeoViewer/helpers/bimBoundaryHelper.js: -------------------------------------------------------------------------------- 1 | define(["../../../lib/xeogl"], function () { 2 | 3 | "use strict"; 4 | 5 | /** 6 | Custom xeoEngine component that shows a wireframe box representing an non axis-aligned 3D boundary. 7 | */ 8 | var BIMBoundaryHelperEntity = xeogl.Entity.extend({ 9 | 10 | type: "xeogl.BIMBoundaryHelperEntity", 11 | 12 | _init: function (cfg) { 13 | 14 | var obbGeometry = this.create({ 15 | type: "xeogl.OBBGeometry" 16 | }); 17 | 18 | var phongMaterial = this.create({ 19 | type: "xeogl.PhongMaterial", 20 | diffuse: cfg.color || [0.5, 0.5, 0.5], 21 | ambient: [0, 0, 0], 22 | specular: [0, 0, 0], 23 | lineWidth: 2, 24 | }); 25 | 26 | var nonCollidableMode = this.create({ 27 | type: "xeogl.Modes", 28 | collidable: false // This helper has no collision boundary of its own 29 | }); 30 | 31 | // Causes this entity to render after all other entities 32 | var stagePriorityThree = this.create({ 33 | type: "xeogl.Stage", 34 | priority: 3 35 | }); 36 | 37 | var visible = this.create({ 38 | type: "xeogl.Visibility", 39 | visible: true 40 | }); 41 | 42 | // Disables depth-testing so that this entity 43 | // appears to "float" over other entities 44 | var depthBufInactive = this.create({ 45 | type: "xeogl.DepthBuf", 46 | active: false 47 | }); 48 | 49 | this._super(xeogl._apply({ 50 | geometry: obbGeometry, 51 | material: phongMaterial, 52 | modes: nonCollidableMode, 53 | stage: stagePriorityThree, 54 | depthBuf: depthBufInactive, 55 | visibility: visible 56 | }, cfg)); 57 | 58 | } 59 | }); 60 | 61 | xeogl.BIMBoundaryHelper = function(scene, viewer, cfg) { 62 | 63 | var self = this; 64 | 65 | self._entities = {}; 66 | self._pool = []; 67 | 68 | self.setSelected = function(ids) { 69 | 70 | var oldIds = Object.keys(self._entities); 71 | 72 | ids.forEach(function(id) { 73 | if (!self._entities[id]) { 74 | var h; 75 | if (self._pool.length > 0) { 76 | h = self._entities[id] = self._pool.shift(); 77 | h.visibility.visible = true; 78 | } else { 79 | h = self._entities[id] = new BIMBoundaryHelperEntity(scene, cfg); 80 | } 81 | h.geometry.boundary = viewer.getObject(id).worldBoundary; 82 | } 83 | 84 | var oldIdx = oldIds.indexOf(id); 85 | if (oldIdx !== -1) { 86 | oldIds.splice(oldIdx, 1); 87 | } 88 | }); 89 | 90 | oldIds.forEach(function(id) { 91 | var h = self._entities[id]; 92 | h.visibility.visible = false; 93 | self._pool.push(h); 94 | delete self._entities[id]; 95 | }); 96 | }; 97 | 98 | }; 99 | 100 | }); 101 | -------------------------------------------------------------------------------- /bimsurfer/src/xeoViewer/utils/collection.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Components for managing collections of components. 3 | * 4 | * @module xeogl 5 | * @submodule collections 6 | */;/** 7 | A **Collection** is a set of {{#crossLink "Component"}}Components{{/crossLink}}. 8 | 9 |
    10 |
  • A {{#crossLink "Component"}}Component{{/crossLink}} can be included in more than one Collection.
  • 11 |
  • {{#crossLink "Component"}}Components{{/crossLink}} can be added to a Collection by instance, ID or type.
  • 12 |
  • A Collection supports iteration over its {{#crossLink "Component"}}Components{{/crossLink}}.
  • 13 |
  • A {{#crossLink "Model"}}Model{{/crossLink}} stores the {{#crossLink "Component"}}Components{{/crossLink}} it has loaded in a Collection.
  • 14 |
  • A {{#crossLink "CollectionBoundary"}}CollectionBoundary{{/crossLink}} provides a World-space {{#crossLink "Boundary3D"}}{{/crossLink}} that encloses a Collection.
  • 15 |
16 | 17 | 18 | 19 | ## Examples 20 | 21 |
    22 |
  • [Adding Entities to a Collection](../../examples/#collections_Collection_creating_add)
  • 23 |
  • [Adding components types to a Collection](../../examples/#collections_Collection_creating_type)
  • 24 |
  • [Iterating a Collection](../../examples/#boundaries_Collection_iterating)
  • 25 |
  • [Visualizing World-space boundary of a Collection](../../examples/#boundaries_CollectionBoundary)
  • 26 |
  • [Visualizing World-space boundaries of a hierarchy of Collections](../../examples/#boundaries_CollectionBoundary_hierarchy)
  • 27 |
28 | 29 | ## Creating Collections 30 | 31 | Our first Collection contains a {{#crossLink "PhongMaterial"}}{{/crossLink}}, added by ID, plus a {{#crossLink "BoxGeometry"}}{{/crossLink}} and 32 | an {{#crossLink "Entity"}}{{/crossLink}}, both added by instance. 33 | 34 | ````javascript 35 | var material = new xeogl.PhongMaterial({ 36 | id: "myMaterial", 37 | diffuse: [0.5, 0.5, 0.0] 38 | }); 39 | 40 | var geometry = new xeogl.BoxGeometry(); 41 | 42 | var entity = new xeogl.Entity({ 43 | id: "myEntity", 44 | material: material, 45 | geometry: geometry 46 | }); 47 | 48 | var collection1 = new xeogl.Collection({ // Initialize with the three components 49 | components: [ 50 | "myMaterial", 51 | geometry, 52 | myEntity 53 | ] 54 | }); 55 | ```` 56 | Our second Collection includes the {{#crossLink "BoxGeometry"}}{{/crossLink}}, added by instance, 57 | and the {{#crossLink "Entity"}}{{/crossLink}}, added by type. If there were more than 58 | one {{#crossLink "Entity"}}{{/crossLink}} in the scene, then that type would ensure 59 | that all the {{#crossLink "Entity"}}Entities{{/crossLink}} were in the Collection. 60 | 61 | ````javascript 62 | var collection2 = new xeogl.Collection(); 63 | 64 | collection2.add([ // Add two components 65 | geometry, 66 | "xeogl.Entity", 67 | ]); 68 | ```` 69 | 70 | ## Accessing Components 71 | 72 | Iterate over the components in a Collection using the convenience iterator: 73 | 74 | ````javascript 75 | collection1.iterate(function(component) { 76 | if (component.isType("xeogl.Entity")) { 77 | this.log("Found the Entity: " + component.id); 78 | } 79 | //.. 80 | }); 81 | ```` 82 | 83 | A Collection also registers its components by type: 84 | 85 | ````javascript 86 | var entities = collection1.types["xeogl.Entity"]; 87 | var theEntity = entities["myEntity"]; 88 | ```` 89 | 90 | ## Removing Components 91 | 92 | We can remove components from a Collection by instance, ID or type: 93 | 94 | ````javascript 95 | collection1.remove("myMaterial"); // Remove one component by ID 96 | collection1.remove([geometry, myEntity]); // Remove two components by instance 97 | collection2.remove("xeogl.Geometry"); // Remove all Geometries 98 | ```` 99 | 100 | ## Getting the boundary of a Collection 101 | 102 | A {{#crossLink "CollectionBoundary"}}{{/crossLink}} provides a {{#crossLink "Boundary3D"}}{{/crossLink}} that 103 | dynamically fits to the collective World-space boundary of all the Components in a Collection. 104 | 105 | ````javascript 106 | var collectionBoundary = new xeogl.CollectionBoundary({ 107 | collection: collection1 108 | }); 109 | 110 | var worldBoundary = collectionBoundary.worldBoundary; 111 | ```` 112 | The {{#crossLink "Boundary3D"}}{{/crossLink}} 113 | will automatically update whenever we add, remove or update any Components that have World-space boundaries. We can subscribe 114 | to updates on it like so: 115 | 116 | ````javascript 117 | worldBoundary.on("updated", function() { 118 | obb = worldBoundary.obb; 119 | aabb = worldBoundary.aabb; 120 | center = worldBoundary.center; 121 | //... 122 | }); 123 | ```` 124 | 125 | Now, if we now re-insert our {{#crossLink "Entity"}}{{/crossLink}} into to our Collection, 126 | the {{#crossLink "Boundary3D"}}{{/crossLink}} will fire our update handler. 127 | 128 | ````javascript 129 | collection1.add(myEntity); 130 | ```` 131 | 132 | 133 | @class Collection 134 | @module xeogl 135 | @submodule collections 136 | @constructor 137 | @param [scene] {Scene} Parent {{#crossLink "Scene"}}{{/crossLink}}. 138 | @param [cfg] {*} Configs 139 | @param [cfg.id] {String} Optional ID, unique among all components in the parent scene, generated automatically when omitted. 140 | @param [cfg.meta] {String:Component} Optional map of user-defined metadata to attach to this Collection. 141 | @param [cfg.components] {{Array of String|Component}} Array of {{#crossLink "Component"}}{{/crossLink}} IDs or instances. 142 | @extends Component 143 | */ 144 | define(["../../../lib/xeogl"], function () { 145 | 146 | "use strict"; 147 | 148 | xeogl.Collection = xeogl.Component.extend({ 149 | 150 | /** 151 | JavaScript class name for this Component. 152 | 153 | @property type 154 | @type String 155 | @final 156 | */ 157 | type: "xeogl.Collection", 158 | 159 | _init: function (cfg) { 160 | 161 | /** 162 | * The {{#crossLink "Components"}}{{/crossLink}} within this Collection, mapped to their IDs. 163 | * 164 | * Fires an {{#crossLink "Collection/updated:event"}}{{/crossLink}} event on change. 165 | * 166 | * @property components 167 | * @type {{String:Component}} 168 | */ 169 | this.components = {}; 170 | 171 | /** 172 | * The number of {{#crossLink "Components"}}{{/crossLink}} within this Collection. 173 | * 174 | * @property numComponents 175 | * @type Number 176 | */ 177 | this.numComponents = 0; 178 | 179 | /** 180 | * A map of maps; for each {{#crossLink "Component"}}{{/crossLink}} type in this Collection, 181 | * a map to IDs to {{#crossLink "Component"}}{{/crossLink}} instances, eg. 182 | * 183 | * ```` 184 | * "xeogl.Geometry": { 185 | * "alpha": , 186 | * "beta": 187 | * }, 188 | * "xeogl.Rotate": { 189 | * "charlie": , 190 | * "delta": , 191 | * "echo": , 192 | * }, 193 | * //... 194 | * ```` 195 | * 196 | * @property types 197 | * @type {String:{String:xeogl.Component}} 198 | */ 199 | this.types = {}; 200 | 201 | // Subscriptions to "destroyed" events from components 202 | this._destroyedSubs = {}; 203 | 204 | if (cfg.components) { 205 | this.add(cfg.components); 206 | } 207 | }, 208 | 209 | /** 210 | * Adds one or more {{#crossLink "Component"}}Components{{/crossLink}}s to this Collection. 211 | * 212 | * The {{#crossLink "Component"}}Component(s){{/crossLink}} may be specified by instance, ID or type. 213 | * 214 | * See class comment for usage examples. 215 | * 216 | * The {{#crossLink "Component"}}Components{{/crossLink}} must be in the same {{#crossLink "Scene"}}{{/crossLink}} as this Collection. 217 | * 218 | * Fires an {{#crossLink "Collection/added:event"}}{{/crossLink}} event. 219 | * 220 | * @method add 221 | * @param {Array of Component} components Array of {{#crossLink "Component"}}Components{{/crossLink}} instances. 222 | */ 223 | add: function (components) { 224 | 225 | components = xeogl._isArray(components) ? components : [components]; 226 | 227 | for (var i = 0, len = components.length; i < len; i++) { 228 | this._add(components[i]); 229 | } 230 | }, 231 | 232 | _add: function (c) { 233 | 234 | var componentId; 235 | var component; 236 | var type; 237 | var types; 238 | 239 | if (c.type) { 240 | 241 | // Component instance 242 | 243 | component = c; 244 | 245 | } else if (xeogl._isNumeric(c) || xeogl._isString(c)) { 246 | 247 | if (this.scene.types[c]) { 248 | 249 | // Component type 250 | 251 | type = c; 252 | 253 | types = this.scene.types[type]; 254 | 255 | if (!types) { 256 | this.warn("Component type not found: '" + type + "'"); 257 | return; 258 | } 259 | 260 | for (componentId in types) { 261 | if (types.hasOwnProperty(componentId)) { 262 | this._add(types[componentId]); 263 | } 264 | } 265 | 266 | return; 267 | 268 | } else { 269 | 270 | // Component ID 271 | 272 | component = this.scene.components[c]; 273 | 274 | if (!component) { 275 | this.warn("Component not found: " + xeogl._inQuotes(c)); 276 | return; 277 | } 278 | } 279 | 280 | } else { 281 | 282 | return; 283 | } 284 | 285 | if (component.scene !== this.scene) { 286 | 287 | // Component in wrong Scene 288 | 289 | this.warn("Attempted to add component from different xeogl.Scene: " + xeogl._inQuotes(component.id)); 290 | return; 291 | } 292 | 293 | // Add component to this map 294 | 295 | if (this.components[component.id]) { 296 | 297 | // Component already in this Collection 298 | return; 299 | } 300 | 301 | this.components[component.id] = component; 302 | 303 | // Register component for its type 304 | 305 | types = this.types[component.type]; 306 | 307 | if (!types) { 308 | types = this.types[component.type] = {}; 309 | } 310 | 311 | types[component.id] = component; 312 | 313 | this.numComponents++; 314 | 315 | // Remove component when it's destroyed 316 | 317 | var self = this; 318 | 319 | this._destroyedSubs[component.id] = component.on("destroyed", 320 | function () { 321 | self._remove(component); 322 | }); 323 | 324 | /** 325 | * Fired whenever an individual {{#crossLink "Component"}}{{/crossLink}} is added to this {{#crossLink "Collection"}}{{/crossLink}}. 326 | * @event added 327 | * @param value {Component} The {{#crossLink "Component"}}{{/crossLink}} that was added. 328 | */ 329 | this.fire("added", component); 330 | 331 | if (!this._dirty) { 332 | this._scheduleUpdate(); 333 | } 334 | }, 335 | 336 | _scheduleUpdate: function () { 337 | if (!this._dirty) { 338 | this._dirty = true; 339 | xeogl.scheduleTask(this._notifyUpdated, this); 340 | } 341 | }, 342 | 343 | _notifyUpdated: function () { 344 | 345 | /* Fired on the next {{#crossLink "Scene/tick.animate:event"}}{{/crossLink}} whenever 346 | * {{#crossLink "Component"}}Components{{/crossLink}} were added or removed since the 347 | * last {{#crossLink "Scene/tick.animate:event"}}{{/crossLink}} event, to provide a batched change event 348 | * for subscribers who don't want to react to every individual addition or removal on this Collection. 349 | * 350 | * @event updated 351 | */ 352 | this.fire("updated"); 353 | this._dirty = false; 354 | }, 355 | 356 | /** 357 | * Removes all {{#crossLink "Component"}}Components{{/crossLink}} from this Collection. 358 | * 359 | * Fires an {{#crossLink "Collection/updated:event"}}{{/crossLink}} event. 360 | * 361 | * @method clear 362 | */ 363 | clear: function () { 364 | 365 | this.iterate(function (component) { 366 | this._remove(component); 367 | }); 368 | }, 369 | 370 | /** 371 | * Destroys all {{#crossLink "Component"}}Components{{/crossLink}} in this Collection. 372 | * 373 | * @method destroyAll 374 | */ 375 | destroyAll: function () { 376 | 377 | this.iterate(function (component) { 378 | component.destroy(); 379 | }); 380 | }, 381 | 382 | /** 383 | * Removes one or more {{#crossLink "Component"}}Components{{/crossLink}} from this Collection. 384 | * 385 | * The {{#crossLink "Component"}}Component(s){{/crossLink}} may be specified by instance, ID or type. 386 | * 387 | * See class comment for usage examples. 388 | * 389 | * Fires a {{#crossLink "Collection/removed:event"}}{{/crossLink}} event. 390 | * 391 | * @method remove 392 | * @param {Array of Components} components Array of {{#crossLink "Component"}}Components{{/crossLink}} instances. 393 | */ 394 | remove: function (components) { 395 | 396 | components = xeogl._isArray(components) ? components : [components]; 397 | 398 | for (var i = 0, len = components.length; i < len; i++) { 399 | this._remove(components[i]); 400 | } 401 | }, 402 | 403 | _remove: function (component) { 404 | 405 | var componentId = component.id; 406 | 407 | if (component.scene !== this.scene) { 408 | this.warn("Attempted to remove component that's not in same xeogl.Scene: '" + componentId + "'"); 409 | return; 410 | } 411 | 412 | delete this.components[componentId]; 413 | 414 | // Unsubscribe from component destruction 415 | 416 | component.off(this._destroyedSubs[componentId]); 417 | 418 | delete this._destroyedSubs[componentId]; 419 | 420 | // Unregister component for its type 421 | 422 | var types = this.types[component.type]; 423 | 424 | if (types) { 425 | delete types[component.id]; 426 | } 427 | 428 | this.numComponents--; 429 | 430 | /** 431 | * Fired whenever an individual {{#crossLink "Component"}}{{/crossLink}} is removed from this {{#crossLink "Collection"}}{{/crossLink}}. 432 | * @event removed 433 | * @param value {Component} The {{#crossLink "Component"}}{{/crossLink}} that was removed. 434 | */ 435 | this.fire("removed", component); 436 | 437 | if (!this._dirty) { 438 | this._scheduleUpdate(); 439 | } 440 | }, 441 | 442 | /** 443 | * Iterates with a callback over the {{#crossLink "Component"}}Components{{/crossLink}} in this Collection. 444 | * 445 | * @method iterate 446 | * @param {Function} callback Callback called for each {{#crossLink "Component"}}{{/crossLink}}. 447 | * @param {Object} [scope=this] Optional scope for the callback, defaults to this Collection. 448 | */ 449 | iterate: function (callback, scope) { 450 | scope = scope || this; 451 | var components = this.components; 452 | for (var componentId in components) { 453 | if (components.hasOwnProperty(componentId)) { 454 | callback.call(scope, components[componentId]); 455 | } 456 | } 457 | }, 458 | 459 | _getJSON: function () { 460 | 461 | var componentIds = []; 462 | 463 | for (var componentId in this.components) { 464 | if (this.components.hasOwnProperty(componentId)) { 465 | componentIds.push(this.components[componentId].id); // Don't convert numbers into strings 466 | } 467 | } 468 | 469 | return { 470 | components: componentIds 471 | }; 472 | }, 473 | 474 | _destroy: function () { 475 | 476 | this.clear(); 477 | } 478 | }); 479 | 480 | }); 481 | -------------------------------------------------------------------------------- /css/metadata.css: -------------------------------------------------------------------------------- 1 | .bimsurfer-metadata h3 { 2 | font-weight: bold; 3 | margin: 0; 4 | padding: 10px 0 0 0; 5 | width: 1000px; 6 | } 7 | 8 | .bimsurfer-metadata h3:before { 9 | content: "\25b8 "; 10 | } 11 | 12 | .bimsurfer-metadata th, 13 | .bimsurfer-metadata td { 14 | width: 50%; 15 | border-bottom: solid 1px #eee; 16 | text-align: left; 17 | } -------------------------------------------------------------------------------- /css/tree.css: -------------------------------------------------------------------------------- 1 | .bimsurfer-static-tree, .bimsurfer-static-tree * { 2 | user-select: none; 3 | -ms-user-select: none; 4 | -moz-user-select: none; 5 | -webkit-user-select: none; 6 | z-index: 1; 7 | } 8 | 9 | .bimsurfer-static-tree div.item { 10 | border-top: solid 1px #eee; 11 | line-height: 12pt; 12 | } 13 | 14 | .bimsurfer-static-tree > div > div.item { 15 | border: none; 16 | } 17 | 18 | .bimsurfer-static-tree div.bimsurfer-tree-label { 19 | margin-left: -100px; 20 | padding-left: 100px; 21 | width: 1000px; 22 | cursor: pointer; 23 | } 24 | 25 | .bimsurfer-static-tree .bimsurfer-tree-children-with-indent { 26 | padding-left: 10px; 27 | } 28 | 29 | .bimsurfer-static-tree div.selected { 30 | background: #6ae; 31 | color: white; 32 | } 33 | 34 | .bimsurfer-static-tree div.bimsurfer-tree-label:before { 35 | content: "\21b3 "; 36 | } 37 | 38 | .bimsurfer-static-tree div.bimsurfer-tree-eye:before { 39 | content: "\1f441"; 40 | } 41 | 42 | .bimsurfer-tree-column { 43 | display: inline-block; 44 | overflow: hidden; 45 | } 46 | 47 | div.bimsurfer-tree-eye-off { 48 | opacity: 0.5; 49 | } -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # BIMsurfer example scripts 2 | 3 | ## Compilation and minification 4 | 5 | BimSurfer relies on RequireJS for module loading. You can use r.js to build compiled and minified source to reduce the number of requests. The glTF example uses such a minified source. Note that the resulting file includes xeogl. 6 | 7 | ~~~ 8 | npm install -g requirejs uglify-es 9 | r.js -o build-gltf_app.js 10 | uglifyjs --compress --mangle -o gltf_app.min.js -- gltf_app.build.js 11 | ~~~ 12 | -------------------------------------------------------------------------------- /examples/build-gltf_app.js: -------------------------------------------------------------------------------- 1 | ({ 2 | baseUrl: ".", 3 | name: "gltf_app", 4 | out: "gltf_app.build.js", 5 | paths: { 6 | bimsurfer: "../bimsurfer/" 7 | }, 8 | optimize: "none" 9 | }) 10 | -------------------------------------------------------------------------------- /examples/css/apiref.css: -------------------------------------------------------------------------------- 1 | textarea, pre { 2 | padding: 20px; 3 | border: solid 1px #ddd; 4 | background: #f4f4f4; 5 | height: 12px; 6 | line-height: 12px; 7 | font-size: 12px; 8 | width: 1200px; 9 | display: block; 10 | } 11 | 12 | pre { 13 | background: white; 14 | overflow: hidden; 15 | white-space: pre-wrap; 16 | } 17 | 18 | button { 19 | margin: 10px 10px; 20 | width: 100px; 21 | } 22 | 23 | #apirefContainer { 24 | padding: 30px 10px; 25 | clear: both; 26 | } -------------------------------------------------------------------------------- /examples/css/demo.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin:0; 3 | padding:0; 4 | font-family: "Open Sans"; 5 | font-size: 10pt; 6 | } 7 | 8 | #topsection{ 9 | border-bottom: solid 1px #ddd; 10 | } 11 | 12 | h1, h2 { 13 | padding: 10px; 14 | margin: 0; 15 | } 16 | 17 | /* From: http://matthewjamestaylor.com/blog/holy-grail-no-quirks-mode.css */ 18 | #contentwrapper{ 19 | position:relative; 20 | clear:both; 21 | float:left; 22 | width:100%; 23 | overflow:hidden; 24 | border-bottom: solid 1px #ddd; 25 | } 26 | 27 | #colmid { 28 | float:left; 29 | width:200%; 30 | position:relative; 31 | left:200px; 32 | } 33 | 34 | #colright { 35 | float:left; 36 | width:100%; 37 | position:relative; 38 | left:50%; 39 | margin-left:-400px; 40 | } 41 | 42 | #col1wrap { 43 | float:right; 44 | width:50%; 45 | position:relative; 46 | right:100%; 47 | } 48 | 49 | #col1pad { 50 | margin:0 15px 0 415px; 51 | overflow:hidden; 52 | border-right: solid 1px #ddd; 53 | } 54 | 55 | #viewerContainer { 56 | width:100%; 57 | overflow:hidden; 58 | line-height: 150%; 59 | } 60 | 61 | #treeContainer { 62 | float:left; 63 | width:200px; 64 | position:relative; 65 | margin-left:-50%; 66 | left:215px; 67 | overflow-x:hidden; 68 | overflow-y:scroll; 69 | height: 600px; 70 | } 71 | 72 | #dataContainer { 73 | float:left; 74 | width:200px; 75 | position:relative; 76 | left:0px; 77 | overflow-x:hidden; 78 | overflow-y:scroll; 79 | height: 600px; 80 | } 81 | 82 | canvas { 83 | display: block; 84 | width: 100%; 85 | height: 600px; 86 | cursor: default; 87 | } 88 | 89 | #status { 90 | border-bottom: solid 1px #ddd; 91 | padding: 5px 15px; 92 | clear: both; 93 | } 94 | 95 | #typeSelector { 96 | border-top: solid 1px #ddd; 97 | padding: 2px 12px; 98 | } 99 | 100 | #typeSelector div { 101 | margin: 0 5px 0 0; 102 | padding: 3px; 103 | border: solid 1px #fff; 104 | border-radius: 3px; 105 | cursor: pointer; 106 | display: inline-block; 107 | height: 20px; 108 | line-height: 20px; 109 | } 110 | 111 | #typeSelector div.inactive:before { 112 | color: #ddd; 113 | } 114 | 115 | #typeSelector div:hover { 116 | border-color: #ddd; 117 | } 118 | 119 | #typeSelector div:before { 120 | margin-right: 5px; 121 | } 122 | -------------------------------------------------------------------------------- /examples/gltf.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 |

BIMsurfer demo

39 |
40 |
 
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 56 |
57 |
58 |
59 | 60 |
61 |
62 | 63 | -------------------------------------------------------------------------------- /examples/gltf_app.js: -------------------------------------------------------------------------------- 1 | function highlight(oid, selected) { 2 | // Clicking an explorer node fits the view to its object and selects 3 | if (selected.length) { 4 | bimSurfer.viewFit({ 5 | ids: selected, 6 | animate: true 7 | }); 8 | } 9 | bimSurfer.setSelection({ 10 | ids:selected, 11 | clear:true, 12 | selected:true 13 | }); 14 | } 15 | 16 | require([ 17 | "bimsurfer/src/MultiModal", 18 | "bimsurfer/lib/domReady!" 19 | ], 20 | function (Viewer) { 21 | var modelName = window.location.hash; 22 | if (modelName.length < 1) { 23 | modelName = "Duplex_A_20110907_optimized"; 24 | } else { 25 | modelName = modelName.substr(1); 26 | } 27 | modelName = "models/" + modelName; 28 | 29 | var v = window.viewer = new Viewer({ 30 | domNode: 'viewerContainer', 31 | modelPath: modelName, 32 | withTreeVisibilityToggle: true, 33 | withTreeViewIcons: true 34 | }); 35 | 36 | if (window.SPINNER_CLASS) { 37 | v.setSpinner({className: window.SPINNER_CLASS}); 38 | } else if (window.SPINNER_URL) { 39 | v.setSpinner({url: window.SPINNER_URL}); 40 | } 41 | v.load3d(); 42 | v.loadMetadata('dataContainer'); 43 | v.loadTreeView('treeContainer'); 44 | }); -------------------------------------------------------------------------------- /examples/js/app.js: -------------------------------------------------------------------------------- 1 | require(["bimsurfer/src/BimSurfer.js", "bimsurfer/src/StaticTreeRenderer.js", "bimsurfer/src/MetaDataRenderer.js", "bimsurfer/lib/domReady.js!"], function (BimSurfer, StaticTreeRenderer, MetaDataRenderer) { 2 | 3 | var bimSurfer = new BimSurfer({ 4 | domNode: "viewerContainer" 5 | }); 6 | 7 | // For console debugging 8 | window.bimSurfer = bimSurfer; 9 | 10 | bimSurfer.load({ 11 | bimserver: ADDRESS, 12 | username: USERNAME, 13 | password: PASSWORD, 14 | poid: 5439489, 15 | roid: 11468803, 16 | schema: "ifc2x3tc1" // < TODO: Deduce automatically 17 | }).then(function (model) { 18 | 19 | model.getTree().then(function (tree) { 20 | 21 | var domtree = new StaticTreeRenderer({ 22 | domNode: 'treeContainer' 23 | }); 24 | 25 | domtree.addModel({name: tree.name, id:model.model.roid, tree:tree}); 26 | domtree.build(); 27 | 28 | var metadata = new MetaDataRenderer({ 29 | domNode: 'dataContainer' 30 | }); 31 | 32 | metadata.addModel({name: tree.name, id:model.model.roid, model:model}); 33 | 34 | domtree.on("click", function (oid) { 35 | // Clicking an explorer node fits the view to its object 36 | bimSurfer.viewFit({ 37 | ids: [oid], 38 | animate: true 39 | }); 40 | }); 41 | 42 | bimSurfer.on("selection-changed", function(selected) { 43 | domtree.setSelected(selected, domtree.SELECT_EXCLUSIVE); 44 | metadata.setSelected(selected); 45 | }); 46 | }); 47 | 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /examples/js/utils.js: -------------------------------------------------------------------------------- 1 | // http://stackoverflow.com/questions/979975/how-to-get-the-value-from-the-get-parameters 2 | var QueryString = function () { 3 | // This function is anonymous, is executed immediately and 4 | // the return value is assigned to QueryString! 5 | var query_string = {}; 6 | var query = window.location.search.substring(1); 7 | var vars = query.split("&"); 8 | for (var i=0;i 2 | 3 | 4 | BIMsurfer V2 - Demo index 5 | 6 | 7 |

BIMservers

8 |
9 |
10 |
11 |
12 |

Other BIMserver

13 | Load from other server 14 | 21 |

glTF

22 | glTF 23 | 144 | 145 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 bimsurfer.org 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /plugin/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AECgeeks/BIMsurfer2/926d1fcc08a373c879a2416110bc3107be1d654c/plugin/icon.png -------------------------------------------------------------------------------- /plugin/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bimsurfer2 5 | BIMsurferV2 6 | BIMsurfer is a JavaScript library to visualize BIM in 3D 7 | 8 | -------------------------------------------------------------------------------- /plugin/version.properties: -------------------------------------------------------------------------------- 1 | build.date=${timestamp} -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | org.opensourcebim 4 | bimsurfer2 5 | 0.0.1-SNAPSHOT 6 | jar 7 | 8 | UTF-8 9 | ${maven.build.timestamp} 10 | yyyy-MM-dd'T'HH:mm:ssZ 11 | 12 | BIMsurfer2 13 | 14 | 15 | 16 | ../../output 17 | 18 | 19 | plugin 20 | plugin 21 | 22 | 23 | css 24 | css 25 | 26 | 27 | bimsurfer 28 | bimsurfer 29 | 30 | 31 | 32 | 33 | org.codehaus.mojo 34 | build-helper-maven-plugin 35 | 36 | 37 | attach-plugin 38 | package 39 | 40 | attach-artifact 41 | 42 | 43 | 44 | 45 | plugin/plugin.xml 46 | xml 47 | plugin 48 | 49 | 50 | plugin/icon.png 51 | png 52 | icon 53 | 54 | 55 | ${project.build.outputDirectory}/plugin/version.properties 56 | properties 57 | version 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | org.sonatype.plugins 66 | nexus-staging-maven-plugin 67 | 1.6.3 68 | true 69 | 70 | ossrh 71 | https://oss.sonatype.org/ 72 | true 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-release-plugin 78 | 2.5.3 79 | 80 | github.com 81 | false 82 | release 83 | deploy 84 | 85 | 86 | 87 | 88 | 89 | 90 | release 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-gpg-plugin 96 | 1.5 97 | 98 | 99 | sign-artifacts 100 | verify 101 | 102 | sign 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | ossrh 114 | https://oss.sonatype.org/content/repositories/snapshots 115 | 116 | 117 | ossrh 118 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 119 | 120 | 121 | 122 | 123 | org.opensourcebim 124 | bimserverapi 125 | 0.0.83 126 | 127 | 128 | 129 | https://github.com/opensourceBIM/BIMsurfer.git 130 | scm:git:https://github.com/opensourceBIM/BIMsurfer.git 131 | scm:git:https://github.com/opensourceBIM/BIMsurfer.git 132 | V2 133 | 134 | 135 | OpenSource BIM 136 | opensourcebim.org 137 | 138 | 139 | GitHub 140 | https://github.com/opensourceBIM/BIMsurfer/issues 141 | 142 | BIMsurfer 143 | https://github.com/opensourceBIM/BIMsurfer 144 | 145 | 146 | GNU Affero General Public License 147 | http://www.gnu.org/licenses/agpl-3.0.en.html 148 | repo 149 | 150 | 151 | 152 | 153 | ruben@logic-labs.nl 154 | Ruben de Laat 155 | 156 | 157 | --------------------------------------------------------------------------------