├── index.js ├── 3dio-logo.png ├── img └── title-pic3.gif ├── src ├── scene │ ├── structure │ │ ├── duplicate.js │ │ ├── validate │ │ │ ├── by-type │ │ │ │ ├── interior.js │ │ │ │ ├── group.js │ │ │ │ ├── level.js │ │ │ │ ├── tag.js │ │ │ │ ├── plan.js │ │ │ │ ├── floorplan.js │ │ │ │ ├── polybox.js │ │ │ │ ├── camera-bookmark.js │ │ │ │ ├── object.js │ │ │ │ ├── box.js │ │ │ │ ├── curtain.js │ │ │ │ ├── column.js │ │ │ │ ├── floor.js │ │ │ │ ├── wall.js │ │ │ │ ├── polyfloor.js │ │ │ │ ├── stairs.js │ │ │ │ ├── railing.js │ │ │ │ ├── closet.js │ │ │ │ └── window.js │ │ │ ├── get-param-value-type.js │ │ │ ├── generic.js │ │ │ └── get-defaults-by-type.js │ │ ├── parametric-objects │ │ │ ├── common │ │ │ │ ├── apply-default-materials.js │ │ │ │ ├── get-material.js │ │ │ │ └── get-materials.js │ │ │ └── floor.js │ │ ├── get.js │ │ ├── remove-unknown.js │ │ ├── normalize.js │ │ └── apply-defaults.js │ ├── get-viewer-url.js │ ├── get-aframe-elements.js │ └── export-svg.js ├── aframe │ ├── component │ │ ├── gblock │ │ │ ├── vertex-placeholder.glsl │ │ │ ├── fragment-placeholder.glsl │ │ │ └── vertex.glsl │ │ └── architecture-toolkit │ │ │ ├── common │ │ │ ├── remove-empty-meshes.js │ │ │ ├── update-schema.js │ │ │ ├── data-to-materials.js │ │ │ └── get-schema.js │ │ │ ├── floor.js │ │ │ ├── kitchen.js │ │ │ ├── railing.js │ │ │ ├── closet.js │ │ │ ├── stairs.js │ │ │ ├── column.js │ │ │ └── polyfloor.js │ ├── three │ │ └── data3d-view │ │ │ ├── io3d-material │ │ │ └── vertex.glsl │ │ │ ├── set-material │ │ │ └── fetch-texture.js │ │ │ └── io3d-material.js │ ├── inspector-plugins-launcher.js │ └── check-dependencies.js ├── light.js ├── utils │ ├── file │ │ ├── get-md5-hash.js │ │ ├── get-default-filename.js │ │ ├── read.js │ │ ├── get-mime-type-from-filename.js │ │ └── gzip.js │ ├── color │ │ ├── rgb-to-hex.js │ │ └── hex-to-rgb.js │ ├── data3d │ │ ├── get-inspector-url.js │ │ ├── load.js │ │ ├── store-in-cache.js │ │ ├── remove-from-cache.js │ │ ├── get-texture-urls.js │ │ ├── get-texture-keys.js │ │ ├── traverse.js │ │ ├── texture-attributes.js │ │ └── buffer │ │ │ ├── get-uvs.js │ │ │ └── get-wireframe.js │ ├── wait.js │ ├── auth │ │ ├── session-stream.js │ │ ├── common │ │ │ └── normalize-session.js │ │ ├── regenerate-secret-api-key.js │ │ ├── list-publishable-api-keys.js │ │ ├── revoke-publishable-api-key.js │ │ ├── generate-publishable-api-key.js │ │ ├── update-publishable-api-key-domains.js │ │ ├── get-secret-api-key.js │ │ ├── request-password-reset.js │ │ ├── log-out.js │ │ ├── resend-activation-email.js │ │ ├── activate-account.js │ │ ├── set-password.js │ │ ├── get-session.js │ │ ├── sign-up.js │ │ └── log-in.js │ ├── short-id.js │ ├── ui │ │ ├── create-log-out-ui.js │ │ ├── create-alert-ui.js │ │ ├── create-message-ui.js │ │ └── create-prompt-ui.js │ ├── io │ │ ├── form-data.js │ │ ├── fetch.js │ │ ├── common │ │ │ └── add-cache-bust-to-query.js │ │ ├── check-if-file-exists.js │ │ ├── fetch-image.js │ │ └── fetch-script.js │ ├── math │ │ └── compare-arrays.js │ ├── uuid.js │ ├── image │ │ ├── get-blob-from-canvas.js │ │ └── get-image-from-file.js │ ├── processing │ │ ├── get-convertible-texture-ids.js │ │ ├── when-done.js │ │ └── when-hi-res-textures-ready.js │ ├── path.js │ ├── poll.js │ ├── ui.js │ ├── auth.js │ └── array │ │ └── decode-to-string.js ├── modify.js ├── storage │ ├── import-aframe-element.js │ ├── get-no-cdn-url-from-id.js │ ├── import-three-object.js │ ├── get-id-from-url.js │ ├── get.js │ ├── get-url-from-id.js │ └── model-exporter.js ├── furniture │ ├── get-info.js │ ├── get-data3d-storage-id.js │ ├── get.js │ └── common │ │ └── normalize-furniture-info.js ├── furniture.js ├── floor-plan.js ├── staging.js ├── floor-plan │ ├── get-conversion-status.js │ ├── convert-to-basic-3d-model.js │ └── recognize.js ├── core │ └── bootstrap.js ├── modify │ └── modify-model.js ├── io3d.js ├── storage.js ├── light │ └── bake.js └── scene.js ├── examples-browser ├── storage-app │ ├── app.js │ ├── index.html │ └── style.css ├── scene │ ├── style.css │ └── export-svg.html ├── api-playground │ ├── img │ │ └── run-icon.svg │ ├── js │ │ └── examples │ │ │ ├── upload-single-file.js │ │ │ ├── login-another-user.js │ │ │ ├── upload-file-with-key.js │ │ │ └── upload-multiple-files.js │ └── index.html ├── aframe-parametric │ └── style.css ├── change-material │ ├── style.css │ └── index.html ├── staging │ ├── stage-scene-structure │ │ └── style.css │ ├── style.css │ └── stage-room-ar │ │ └── README.md ├── no-view │ ├── main.js │ ├── index.html │ └── js │ │ └── run-code.js ├── millennium-falcon │ └── index.html └── offline-data │ └── sw.js ├── test ├── scene │ ├── get-viewer-url.js │ └── structure │ │ ├── apply-defaults.js │ │ ├── get.js │ │ ├── parametric-objects │ │ ├── railing.js │ │ ├── closet.js │ │ ├── window.js │ │ ├── stairs.js │ │ ├── floor.js │ │ ├── polyfloor.js │ │ ├── column.js │ │ ├── wall.js │ │ ├── door.js │ │ └── kitchen.js │ │ ├── to-aframe-elements.js │ │ ├── validate.js │ │ └── from-aframe-elements.js ├── utils │ └── uuid.js ├── furniture │ └── search.js └── staging │ └── replace-furniture.js ├── .travis.yml ├── gulpfile.js ├── .babelrc ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── tasks ├── lite-server.config.js ├── jshint.js ├── dev-node.js ├── build.js ├── preamble.js ├── jshint-configs.js └── dev-browser.js ├── examples-node └── main.js ├── LICENSE.md └── README.md /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./build/3dio') -------------------------------------------------------------------------------- /3dio-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archilogic-com/3dio-js/HEAD/3dio-logo.png -------------------------------------------------------------------------------- /img/title-pic3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/archilogic-com/3dio-js/HEAD/img/title-pic3.gif -------------------------------------------------------------------------------- /src/scene/structure/duplicate.js: -------------------------------------------------------------------------------- 1 | /* scene structure duplication will happen here */ 2 | /* all uuids need to be regenerated */ -------------------------------------------------------------------------------- /src/scene/get-viewer-url.js: -------------------------------------------------------------------------------- 1 | export default function getViewerUrl (args) { 2 | return 'https://spaces.archilogic.com/3d/!'+args.sceneId 3 | } -------------------------------------------------------------------------------- /src/aframe/component/gblock/vertex-placeholder.glsl: -------------------------------------------------------------------------------- 1 | varying vec3 v_normal; 2 | varying vec3 v_position; 3 | varying vec3 v_binormal; 4 | varying vec3 v_tangent; 5 | -------------------------------------------------------------------------------- /src/light.js: -------------------------------------------------------------------------------- 1 | import bake from './light/bake.js' 2 | 3 | var light = { 4 | bake: bake.bakePreview, 5 | bakeLoRes: bake.bakePreview, 6 | bakeHiRes: bake.bakeRegular 7 | } 8 | 9 | export default light -------------------------------------------------------------------------------- /src/utils/file/get-md5-hash.js: -------------------------------------------------------------------------------- 1 | import readFile from './read.js' 2 | import md5 from '../math/md5.js' 3 | 4 | export default function getMd5Hash (file) { 5 | return readFile(file, 'binaryString').then(md5) 6 | } -------------------------------------------------------------------------------- /src/utils/color/rgb-to-hex.js: -------------------------------------------------------------------------------- 1 | export default function rgbToHex(array) { 2 | return "#" + ((1 << 24) + (Math.round(array[0]*255) << 16) + (Math.round(array[1]*255) << 8) + (Math.round(array[2]*255))).toString(16).slice(1) 3 | } -------------------------------------------------------------------------------- /src/utils/data3d/get-inspector-url.js: -------------------------------------------------------------------------------- 1 | export default function getData3dInspectorUrl (storageId) { 2 | // TODO: replace spaces SDK with mini app to inspect data3d files 3 | return 'https://spaces.archilogic.com/3d/?mode=sdk&file='+storageId 4 | } -------------------------------------------------------------------------------- /src/modify.js: -------------------------------------------------------------------------------- 1 | import modifyModel from './modify/modify-model.js' 2 | 3 | var modify = { 4 | collisionObject: modifyModel.collisionObject, 5 | consolidateFaceSides: modifyModel.consolidateFaceSides, 6 | origami: modifyModel.origami 7 | } 8 | 9 | export default modify -------------------------------------------------------------------------------- /src/storage/import-aframe-element.js: -------------------------------------------------------------------------------- 1 | import importThreeObject from './import-three-object.js' 2 | 3 | export default function importAframeElement(selector) { 4 | 5 | var threeObject = document.querySelector(selector).object3D 6 | 7 | return importThreeObject(threeObject) 8 | 9 | } -------------------------------------------------------------------------------- /src/storage/get-no-cdn-url-from-id.js: -------------------------------------------------------------------------------- 1 | import configs from '../core/configs.js' 2 | import getUrlFromStorageId from './get-url-from-id.js' 3 | 4 | // main 5 | export default function getNoCdnUrlFromStorageId (storageId) { 6 | 7 | return getUrlFromStorageId(storageId, { cdn: false }) 8 | 9 | } -------------------------------------------------------------------------------- /examples-browser/storage-app/app.js: -------------------------------------------------------------------------------- 1 | var el = document.getElementById('file-box') 2 | 3 | io3d.utils.ui.fileDrop({ 4 | elementId: 'file-drop-box', 5 | upload: true, 6 | dragOverCssClass: 'file-drop-box-dragover', 7 | onInput: function(keys){ 8 | console.log(keys) 9 | } 10 | }) -------------------------------------------------------------------------------- /src/utils/wait.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird' 2 | 3 | // function 4 | 5 | export default function wait(duration, passThroughValue) { 6 | return new Promise(function (resolve, reject) { 7 | setTimeout(function(){ 8 | resolve(passThroughValue) 9 | }, duration) 10 | }) 11 | } -------------------------------------------------------------------------------- /test/scene/get-viewer-url.js: -------------------------------------------------------------------------------- 1 | import getViewerUrl from '../../src/scene/get-viewer-url' 2 | 3 | test('Scene: get url form id', () => { 4 | const id = '7078987a-d67c-4d01-bd7d-a3c4bb51244b' 5 | const url = getViewerUrl({sceneId: id}) 6 | expect(url).toBe('https://spaces.archilogic.com/3d/!' + id) 7 | }) -------------------------------------------------------------------------------- /src/scene/get-aframe-elements.js: -------------------------------------------------------------------------------- 1 | import getStructure from './structure/get.js' 2 | import getAframeElementsFromSceneStructure from './structure/to-aframe-elements.js' 3 | 4 | export default function getAframeElements(id) { 5 | return getStructure(id) 6 | .then(getAframeElementsFromSceneStructure) 7 | } -------------------------------------------------------------------------------- /src/utils/file/get-default-filename.js: -------------------------------------------------------------------------------- 1 | import shortId from '../short-id.js' 2 | 3 | export default function getDefaultFilename () { 4 | var d = new Date() 5 | return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate() 6 | + '_' + d.getHours() + '-' + d.getMinutes() + '-' + d.getSeconds() + '_' + shortId() 7 | } -------------------------------------------------------------------------------- /src/utils/auth/session-stream.js: -------------------------------------------------------------------------------- 1 | import normalizeSession from './common/normalize-session.js' 2 | import Rx from 'rxjs/BehaviorSubject.js' 3 | 4 | /** 5 | * Creates a session stream 6 | * @function io3d.auth.session$ 7 | */ 8 | 9 | var session$ = new Rx.BehaviorSubject(normalizeSession({})) 10 | 11 | export default session$ -------------------------------------------------------------------------------- /src/utils/short-id.js: -------------------------------------------------------------------------------- 1 | export default function getShortId (length) { 2 | length = length || 6 3 | var shortId = '' 4 | var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' 5 | for (var i = 0; i < length; i++) shortId += possible.charAt(Math.floor(Math.random() * possible.length)) 6 | return shortId 7 | } -------------------------------------------------------------------------------- /src/furniture/get-info.js: -------------------------------------------------------------------------------- 1 | import callService from '../utils/services/call.js' 2 | import normalizeFurnitureInfo from './common/normalize-furniture-info.js' 3 | 4 | export default function getFurnitureInfo (id) { 5 | return callService('Product.read', { resourceId:id }).then(function(rawInfo){ 6 | return normalizeFurnitureInfo(rawInfo) 7 | }) 8 | } -------------------------------------------------------------------------------- /examples-browser/scene/style.css: -------------------------------------------------------------------------------- 1 | .custom-controls { 2 | position: absolute; 3 | z-index:1000; top: 20px; left: 20px; 4 | } 5 | 6 | .custom-controls > input { 7 | height: 30px; 8 | font-size: 16px; 9 | padding: 0px 12px; 10 | outline: none; 11 | border: 0; 12 | font-weight: bold; 13 | border-bottom: 2px solid #333; 14 | margin: 0 10px 20px 0; 15 | } -------------------------------------------------------------------------------- /examples-browser/api-playground/img/run-icon.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/aframe/component/gblock/fragment-placeholder.glsl: -------------------------------------------------------------------------------- 1 | uniform vec3 u_color; 2 | uniform float u_metallic; 3 | uniform float u_roughness; 4 | uniform vec3 u_light0Pos; 5 | uniform vec3 u_light0Color; 6 | uniform vec3 u_light1Pos; 7 | uniform vec3 u_light1Color; 8 | uniform mat4 u_modelMatrix; 9 | uniform sampler2D u_reflectionCube; 10 | uniform sampler2D u_reflectionCubeBlur; -------------------------------------------------------------------------------- /test/scene/structure/apply-defaults.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../src/scene/structure/apply-defaults.js'; 2 | 3 | test('applyDefaults', () => { 4 | const el3d = { 5 | type: 'wall', 6 | l: 1 7 | } 8 | const _el3d = applyDefaults(el3d) 9 | expect(_el3d.h).toBe(2.4) 10 | expect(_el3d.x).toBe(0) 11 | expect(_el3d.children).toEqual([]) 12 | }); 13 | -------------------------------------------------------------------------------- /src/utils/ui/create-log-out-ui.js: -------------------------------------------------------------------------------- 1 | import logOut from '../auth/log-out.js' 2 | import createAlertUi from './create-alert-ui.js' 3 | 4 | export default function createLogOutUi () { 5 | return logOut().then(function onSuccess(){ 6 | return createAlertUi('Log out successful') 7 | }, function onReject(e) { 8 | return createAlertUi('Log out error:

'+e) 9 | }) 10 | } -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/common/remove-empty-meshes.js: -------------------------------------------------------------------------------- 1 | // clean up empty meshes to prevent errors on material update 2 | export default function (meshes) { 3 | Object.keys(meshes).forEach(key => { 4 | if (!meshes[key].positions || !meshes[key].positions.length) { 5 | //console.warn('no vertices for mesh', key) 6 | delete meshes[key] 7 | } 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/furniture.js: -------------------------------------------------------------------------------- 1 | import search from './furniture/search.js' 2 | import get from './furniture/get.js' 3 | import getInfo from './furniture/get-info.js' 4 | import getData3dStorageId from './furniture/get-data3d-storage-id.js' 5 | 6 | var furniture = { 7 | search: search, 8 | get: get, 9 | getInfo: getInfo, 10 | getData3dStorageId: getData3dStorageId 11 | } 12 | 13 | export default furniture -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/common/update-schema.js: -------------------------------------------------------------------------------- 1 | export default function updateSchema(newData) { 2 | var materialProperties = {} 3 | Object.keys(newData) 4 | .filter(function (propKey) { return propKey.substr(0, 9) === 'material_' }) 5 | .forEach(function (propKey) { 6 | materialProperties[propKey] = { type: 'string' } 7 | }) 8 | this.extendSchema(materialProperties) 9 | } -------------------------------------------------------------------------------- /src/floor-plan.js: -------------------------------------------------------------------------------- 1 | import convertToBasic3dModel from './floor-plan/convert-to-basic-3d-model.js' 2 | import getConversionStatus from './floor-plan/get-conversion-status.js' 3 | import recognize from './floor-plan/recognize.js' 4 | 5 | var floorPlan = { 6 | convertToBasic3dModel: convertToBasic3dModel, 7 | getConversionStatus: getConversionStatus, 8 | recognize: recognize 9 | } 10 | 11 | export default floorPlan -------------------------------------------------------------------------------- /src/staging.js: -------------------------------------------------------------------------------- 1 | import getFurnishings from './staging/get-furnishings.js' 2 | import replaceFurniture from './staging/replace-furniture.js' 3 | import getFurnitureAlternatives from './staging/get-furniture-alternatives.js' 4 | 5 | var staging = { 6 | getFurnishings: getFurnishings, 7 | getFurnitureAlternatives: getFurnitureAlternatives, 8 | replaceFurniture: replaceFurniture 9 | } 10 | 11 | export default staging 12 | -------------------------------------------------------------------------------- /examples-browser/api-playground/js/examples/upload-single-file.js: -------------------------------------------------------------------------------- 1 | var user = {name: 'gertrud', password: 'afgshgrtecsru6ez5dt'} 2 | var file = new Blob(['Hello World'], { type: 'text/plain' }) 3 | file.name = 'booo.txt' 4 | 5 | io3d.auth.logIn(user).then(function (x) { 6 | 7 | return io3d.storage.put(file) 8 | 9 | }).then(function(key){ 10 | 11 | console.log('file uploaded to:', 'https://storage.3d.io'+key) 12 | 13 | }) 14 | -------------------------------------------------------------------------------- /src/utils/io/form-data.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | 3 | var FormData_ 4 | if (runtime.isNode) { 5 | FormData_ = require('form-data') 6 | } else if (typeof FormData !== 'undefined') { 7 | FormData_ = FormData 8 | } else { 9 | console.warn('Missing FormData API.') 10 | FormData_ = function FormDataError() { 11 | throw new Error('Missing FormData API.') 12 | } 13 | } 14 | 15 | export default FormData_ 16 | -------------------------------------------------------------------------------- /src/utils/math/compare-arrays.js: -------------------------------------------------------------------------------- 1 | export default function compareArrays(a, b, precision) { 2 | 3 | if (a === b) { 4 | return true 5 | } else if (a.length !== b.length) { 6 | return false 7 | } else { 8 | precision = precision === undefined ? 1 : precision 9 | var step = ~~(a.length / (a.length * precision)) 10 | for (var i = 0, l = a.length; i { 4 | expect(uuid.validate('7078987a-d67c-4d01-bd7d-a3c4bb51244b')).toBe(true) 5 | }); 6 | 7 | test('UUID: rejects invalid UUIDs', () => { 8 | expect(uuid.validate('xxx8987a-d67c-4d01-bd7d-a3c4bb51244b')).toBe(false) 9 | }) 10 | 11 | test('UUID: generated UUIDs should validate', () => { 12 | const result = uuid.validate(uuid.generate()) 13 | expect(result).toBe(true) 14 | }) -------------------------------------------------------------------------------- /src/utils/io/fetch.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | 3 | export default (function(){ 4 | 5 | if (runtime.isNode) { 6 | // overwrite whatwg-fetch polyfill 7 | global.fetch = require('node-fetch') 8 | return global.fetch 9 | } else if (typeof fetch !== 'undefined') { 10 | return fetch 11 | } else { 12 | console.warn('Missing global fetch API.') 13 | return function() { 14 | throw new Error('Missing global fetch API.') 15 | } 16 | } 17 | 18 | })() -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // gulp 4 - https://github.com/gulpjs/gulp/tree/4.0 2 | const gulp = require('gulp') 3 | 4 | // register tasks for command line 5 | gulp.task('dev-browser', require('./tasks/dev-browser')) 6 | gulp.task('dev-node', require('./tasks/dev-node')) 7 | gulp.task('dev-build', require('./tasks/build')) 8 | 9 | gulp.task('jshint', require('./tasks/jshint.js')) 10 | 11 | gulp.task('release', require('./tasks/release').release) 12 | gulp.task('release-build', require('./tasks/release').releaseBuild) 13 | -------------------------------------------------------------------------------- /src/scene/export-svg.js: -------------------------------------------------------------------------------- 1 | import callService from '../utils/services/call.js' 2 | 3 | export default function exportSvg (args) { 4 | if (!args.sceneStructure || typeof args.sceneStructure !== 'object') { 5 | return Promise.reject('Svg export failed: invalid input') 6 | } 7 | return callService('Scene.exportSvg', {arguments: args}) 8 | .catch(function(error) { 9 | console.error(error) 10 | return Promise.reject('Svg export failed: check console for details') 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/level.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'node equivalent to a building storey', 3 | params: {}, 4 | childrenTypes: [ 5 | 'box', 6 | 'closet', 7 | 'column', 8 | 'curtain', 9 | 'floor', 10 | 'floorplan', 11 | 'group', 12 | 'interior', 13 | 'kitchen', 14 | 'object', 15 | 'polybox', 16 | 'polyfloor', 17 | 'railing', 18 | 'stairs', 19 | 'tag', 20 | 'wall' 21 | ], 22 | parentTypes: ['plan'] 23 | } 24 | -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/tag.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'all kinds of stairs types', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 0, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | title: { 11 | type: 'string', 12 | optional: false 13 | }, 14 | notes: { 15 | type: 'string', 16 | optional: true 17 | }, 18 | }, 19 | childrenTypes: [], 20 | parentTypes: ['level', 'interior'] 21 | } 22 | -------------------------------------------------------------------------------- /examples-browser/api-playground/js/examples/upload-multiple-files.js: -------------------------------------------------------------------------------- 1 | var user = {name: 'gertrud', password: 'afgshgrtecsru6ez5dt'} 2 | var files = [ 3 | new Blob(['Hello World'], { type: 'text/plain' }), 4 | new Blob(['Hello World2'], { type: 'text/plain' }), 5 | new Blob(['Hello World3'], { type: 'text/plain' }) 6 | ] 7 | 8 | io3d.auth.logIn(user).then(function (x) { 9 | 10 | return io3d.storage.put(files) 11 | 12 | }).then(function(keys){ 13 | 14 | console.log('file keys: ', keys) 15 | 16 | }) 17 | -------------------------------------------------------------------------------- /test/scene/structure/get.js: -------------------------------------------------------------------------------- 1 | import getStructure from '../../../src/scene/structure/get' 2 | 3 | // mock runtime module to prevent from tests blowing up 4 | jest.mock('../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true, require: require})) 5 | 6 | test('Scene: get sceneStructure form id', async () => { 7 | const id = '5a281187-475a-4613-8fa5-a2e92af9914d' 8 | const result = await getStructure(id) 9 | expect(result.type).toBe('plan') 10 | expect(result.children[0].type).toBe('level') 11 | }) -------------------------------------------------------------------------------- /src/storage/import-three-object.js: -------------------------------------------------------------------------------- 1 | import runtime from '../core/runtime.js' 2 | import storagePut from './put.js' 3 | import getData3dFromThreeJs from '../utils/data3d/from-three.js' 4 | import encodeData3dToBinary from '../utils/data3d/encode-binary.js' 5 | import whenHiResTexturesReady from '../utils/processing/when-hi-res-textures-ready.js' 6 | 7 | // main 8 | 9 | export default function importThreeObject(o) { 10 | 11 | return getData3dFromThreeJs(o) 12 | .then(encodeData3dToBinary) 13 | .then(storagePut) 14 | 15 | } -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/plan.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'highest node in hierarchy, contains levels', 3 | params: { 4 | modelDisplayName: { 5 | type: 'string', 6 | optional: true, 7 | skipInAframe: true, 8 | description: 'name of the scene' 9 | }, 10 | v: { 11 | type: 'number', 12 | defaultValue: 1, 13 | optional: true, 14 | skipInAframe: true, 15 | description: 'version' 16 | } 17 | }, 18 | childrenTypes: ['level', 'camera-bookmark'], 19 | parentTypes: [] 20 | } -------------------------------------------------------------------------------- /src/utils/io/common/add-cache-bust-to-query.js: -------------------------------------------------------------------------------- 1 | export default function addCacheBustToQuery (url) { 2 | var cacheBust = '___cacheBust='+Date.now() 3 | if (url.indexOf('?') > -1) { 4 | // url has query: append cache bust 5 | url = url.replace('?','?'+cacheBust+'&') 6 | } else if (url.indexOf('#') > -1) { 7 | // url has no query but hash: prepend cache bust to hash tag 8 | url = url.replace('#', '?'+cacheBust+'#') 9 | } else { 10 | // no query and no hash tag: add cache bust 11 | url = url + '?' + cacheBust 12 | } 13 | return url 14 | } -------------------------------------------------------------------------------- /src/floor-plan/get-conversion-status.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird' 2 | import callServices from '../utils/services/call.js' 3 | 4 | export default function getConversionStatus (args) { 5 | 6 | // API 7 | var conversionId = args.conversionId 8 | 9 | // send request to server side endpoint 10 | return callServices('FloorPlan.getConversionStatus', { 11 | conversionId: conversionId 12 | }).catch(function onError (error) { 13 | // conversion request error 14 | // TODO: provide info in debug mode 15 | return Promise.reject(error) 16 | }) 17 | 18 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | // unit tests 4 | "test": { 5 | "presets": [ 6 | "es2015" 7 | ] 8 | }, 9 | // local development 10 | "development": { 11 | "presets": [ 12 | [ "es2015", {"modules": false} ] 13 | ], 14 | "plugins": [ 15 | "external-helpers" 16 | ] 17 | }, 18 | // release deployment 19 | "production": { 20 | "presets": [ 21 | [ "es2015", {"modules": false} ] 22 | ], 23 | "plugins": [ 24 | "external-helpers" 25 | ] 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/utils/color/hex-to-rgb.js: -------------------------------------------------------------------------------- 1 | export default function hexToRgb(hex) { 2 | // TODO: check whether input is string (html style) or number (threejs style) 3 | var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex) 4 | return result ? [ 5 | round( parseInt(result[1],16)/255, 0.001 ), 6 | round( parseInt(result[2],16)/255, 0.001 ), 7 | round( parseInt(result[3],16)/255, 0.001 ) 8 | ] : null 9 | } 10 | 11 | // helpers 12 | 13 | function round(value, step) { 14 | step || (step = 1.0) 15 | var inv = 1.0 / step 16 | return Math.round(value * inv) / inv 17 | } -------------------------------------------------------------------------------- /src/scene/structure/parametric-objects/common/apply-default-materials.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cloneDeep from 'lodash/cloneDeep' 4 | 5 | // apply default material names to object in case material key is unset 6 | export default function(materials, defaults){ 7 | 8 | if (!defaults) { 9 | console.error('No default materials set') 10 | defaults={} 11 | } 12 | 13 | if (!materials) return Promise.resolve(cloneDeep(defaults)) 14 | 15 | Object.keys(defaults).forEach(function(d){ 16 | if (!materials[d]) materials[d]=defaults[d] 17 | }) 18 | 19 | return materials 20 | } 21 | -------------------------------------------------------------------------------- /src/scene/structure/validate/get-param-value-type.js: -------------------------------------------------------------------------------- 1 | export default function getParamValueType (value, target) { 2 | if (target === 'int' && isInt(value)) { 3 | return 'int' 4 | } 5 | if (Array.isArray(value)) { 6 | // TODO: add support for more sophisticated array types 7 | // array-with-objects, array-with-numbers, array-with-arrays-with-numbers 8 | return 'array' 9 | } else { 10 | return typeof value 11 | } 12 | } 13 | 14 | function isInt(value) { 15 | return !isNaN(value) && 16 | parseInt(Number(value)) == value && 17 | !isNaN(parseInt(value, 10)); 18 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please read the [contribution guidelines](CONTRIBUTING.md) before submitting a PR. 2 | 3 | **Scope & Features** 4 | - What is the pull request for, which features does it implement 5 | - Why should this feature be included 6 | 7 | **Design** 8 | - How was the code designed 9 | - Reference the Github Issue / Github Project associated 10 | 11 | **How to use** 12 | - How does the new feature work 13 | - How do the changes affect users 14 | 15 | **How to test** 16 | - How has this feature been tested? 17 | 18 | **Notes** 19 | - Additional information for the reviewer 20 | -------------------------------------------------------------------------------- /tasks/lite-server.config.js: -------------------------------------------------------------------------------- 1 | // https://github.com/johnpapa/lite-server 2 | // https://browsersync.io/docs/options/ 3 | module.exports = { 4 | port: 8080, 5 | startPath: '/examples-browser/', 6 | files: [{ 7 | match: ['examples-browser/**'], 8 | watchEvents: ['change'] 9 | }, { 10 | match: ['build/**'], 11 | watchEvents: ['add'] 12 | }], 13 | serveStatic: ['./'], // uses index.html in directories 14 | server: { 15 | directory: true, 16 | // this is required to get directory listings (?bug?) 17 | middleware: { 1: null } 18 | }, 19 | scrollRestoreTechnique: 'cookie' 20 | } -------------------------------------------------------------------------------- /examples-browser/aframe-parametric/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Verdana, Geneva, sans-serif; 3 | background-color: rgb(31, 37, 50); 4 | } 5 | 6 | #buttons { 7 | position: absolute; 8 | top: 25px; 9 | right: 15px; 10 | white-space: nowrap; 11 | font-weight: 300; 12 | } 13 | #buttons a { 14 | display: inline; 15 | vertical-align: middle; 16 | margin-left: 10px; 17 | padding: 8px 10px 8px 10px; 18 | background-color: rgba(0,0,0,0.65); 19 | color: white; 20 | cursor: pointer; 21 | text-decoration: none; 22 | text-shadow: 0px 0px 10px rgba(0,0,0,0.5); 23 | } -------------------------------------------------------------------------------- /examples-browser/change-material/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Verdana, Geneva, sans-serif; 3 | background-color: rgb(31, 37, 50); 4 | } 5 | 6 | #buttons { 7 | position: absolute; 8 | top: 25px; 9 | right: 15px; 10 | white-space: nowrap; 11 | font-weight: 300; 12 | } 13 | #buttons a { 14 | display: inline; 15 | vertical-align: middle; 16 | margin-left: 10px; 17 | padding: 8px 10px 8px 10px; 18 | background-color: rgba(0,0,0,0.65); 19 | color: white; 20 | cursor: pointer; 21 | text-decoration: none; 22 | text-shadow: 0px 0px 10px rgba(0,0,0,0.5); 23 | } -------------------------------------------------------------------------------- /tasks/jshint.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const jshintConfigs = require('./jshint-configs') 3 | const jshint = require('gulp-jshint') 4 | const jshintReporter = require('jshint-stylish') 5 | 6 | module.exports = function runJshint () { 7 | return gulp.src('src/**/*.js') 8 | .pipe(jshint(jshintConfigs)) 9 | .pipe(jshint.reporter(jshintReporter)) 10 | .pipe(jshint.reporter({ 11 | reporter: function(errors) { 12 | if (errors.length) { 13 | console.error(`Stopped after ${errors.length} JSHINT Errors.`) 14 | process.exit(1) 15 | } 16 | } 17 | })) 18 | } -------------------------------------------------------------------------------- /examples-browser/staging/stage-scene-structure/style.css: -------------------------------------------------------------------------------- 1 | .controls { 2 | font-family: arial; 3 | font-size: 14px; 4 | position: absolute; 5 | z-index:1000; top: 20px; left: 20px; 6 | } 7 | 8 | #select-label { 9 | display: none; 10 | list-style-type: none; 11 | padding: 0; 12 | margin: 5px 0; 13 | } 14 | 15 | #select-label li { 16 | font-family: arial; 17 | font-size: 12px; 18 | letter-spacing: 1px; 19 | padding: 4px 15px; 20 | width: 100px; 21 | background: rgba(248, 248, 250, 0.8); 22 | margin-bottom: 2px; 23 | cursor: pointer; 24 | } 25 | 26 | #select-label li:hover { 27 | background: rgba(255, 127, 80, 0.8); 28 | } -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/floorplan.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'reference to a floor plan image', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 0, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | w: { // width in meters 11 | type: 'number', 12 | optional: false, 13 | min: 0.01 14 | }, 15 | l: { // length in meters 16 | type: 'number', 17 | optional: false, 18 | min: 0.01 19 | }, 20 | file: { 21 | type: 'string', 22 | optional: false 23 | } 24 | }, 25 | childrenTypes: [], 26 | parentTypes: ['level'] 27 | } -------------------------------------------------------------------------------- /src/scene/structure/get.js: -------------------------------------------------------------------------------- 1 | import callService from '../../utils/services/call.js' 2 | import uuid from '../../utils/uuid.js' 3 | 4 | export default function getSceneStructure (id) { 5 | if (!uuid.validate(id)) return Promise.reject('id not valid') 6 | return callService('Model.read', { arguments: {resourceId:id}}) 7 | .then(function(result) { 8 | var sceneStructure = result.modelStructure 9 | sceneStructure.id = result.modelResourceId 10 | sceneStructure.v = 1 11 | sceneStructure.modelDisplayName = result.modelDisplayName 12 | sceneStructure.modelResourceName = result.modelResourceName 13 | return sceneStructure 14 | }) 15 | } -------------------------------------------------------------------------------- /src/utils/auth/regenerate-secret-api-key.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import Promise from 'bluebird' 3 | import log from 'js-logger' 4 | 5 | /** 6 | * Reenerate secret API key 7 | */ 8 | 9 | export default function regenerateSecretApiKey () { 10 | log.debug('Sending API request to generate secret API key ...') 11 | return callServices('Organization.generateSecretApiKey').then(function onSuccess(key) { 12 | log.debug('API: Generating secret API key successful: ', key) 13 | return key 14 | }, function onReject(error) { 15 | log.error('API: Error generating secret key.', error) 16 | return Promise.reject(error) 17 | }) 18 | } -------------------------------------------------------------------------------- /src/utils/io/check-if-file-exists.js: -------------------------------------------------------------------------------- 1 | import fetch from './fetch.js' 2 | import addCacheBustToQuery from './common/add-cache-bust-to-query.js' 3 | 4 | export default function checkIfFileExists (url) { 5 | 6 | if (!url) return Promise.reject('Please provide file URL string as param.') 7 | 8 | return fetch( addCacheBustToQuery(url), { 9 | method: 'HEAD', 10 | cache: 'reload' 11 | }).then(function onSuccess (response) { 12 | // response.ok provides boolean type information on request status 13 | // https://developer.mozilla.org/en-US/docs/Web/API/Response/ok 14 | return response.ok 15 | }, function onReject () { 16 | return false 17 | }) 18 | } -------------------------------------------------------------------------------- /src/core/bootstrap.js: -------------------------------------------------------------------------------- 1 | import { name, version, homepage } from '../../package.json' 2 | import Logger from 'js-logger' 3 | import './polyfills.js' 4 | import runtime from './runtime.js' 5 | 6 | // Bootstrap logger 7 | Logger.useDefaults() 8 | 9 | // print header to console in browser environment 10 | if (runtime.isBrowser) { 11 | console.log( 12 | homepage + 13 | ' ' + 14 | version + 15 | ' (@' + 16 | GIT_BRANCH + 17 | ' #' + 18 | GIT_COMMIT.substr(0, 7) + 19 | ' ' + 20 | BUILD_DATE + 21 | ')' 22 | ) 23 | } 24 | 25 | // global dependencies 26 | 27 | // three.js 28 | if (runtime.isNode) global.THREE = require('three') 29 | -------------------------------------------------------------------------------- /src/utils/auth/list-publishable-api-keys.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import Promise from 'bluebird' 3 | import log from 'js-logger' 4 | 5 | /** 6 | * List publishable API keys 7 | */ 8 | 9 | export default function listPublishableApiKeys () { 10 | log.debug('Sending API request to list publishable API keys ...') 11 | return callServices('Organization.listPublishableApiKeys').then(function onSuccess(keys) { 12 | log.debug('API: Listing publishable API keys successful: ', keys) 13 | return keys 14 | }, function onReject(error) { 15 | log.error('API: Error listing publishable keys.', error) 16 | return Promise.reject(error) 17 | }) 18 | } -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/common/data-to-materials.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import cloneDeep from 'lodash/cloneDeep' 3 | import getMaterial from '../../../../scene/structure/parametric-objects/common/get-material.js' 4 | 5 | export default function dataToMaterials(data) { 6 | 7 | let materials={} 8 | 9 | Object.keys(data).filter(function(key) { 10 | return key.indexOf('material_') > -1 11 | }).forEach(function(key) { 12 | var mesh = key.replace('material_', '') 13 | materials[mesh] = data[key] 14 | }) 15 | 16 | Object.keys(materials).forEach(mat => { 17 | materials[mat] = getMaterial(materials[mat]) 18 | }) 19 | 20 | return materials; 21 | } 22 | -------------------------------------------------------------------------------- /src/furniture/get-data3d-storage-id.js: -------------------------------------------------------------------------------- 1 | import callService from '../utils/services/call.js' 2 | import normalizeFurnitureInfo from './common/normalize-furniture-info.js' 3 | 4 | export default function getFurnitureData3dStorageId (furnitureId) { 5 | return callService('Product.read', { resourceId:furnitureId }).then(function(rawInfo){ 6 | 7 | var info = normalizeFurnitureInfo(rawInfo) 8 | 9 | // some furniture might not have a storage id (i.e. groups) 10 | var storageId = info && info.data3dStorageId ? info.data3dStorageId : null 11 | 12 | return storageId ? storageId : Promise.reject('This furniture has no own Storage ID (i.e. is a group of furniture objects)') 13 | }) 14 | } -------------------------------------------------------------------------------- /examples-browser/storage-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
drag & drop files here
or
click to select
13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/furniture/get.js: -------------------------------------------------------------------------------- 1 | import getFurnitureInfo from './get-info.js' 2 | import loadData3d from '../utils/data3d/load.js' 3 | 4 | export default function getFurniture (id) { 5 | // we need to call furniture info first in order to obtain data3d URL 6 | return getFurnitureInfo(id).then(function(info){ 7 | return loadData3d(info.data3dUrl, { loadingQueuePrefix: 'interior' }).then(function(data3d){ 8 | return { 9 | // contains lightweight metadata like designer name and description 10 | info: info, 11 | // contains geometry and material definitions 12 | data3d: data3d 13 | } 14 | }) 15 | .catch(function(err) { 16 | console.warn(err) 17 | }) 18 | }) 19 | } -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/polybox.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'polygonal extrusion object', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 1, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | h: { // height in meters 11 | type: 'number', 12 | defaultValue: 1, 13 | optional: false, 14 | min: 0.01 15 | }, 16 | polygon: { 17 | //type: 'array-with-arrays-with-numbers', 18 | type: 'array', 19 | aframeType: 'string', 20 | optional: false 21 | } 22 | }, 23 | childrenTypes: [], 24 | parentTypes: ['level'], 25 | aframeComponent: { 26 | name: 'io3d-polybox' 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/ui/create-alert-ui.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | import createConfirmUi from './create-confirm-ui.js' 3 | import el from './common/dom-el.js' 4 | 5 | // main 6 | 7 | export default function createAlertUi (a, b) { 8 | runtime.assertBrowser() 9 | 10 | if (el.isElement(a) || typeof a === 'string') { 11 | b = b || {} 12 | b.closeButton = false 13 | b.cancelButton = false 14 | b.confirmButton = true 15 | } else if (typeof a === 'object') { 16 | a.closeButton = false 17 | a.cancelButton = false 18 | a.confirmButton = true 19 | } else { 20 | throw 'Argument mismatch https://3d.io/docs/api/1/ui.html' 21 | } 22 | 23 | return createConfirmUi(a, b) 24 | } -------------------------------------------------------------------------------- /src/scene/structure/parametric-objects/common/get-material.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cloneDeep from 'lodash/cloneDeep' 4 | import materialLibrary from './material-lib.js' 5 | 6 | export default function (material) { 7 | var STORAGE_URL = 'https://storage.3d.io/' 8 | var mat = materialLibrary[material] 9 | 10 | if (!mat) return material 11 | 12 | var attr = cloneDeep(mat.attributes) 13 | Object.keys(attr).forEach(a => { 14 | // get textures 15 | if (a.indexOf('map') > -1 ) { 16 | // fix to prevent double slash 17 | if (attr[a][0] === '/') attr[a] = attr[a].substring(1) 18 | // get full texture path 19 | attr[a] = STORAGE_URL + attr[a] 20 | } 21 | }) 22 | return attr 23 | } 24 | -------------------------------------------------------------------------------- /examples-node/main.js: -------------------------------------------------------------------------------- 1 | var io3d = require('../build/3dio.js') 2 | 3 | // var key = '/3f995099-d624-4c8e-ab6b-1fd5e3799173/170420-1105-7yb890/8db476b3-7eb7-45a7-a09b-03e965f12685.gz.data3d.buffer' 4 | var key = '/fd72bf39-9d3a-471f-a4ff-ecaa3f5ff30b/bake/2017-04-15_22-45-14_XsiltX/regular/lighting.gz.data3d.buffer' 5 | 6 | 7 | // get object from base store 8 | // io3d.storage.get(key).then(function(data3d){ 9 | // console.log('materials: ', data3d.materials) 10 | // }) 11 | 12 | console.log(io3d.runtime.libInfo) 13 | 14 | // io3d.furniture.get('d23062b7-6ec4-45c0-9cee-c96d835b5f54').then(console.log) 15 | 16 | 17 | // io3d.storage.put( Buffer.from('Hello World!', 'utf-8') ).then(function(key){ 18 | // console.log(key) 19 | // }).catch(console.error) -------------------------------------------------------------------------------- /src/utils/auth/revoke-publishable-api-key.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import Promise from 'bluebird' 3 | import log from 'js-logger' 4 | 5 | /** 6 | * Revoke publishable API key 7 | */ 8 | 9 | export default function revokePublishableApiKey (args) { 10 | 11 | var key = args.key 12 | 13 | log.debug('Sending API request to generate publishable API key ...') 14 | return callServices('Organization.revokePublishableApiKey', { 15 | key: key 16 | }).then(function onSuccess(message) { 17 | log.debug('API: Revoking publishable API key successful: ', message) 18 | return message 19 | }, function onReject(error) { 20 | log.error('API: Error revoking publishable key.', error) 21 | return Promise.reject(error) 22 | }) 23 | } -------------------------------------------------------------------------------- /src/aframe/three/data3d-view/io3d-material/vertex.glsl: -------------------------------------------------------------------------------- 1 | varying vec3 vViewPosition; 2 | 3 | #ifndef FLAT_SHADED 4 | varying vec3 vNormal; 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | void main() 12 | { 13 | // vUv = uv; 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #ifndef FLAT_SHADED 21 | // Normal computed with derivatives when FLAT_SHADED 22 | vNormal = normalize( transformedNormal ); 23 | #endif 24 | 25 | #include 26 | #include 27 | 28 | vViewPosition = - mvPosition.xyz; 29 | 30 | #include 31 | #include 32 | 33 | } -------------------------------------------------------------------------------- /src/utils/uuid.js: -------------------------------------------------------------------------------- 1 | var PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i 2 | 3 | /** 4 | * Generate an UUID as specified in RFC4122 5 | */ 6 | 7 | var uuid = {} 8 | 9 | uuid.generate = function generateUuid () { 10 | var d = Date.now() 11 | var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { 12 | var r = (d + Math.random() * 16) % 16 | 0; 13 | d = Math.floor(d / 16); 14 | return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); 15 | }) 16 | return uuid 17 | } 18 | 19 | /** 20 | * Validates UUID as specified in RFC4122 21 | */ 22 | 23 | uuid.validate = function validateUuid (str) { 24 | if (!str || typeof str !== "string") return false 25 | return PATTERN.test(str) 26 | } 27 | 28 | export default uuid -------------------------------------------------------------------------------- /src/utils/image/get-blob-from-canvas.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird' 2 | import runtime from '../../core/runtime.js' 3 | import getDefaultFilename from '../file/get-default-filename.js' 4 | 5 | export default function getBlobFromCanvas (canvas, options) { 6 | runtime.assertBrowser() 7 | 8 | // API 9 | options = options || {} 10 | var mimeType = options.mimeType || 'image/jpeg' // can be: 'image/jpeg' or 'image/png' 11 | var quality = options.quality || 98 12 | var fileName = options.fileName || getDefaultFilename() + (mimeType === 'image/jpeg' ? '.jpg' : '.png') 13 | 14 | // run 15 | return new Promise(function (resolve, reject) { 16 | canvas.toBlob(function (blob) { 17 | blob.name = fileName 18 | resolve(blob) 19 | }, mimeType, quality) 20 | }) 21 | 22 | } -------------------------------------------------------------------------------- /examples-browser/no-view/main.js: -------------------------------------------------------------------------------- 1 | var key = '/3f995099-d624-4c8e-ab6b-1fd5e3799173/170420-1105-7yb890/8db476b3-7eb7-45a7-a09b-03e965f12685.gz.data3d.buffer' 2 | 3 | 4 | // get object from base store 5 | // io3d.storage.get(key).then(function(data3d){ 6 | // console.log('Data3d has '+Object.keys(data3d.materials).length+' materials.') 7 | // }) 8 | 9 | // upload object to base store 10 | // var file = new Blob(['Hello World'], { type: 'text/plain' }) 11 | // file.name = 'blob.txt' 12 | // io3d.storage.put(file).then(function(key){ 13 | // console.log(key) 14 | // }).catch(console.log) 15 | 16 | // get product data 17 | // io3d.furniture.get('d23062b7-6ec4-45c0-9cee-c96d835b5f54').then(function(result){ 18 | // console.log('Data3d has '+Object.keys(result.data3d.materials).length+' materials.') 19 | // }) 20 | -------------------------------------------------------------------------------- /src/utils/auth/generate-publishable-api-key.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import Promise from 'bluebird' 3 | import log from 'js-logger' 4 | 5 | /** 6 | * Generate publishable API key 7 | */ 8 | 9 | export default function generatePublishableApiKey (args) { 10 | 11 | var allowedDomains = args.allowedDomains 12 | 13 | log.debug('Sending API request to generate publishable API key ...') 14 | return callServices('Organization.generatePublishableApiKey', { 15 | allowedDomains: allowedDomains 16 | }).then(function onSuccess(key) { 17 | log.debug('API: Generating publishable API key successful: ', key) 18 | return key 19 | }, function onReject(error) { 20 | log.error('API: Error generating publishable key.', error) 21 | return Promise.reject(error) 22 | }) 23 | } -------------------------------------------------------------------------------- /src/floor-plan/convert-to-basic-3d-model.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird' 2 | import callServices from '../utils/services/call.js' 3 | 4 | export default function convertFloorPlanToBasic3dModel (args) { 5 | 6 | // API 7 | var floorPlan = args.floorPlan 8 | var address = args.address 9 | var callback = args.callback 10 | 11 | // send request to server side endpoint 12 | return callServices('FloorPlan.convertToBasic3dModel', { 13 | floorplan: floorPlan, 14 | address: address, 15 | callback: callback 16 | }).then(function onSuccess (result) { 17 | // conversion request accepted 18 | return result.conversionId 19 | }).catch(function onError (error) { 20 | // conversion request error 21 | // TODO: provide info in debug mode 22 | return Promise.reject(error) 23 | }) 24 | 25 | } -------------------------------------------------------------------------------- /src/storage/get-id-from-url.js: -------------------------------------------------------------------------------- 1 | import configs from '../core/configs.js' 2 | 3 | // constants 4 | var URL_TO_ID_CACHE = {} 5 | 6 | // main 7 | export default function getStorageIdFromUrl(url) { 8 | // check cache 9 | if (URL_TO_ID_CACHE[url]) return URL_TO_ID_CACHE[url] 10 | 11 | var isStorageRegexp = new RegExp( 12 | '^(http(s?))\\:\\/\\/(' + 13 | configs.storageDomain + 14 | '|' + 15 | configs.storageDomainNoCdn + 16 | ')' 17 | ) 18 | 19 | // check if url is valid url 20 | if (isStorageRegexp.test(url)) { 21 | var storageId = url.replace(isStorageRegexp, '') 22 | // add to cache 23 | URL_TO_ID_CACHE[url] = storageId 24 | return storageId 25 | 26 | } else { 27 | console.error('Provided URL is not a valid URL:', url) 28 | return undefined 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/io/fetch-image.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird' 2 | import runtime from '../../core/runtime.js' 3 | 4 | export default function fetchImage (url) { 5 | return new Promise(function (resolve, reject) { 6 | 7 | var img = document.createElement('img') 8 | img.crossOrigin = 'Anonymous' 9 | 10 | img.onload = function () { 11 | resolve(img) 12 | } 13 | 14 | var triedWithCacheBust = false 15 | img.onerror = function () { 16 | if(triedWithCacheBust) { 17 | reject('Error loading image ' + url) 18 | } else { 19 | // try again with cache busting to avoid things like #1510 20 | triedWithCacheBust = true 21 | img.src = ( url.indexOf('?') > -1 ? '&' : '&' ) + 'cacheBust=' + new Date().getTime() 22 | } 23 | } 24 | 25 | // initiate image loading 26 | img.src = url 27 | 28 | }) 29 | } -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/camera-bookmark.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'preset camera positions for animations and navigation', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 0, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | rx: { 11 | type: 'number', 12 | defaultValue: 0, 13 | skipInAframe: true, 14 | description: 'pitch' 15 | }, 16 | distance: { 17 | type: 'number', 18 | skipInAframe: true 19 | }, 20 | fov: { 21 | type: 'number', 22 | defaultValue: 71, 23 | skipInAframe: true 24 | }, 25 | name: { 26 | type: 'string', 27 | defaultValue: 'Camera Bookmark' 28 | } 29 | }, 30 | parentTypes: ['plan'], 31 | aframeComponent: { 32 | name: 'tour-waypoint' 33 | } 34 | } -------------------------------------------------------------------------------- /src/utils/file/read.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | 3 | var FILE_READ_METHODS = { 4 | undefined: 'readAsText', 5 | text: 'readAsText', 6 | dataUrl: 'readAsDataURL', 7 | binaryString: 'readAsBinaryString', 8 | arrayBuffer: 'readAsArrayBuffer' 9 | } 10 | 11 | export default function readFile(blob, type) { 12 | runtime.assertBrowser() 13 | 14 | return new Promise(function(resolve, reject){ 15 | var fileReader = new window.FileReader() 16 | fileReader.onload = function (e) { 17 | // IE 11 requires this 18 | // http://stackoverflow.com/a/32665193/2835973 19 | resolve(fileReader.content || fileReader.result) 20 | } 21 | fileReader.onerror = function (err){ 22 | reject(err) 23 | } 24 | // start reading file 25 | fileReader[ FILE_READ_METHODS[type] ](blob) 26 | }) 27 | } -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/railing.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import railing from '../../../../src/scene/structure/parametric-objects/railing' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true, require: require})) 7 | 8 | test('get railing data3d', async function() { 9 | const attributes = applyDefaults({type: 'railing'}) 10 | const data3d = await railing(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual([ 'railing' ]) 14 | expect(data3d.meshes.railing.positions).toBeDefined() 15 | // check for valid vertices 16 | expect(data3d.meshes.railing.positions.every(a => !isNaN(a))).toBeTruthy() 17 | }); 18 | -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/object.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'referenced 3d object in data3d.buffer format, for conversion drop a .obj into the editor spaces.archilogic.com/3d', 3 | params: { 4 | object: { 5 | type: 'string', 6 | optional: false, 7 | skipInAframe: true, 8 | description: 'reference to data3d.buffer file' 9 | }, 10 | sourceScale: { 11 | type: 'number', 12 | optional: true, 13 | skipInAframe: true, 14 | description: 'relative scale of source file to 1 meter' 15 | }, 16 | flipYZ: { 17 | type: 'boolean', 18 | optional: true, 19 | skipInAframe: true, 20 | description: 'flip Y and Z Axis' 21 | } 22 | }, 23 | childrenTypes: ['interior'], 24 | parentTypes: ['level'], 25 | aframeComponent: { 26 | name: 'io3d-data3d' 27 | } 28 | } -------------------------------------------------------------------------------- /src/utils/processing/get-convertible-texture-ids.js: -------------------------------------------------------------------------------- 1 | import getUrlFromStorageId from '../../storage/get-url-from-id.js' 2 | import getStorageIdFromUrl from '../../storage/get-id-from-url.js' 3 | import getTextureKeys from '../data3d/get-texture-keys.js' 4 | import loadData3d from '../data3d/load.js' 5 | 6 | export default function getConvertibleTextureKeys(storageId) { 7 | 8 | var url = getUrlFromStorageId(storageId) 9 | 10 | return loadData3d(url) 11 | .then(function(data3d) { 12 | 13 | return getTextureKeys(data3d, { 14 | filter: function (storageId, type, format, material, data3d) { 15 | // use source maps only 16 | return format === 'source' ? storageId : false 17 | } 18 | }) 19 | 20 | }) 21 | .then(function(textures) { 22 | return textures.map(getStorageIdFromUrl) 23 | }) 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/scene/structure/remove-unknown.js: -------------------------------------------------------------------------------- 1 | import getDefaultsByType from './validate/get-defaults-by-type.js' 2 | 3 | export default function removeUnknown(element3d) { 4 | var knownParameters = getDefaultsByType() 5 | // remove invalid types entirely 6 | if (!knownParameters[element3d.type]) return 7 | 8 | var params = knownParameters[element3d.type].params 9 | var possibleChildren = knownParameters[element3d.type].childrenTypes 10 | // remove invalid params 11 | Object.keys(element3d).forEach(function(key) { 12 | if (!params[key]) { 13 | delete element3d[key] 14 | } 15 | }) 16 | // remove invalid children 17 | if (element3d.children && element3d.children.length) { 18 | element3d.children = element3d.children.filter(function(child) { 19 | return possibleChildren.indexOf(child.type) > -1 20 | }) 21 | } 22 | return element3d 23 | } -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/closet.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import closet from '../../../../src/scene/structure/parametric-objects/closet' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true})) 7 | 8 | test('get closet data3d', async function() { 9 | const attributes = applyDefaults({type: 'closet'}) 10 | const data3d = await closet(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual([ 'closet' ]) 14 | expect(data3d.meshes.closet.positions).toBeDefined() 15 | expect(data3d.meshes.closet.uvs).toBeDefined() 16 | 17 | // check for valid vertices 18 | expect(data3d.meshes.closet.positions.every(a => !isNaN(a))).toBeTruthy() 19 | }); 20 | -------------------------------------------------------------------------------- /src/utils/processing/when-done.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | import poll from '../poll.js' 3 | import fetch from '../io/fetch.js' 4 | import urlUtils from '../url.js' 5 | import configs from '../../core/configs.js' 6 | 7 | // main 8 | 9 | export default function getResult(processingId) { 10 | return poll(function(resolve, reject, next) { 11 | var url = 'https://' + configs.storageDomainNoCdn + '/' + processingId 12 | 13 | fetch(url) 14 | .then(function(response) { 15 | return response.json() 16 | }) 17 | .then(function(message) { 18 | var status = message.params.status 19 | 20 | if (status === 'ERROR') { 21 | reject(message.params.data) 22 | } else if (status === 'SUCCESS') { 23 | resolve(message.params.data) 24 | } else { 25 | next() 26 | } 27 | }) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /src/aframe/inspector-plugins-launcher.js: -------------------------------------------------------------------------------- 1 | import fetchScript from '../utils/io/fetch-script.js' 2 | 3 | // internals 4 | 5 | var INSPECTOR_PLUGINS_URL = 'https://dist.3d.io/3dio-inspector-plugins/0.x.x/3dio-inspector-plugins.js' 6 | 7 | function init() { 8 | 9 | if (window.AFRAME && window.AFRAME.INSPECTOR && window.AFRAME.INSPECTOR.opened) { 10 | // inspector opened: load immediately 11 | loadPlugins() 12 | } else { 13 | // initialize on inspector ready event 14 | window.addEventListener('inspector-loaded', loadPlugins) 15 | } 16 | 17 | // methods 18 | 19 | function loadPlugins () { 20 | if (window.io3d.aframe.pluginsLoaded) return 21 | fetchScript(INSPECTOR_PLUGINS_URL).catch(function(error){ 22 | console.error('Could not load inspector plugins: '+error) 23 | }) 24 | } 25 | 26 | } 27 | 28 | // expose API 29 | 30 | export default { 31 | init: init 32 | } -------------------------------------------------------------------------------- /src/utils/data3d/load.js: -------------------------------------------------------------------------------- 1 | import fetch from '../io/fetch.js' 2 | import decodeBinary from './decode-binary.js' 3 | import PromiseCache from '../promise-cache.js' 4 | 5 | // private shared 6 | 7 | var cache = new PromiseCache() 8 | 9 | // main 10 | 11 | export default function loadData3d (url, options) { 12 | 13 | // prevent loading of unsupported formats 14 | if (url.indexOf('data3d.buffer') < 0) return Promise.reject(url + ' no data3d') 15 | // try cache 16 | var cacheKey = url 17 | var promiseFromCache = cache.get(cacheKey) 18 | if (promiseFromCache) return promiseFromCache 19 | 20 | // fetch 21 | var promise = fetch(url, options).then(function(res){ 22 | return res.arrayBuffer() 23 | }).then(function(buffer){ 24 | return decodeBinary(buffer, { url: url }) 25 | }) 26 | 27 | // add to cache 28 | cache.add(cacheKey, promise) 29 | 30 | return promise 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/auth/update-publishable-api-key-domains.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import Promise from 'bluebird' 3 | import log from 'js-logger' 4 | 5 | /** 6 | * Update publishable API key domains 7 | */ 8 | 9 | export default function updatePublishableApiKeyDomains (args) { 10 | 11 | var key = args.key 12 | var allowedDomains = args.allowedDomains 13 | 14 | log.debug('Sending API request to update publishable API key domains ...') 15 | return callServices('Organization.updatePublishableApiKeyDomains', { 16 | key: key, 17 | allowedDomains: allowedDomains 18 | }).then(function onSuccess(message) { 19 | log.debug('API: Updating publishable API key domains successful: ', message) 20 | return message 21 | }, function onReject(error) { 22 | log.error('API: Error updating publishable key domains.', error) 23 | return Promise.reject(error) 24 | }) 25 | } -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/box.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'simple box object', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 0, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | l: { // length in meters 11 | type: 'number', 12 | defaultValue: 1, 13 | optional: false, 14 | min: 0.01, 15 | description: 'length' 16 | }, 17 | w: { // width in meters 18 | type: 'number', 19 | defaultValue: 1, 20 | optional: false, 21 | min: 0.01, 22 | description: 'width' 23 | }, 24 | h: { // height in meters 25 | type: 'number', 26 | defaultValue: 1, 27 | optional: false, 28 | min: 0.01, 29 | description: 'height' 30 | } 31 | }, 32 | childrenTypes: [], 33 | parentTypes: ['level'], 34 | aframeComponent: { 35 | name: 'io3d-box' 36 | } 37 | } -------------------------------------------------------------------------------- /src/utils/auth/get-secret-api-key.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import regenerateSecretApiKey from './regenerate-secret-api-key.js' 3 | import log from 'js-logger' 4 | 5 | /** 6 | * Get secret API key 7 | * @function io3d.auth.getSecretApiKey 8 | */ 9 | 10 | export default function getSecretApiKey () { 11 | log.debug('Sent API request reading secret key ...') 12 | return callServices('Organization.read').then(function onSuccess (result) { 13 | if (result.secretApiKey) { 14 | log.debug('Received secret API key from API') 15 | return result.secretApiKey 16 | } else { 17 | // user has no secret key yet: generate one 18 | log.debug('User has no secret key. Sent request to generate one.') 19 | return regenerateSecretApiKey() 20 | } 21 | }, function onError (error) { 22 | log.debug('Error receiving secret API key') 23 | return Promise.reject(error) 24 | }) 25 | } -------------------------------------------------------------------------------- /tasks/dev-node.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const build = require('./build') 3 | const chalk = require('chalk') 4 | const spawn = require('child_process').spawn 5 | 6 | // tasks 7 | 8 | const runNodeExample = gulp.series( 9 | build, 10 | runScriptFromCli 11 | ) 12 | 13 | function runScriptFromCli () { 14 | return new Promise((resolve, reject) => { 15 | const ls = spawn('node', ['examples-node/main.js']) 16 | ls.stdout.on('data', (data) => { 17 | console.log(`out: ${data}`) 18 | }) 19 | ls.stderr.on('data', (data) => { 20 | console.error(chalk.red(`err: ${data}`)) 21 | }) 22 | ls.on('close', (code) => { 23 | if (code === 0) { 24 | console.log(`example: done`) 25 | resolve() 26 | } else { 27 | throw new Error(`example: exited with code ${code}`.red) 28 | reject() 29 | } 30 | }) 31 | }) 32 | } 33 | 34 | // exports 35 | 36 | module.exports = runNodeExample -------------------------------------------------------------------------------- /src/aframe/check-dependencies.js: -------------------------------------------------------------------------------- 1 | import runtime from '../core/runtime.js' 2 | 3 | export default function checkDependencies (args, target) { 4 | 5 | if (args.three && !runtime.has.three) { 6 | return handleError(args.onError, target, 'Sorry: THREE not available.') 7 | } else if (args.aframe && !runtime.has.aframe) { 8 | return handleError(args.onError, target, 'Sorry: AFRAME not available.') 9 | } else { 10 | return typeof target === 'function' ? target() : target 11 | } 12 | 13 | } 14 | 15 | // helper 16 | 17 | function handleError (errorCallback, target, message) { 18 | // call errorCallback if provided 19 | if (errorCallback) errorCallback(message) 20 | // based on target type... 21 | if (typeof target === 'function') { 22 | // return a function throwing an error to handle runtime access 23 | return function onError () { 24 | throw new Error(message) 25 | } 26 | } else { 27 | return false 28 | } 29 | } -------------------------------------------------------------------------------- /src/utils/auth/request-password-reset.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import Promise from 'bluebird' 3 | import log from 'js-logger' 4 | 5 | /** 6 | * Request password reset for a specific user 7 | * @function io3d.auth.requestPasswordReset 8 | * @param {object} args 9 | * @param {string} args.email 10 | */ 11 | export default function requestPasswordReset (args) { 12 | 13 | var credentials = { 14 | email: args.email 15 | } 16 | 17 | log.debug('Sending password reset request to API ...') 18 | return callServices('User.requestPasswordReset', credentials) 19 | .then(function onSuccess(result) { 20 | 21 | // success 22 | log.debug('API: requesting password reset successful.') 23 | return Promise.resolve() 24 | 25 | }, function onError(error){ 26 | 27 | // denied 28 | log.debug('API: requesting password reset failed.', error) 29 | return Promise.reject(error) 30 | 31 | }) 32 | 33 | } -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/window.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import window3d from '../../../../src/scene/structure/parametric-objects/window' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true})) 7 | 8 | test('get window data3d', async function(){ 9 | const attributes = applyDefaults({type: 'window'}) 10 | const data3d = await window3d(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual([ 'frame', 'glass' ]) 14 | expect(data3d.meshes.frame.positions).toBeDefined() 15 | expect(data3d.meshes.frame.normals).toBeDefined() 16 | // check for valid vertices 17 | expect(data3d.meshes.frame.positions.every(a => !isNaN(a))).toBeTruthy() 18 | expect(data3d.meshes.glass.positions.every(a => !isNaN(a))).toBeTruthy() 19 | }); 20 | -------------------------------------------------------------------------------- /src/utils/auth/log-out.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import getSession from './get-session.js' 3 | import Promise from 'bluebird' 4 | import log from 'js-logger' 5 | 6 | /** 7 | * Log out currently authenticated user. 8 | * @function io3d.auth.logOut 9 | */ 10 | export default function logOut () { 11 | 12 | log.debug('Sending API log out request...') 13 | return callServices('User.logOut').then(function onLogoutSuccess (result) { 14 | 15 | // verify if user has been logged out 16 | return getSession() 17 | 18 | }).then(function onSessionSuccess (session) { 19 | 20 | if (!session.isAuthenticated) { 21 | log.debug('API: Log out successful.') 22 | return session 23 | } else { 24 | return Promise.reject('Log out error: Session has not been terminated.') 25 | } 26 | 27 | }).catch(function onError (error) { 28 | 29 | log.error('Log out error.', error) 30 | return Promise.reject(error) 31 | 32 | }) 33 | } -------------------------------------------------------------------------------- /src/utils/auth/resend-activation-email.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import Promise from 'bluebird' 3 | import log from 'js-logger' 4 | 5 | /** 6 | * Resend activation email 7 | * @function io3d.auth.resendActivationEmail 8 | * @param {object} args 9 | * @param {string} args.email 10 | */ 11 | export default function resendActivationEmail (args) { 12 | 13 | var credentials = { 14 | email: args.email 15 | } 16 | 17 | // 18 | log.debug('Sending account activation request to API ...') 19 | return callServices('User.requestAccountActivation', credentials) 20 | .then(function onSuccess(result) { 21 | 22 | // success 23 | log.debug('API: requesting account activation successful.') 24 | return Promise.resolve() 25 | 26 | }, function onError(error){ 27 | 28 | // denied 29 | log.debug('API: requesting account activation failed.', error) 30 | return Promise.reject(error) 31 | 32 | }) 33 | 34 | 35 | } -------------------------------------------------------------------------------- /src/utils/data3d/store-in-cache.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | import loadData3d from './load.js' 3 | import getTextureUrls from './get-texture-urls' 4 | 5 | export default function storeInCache (url, cacheName) { 6 | if (!isCacheAvailable()) return Promise.reject() 7 | 8 | return window.caches.open(cacheName || '3dio-data3d').then(function onCacheReady(cache) { 9 | return loadData3d(url).then(function onData3dReady(data3d) { 10 | var cacheUrls = new Array(url).concat(getTextureUrls(data3d)) 11 | return cache.addAll(cacheUrls) 12 | }) 13 | }) 14 | } 15 | 16 | // helpers 17 | 18 | function isCacheAvailable () { 19 | if (!runtime.isBrowser) { 20 | console.warn('The offline cache is only available in the browser') 21 | return false 22 | } 23 | 24 | if (typeof window.caches === 'undefined') { 25 | console.warn('Your browser does not support offline cache storage') 26 | return false 27 | } 28 | 29 | return true 30 | } -------------------------------------------------------------------------------- /src/utils/path.js: -------------------------------------------------------------------------------- 1 | // from https://github.com/jbgutierrez/path-parse 2 | // Split a filename into [root, dir, basename, ext], unix version 3 | // 'root' is just a slash, or nothing. 4 | var splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; 5 | 6 | function parsePath (path) { 7 | if (typeof path !== 'string') { 8 | throw new TypeError( 9 | "Parameter 'path' must be a string, not " + typeof path 10 | ); 11 | } 12 | var allParts = splitPathRe.exec(path).slice(1) 13 | if (!allParts || allParts.length !== 4) { 14 | throw new TypeError("Invalid path '" + path + "'"); 15 | } 16 | allParts[2] = allParts[2] || ''; 17 | allParts[3] = allParts[3] || ''; 18 | 19 | return { 20 | root: allParts[0], 21 | dir: allParts[0] + allParts[1].slice(0, -1), 22 | base: allParts[2], 23 | ext: allParts[3], 24 | name: allParts[2].slice(0, allParts[2].length - allParts[3].length) 25 | } 26 | } 27 | 28 | export default { 29 | parse: parsePath 30 | } -------------------------------------------------------------------------------- /examples-browser/no-view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | base query 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

console says:

14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tasks/build.js: -------------------------------------------------------------------------------- 1 | const spawn = require('child_process').spawn 2 | const gulp = require('gulp') 3 | const del = require('del') 4 | const chalk = require('chalk') 5 | 6 | // configs 7 | 8 | const dest = 'build' 9 | 10 | // tasks 11 | 12 | const build = gulp.series(cleanBuildDir, bundleScripts) 13 | 14 | function cleanBuildDir () { 15 | return del([dest]) 16 | } 17 | 18 | function bundleScripts () { 19 | return new Promise((resolve, reject) => { 20 | const ls = spawn('rollup', ['-c','tasks/rollup.config.js'], {shell: true} ) 21 | ls.stdout.on('data', (data) => { 22 | console.log(`rollup: ${data}`) 23 | }) 24 | ls.stderr.on('data', (data) => { 25 | console.error(chalk.red(`rollup: ${data}`)) 26 | }) 27 | ls.on('close', (code) => { 28 | if (code === 0) { 29 | resolve() 30 | } else { 31 | throw new Error(`rollup: exited with code ${code}`) 32 | reject() 33 | } 34 | }) 35 | }) 36 | } 37 | 38 | // export build task 39 | 40 | module.exports = build -------------------------------------------------------------------------------- /src/utils/data3d/remove-from-cache.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | import loadData3d from './load.js' 3 | import getTextureUrls from './get-texture-urls' 4 | 5 | export default function removeFromCache (url, cacheName) { 6 | if (!isCacheAvailable()) return Promise.reject() 7 | 8 | return window.caches.open(cacheName || '3dio-data3d').then(function onCacheReady(cache) { 9 | return loadData3d(url).then(function onData3dReady(data3d) { 10 | var cacheUrls = new Array(url).concat(getTextureUrls(data3d)) 11 | return Promise.all(cacheUrls.map(function removeItem(url) { return cache.delete(url) })) 12 | }) 13 | }) 14 | } 15 | 16 | // helpers 17 | 18 | function isCacheAvailable () { 19 | if (!runtime.isBrowser) { 20 | console.warn('The offline cache is only available in the browser') 21 | return false 22 | } 23 | 24 | if (typeof window.caches === 'undefined') { 25 | console.warn('Your browser does not support offline cache storage') 26 | return false 27 | } 28 | 29 | return true 30 | } -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/curtain.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'curtain with random folds', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 1, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | l: { // length in meters 11 | type: 'number', 12 | defaultValue: 1.8, 13 | optional: false, 14 | min: 0.01, 15 | description: 'length' 16 | }, 17 | w: { // width in meters 18 | type: 'number', 19 | defaultValue: 0.2, 20 | optional: false, 21 | min: 0.01, 22 | description: 'thickness' 23 | }, 24 | h: { // height in meters 25 | type: 'number', 26 | defaultValue: 2.4, 27 | optional: false, 28 | min: 0.01, 29 | description: 'height' 30 | }, 31 | folds: { 32 | type: 'number', 33 | defaultValue: 14, 34 | optional: true, 35 | min: 0.01, 36 | description: 'number of folds' 37 | } 38 | }, 39 | childrenTypes: [], 40 | parentTypes: ['level'] 41 | } -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/stairs.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import stairs from '../../../../src/scene/structure/parametric-objects/stairs' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true})) 7 | 8 | test('get stairs data3d', async function() { 9 | const attributes = applyDefaults({type: 'stairs'}) 10 | const data3d = await stairs(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual([ 'steps', 'tread', 'railing' ]) 14 | expect(data3d.meshes.steps.positions).toBeDefined() 15 | expect(data3d.meshes.steps.uvs).toBeDefined() 16 | // check for valid vertices 17 | expect(data3d.meshes.steps.positions.every(a => !isNaN(a))).toBeTruthy() 18 | expect(data3d.meshes.tread.positions.every(a => !isNaN(a))).toBeTruthy() 19 | expect(data3d.meshes.railing.positions.every(a => !isNaN(a))).toBeTruthy() 20 | }); 21 | -------------------------------------------------------------------------------- /src/modify/modify-model.js: -------------------------------------------------------------------------------- 1 | import callServices from '../utils/services/call.js' 2 | 3 | // main 4 | function getModifier(modifier) { 5 | return function modifyModel(storageId, options) { 6 | 7 | // API 8 | options = options || {} 9 | 10 | var modifySettings = {} 11 | if (options.ratio) modifySettings.ratio = options.ratio 12 | if (options.subdivisions) modifySettings.subdivisions = options.subdivisions 13 | 14 | var modifyParams = { 15 | method: 'modify'.concat('.', modifier), 16 | params: { 17 | inputFileKey: storageId 18 | } 19 | } 20 | 21 | if (Object.keys(modifySettings).length > 0) { 22 | modifyParams.params.settings = JSON.stringify(modifySettings) 23 | } 24 | 25 | return callServices('Processing.task.enqueue', modifyParams) 26 | 27 | } 28 | } 29 | 30 | // expose api 31 | 32 | export default { 33 | collisionObject: getModifier('collisionObject'), 34 | consolidateFaceSides: getModifier('consolidateFaceSides'), 35 | origami: getModifier('origami') 36 | } -------------------------------------------------------------------------------- /src/scene/structure/parametric-objects/common/get-materials.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import cloneDeep from 'lodash/cloneDeep' 4 | import materialLibrary from './material-lib.js' 5 | 6 | // resolve standard materials if necessary 7 | export default function (materials, defaults) { 8 | Object.keys(materials).map(meshName => { 9 | if (typeof(materials[meshName])==="string") { 10 | materials[meshName]=getMaterial(materials[meshName]) 11 | } 12 | }) 13 | return Promise.resolve(materials) 14 | } 15 | 16 | function getMaterial(material) { 17 | var STORAGE_URL = 'https://storage.3d.io/' 18 | var mat = materialLibrary[material] 19 | 20 | if (!mat) return material 21 | 22 | var attr = cloneDeep(mat.attributes) 23 | Object.keys(attr).forEach(a => { 24 | // get textures 25 | if (a.indexOf('map') > -1 ) { 26 | // fix to prevent double slash 27 | if (attr[a][0] === '/') attr[a] = attr[a].substring(1) 28 | // get full texture path 29 | attr[a] = STORAGE_URL + attr[a] 30 | } 31 | }) 32 | return attr 33 | } 34 | -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/column.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'simple structural column object, round or square', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 1, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | l: { // diameter 11 | type: 'number', 12 | defaultValue: 0.2, 13 | optional: false, 14 | min: 0.01, 15 | description: 'length for square / diameter for circle' 16 | }, 17 | h: { // height in meters 18 | type: 'number', 19 | defaultValue: 2.4, 20 | optional: false, 21 | min: 0.01, 22 | description: 'height' 23 | }, 24 | shape: { 25 | type: 'string', 26 | defaultValue: 'square', 27 | optional: false, 28 | min: 0.01, 29 | possibleValues: ['square', 'circle'], 30 | description: 'column contour' 31 | } 32 | }, 33 | childrenTypes: [], 34 | parentTypes: [ 35 | 'level', 36 | 'group' 37 | ], 38 | aframeComponent: { 39 | name: 'io3d-column' 40 | } 41 | } -------------------------------------------------------------------------------- /src/storage/get.js: -------------------------------------------------------------------------------- 1 | import runtime from '../core/runtime' 2 | import configs from '../core/configs' 3 | import loadData3d from '../utils/data3d/load' 4 | import fetch from '../utils/io/fetch' 5 | import getUrlFromStorageId from './get-url-from-id.js' 6 | 7 | // main 8 | 9 | export default function getFromStorage (storageId, options) { 10 | 11 | // WIP: for now, assume that this is only being used for data3d 12 | options = options || {} 13 | options.type = options.type || 'data3d' // TODO: support more types 14 | var queueName = options.queueName 15 | var loadingQueuePrefix = options.loadingQueuePrefix 16 | 17 | switch(options.type) { 18 | case 'json': 19 | // do not use queue for generic JSON requests 20 | return fetch(getUrlFromStorageId(storageId, options)).then(function(response) { return response.json() }) 21 | break 22 | default: 23 | return loadData3d(getUrlFromStorageId(storageId), { 24 | queueName: queueName, 25 | loadingQueuePrefix: loadingQueuePrefix 26 | }) 27 | break 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /tasks/preamble.js: -------------------------------------------------------------------------------- 1 | const packageInfo = require('../package.json') 2 | const moment = require('moment') 3 | const execSync = require('child_process').execSync 4 | 5 | const date = moment().format('YYYY/MM/DD HH:mm') 6 | const gitBranchName = process.env.TRAVIS_BRANCH || execSync(`git rev-parse --abbrev-ref HEAD`).toString('utf8').replace('\n', '') 7 | const gitCommitSha1 = execSync(`git rev-parse HEAD`).toString('utf8').replace('\n', '') 8 | 9 | const text = `/** 10 | * @preserve 11 | * @name ${packageInfo.name} 12 | * @version ${packageInfo.version} 13 | * @date ${date} 14 | * @branch ${gitBranchName} 15 | * @commit ${gitCommitSha1} 16 | * @description ${packageInfo.description} 17 | * @see ${packageInfo.homepage} 18 | * @tutorial https://github.com/${packageInfo.repository} 19 | * @author ${packageInfo.author.name} <${packageInfo.author.email}> (${packageInfo.author.url}) 20 | * @license ${packageInfo.license} 21 | */ 22 | ` 23 | 24 | module.exports = { 25 | text: text, 26 | date: date, 27 | gitBranchName: gitBranchName, 28 | gitCommitSha1: gitCommitSha1 29 | } -------------------------------------------------------------------------------- /src/utils/data3d/get-texture-urls.js: -------------------------------------------------------------------------------- 1 | export default function getTextureUrls (data3d) { 2 | var materialUrls = [] 3 | Object.keys(data3d.materials).forEach(function cacheMaterial(materialKey) { 4 | var material = data3d.materials[materialKey] 5 | if (material.mapDiffuse) materialUrls.push(material.mapDiffuse) 6 | if (material.mapDiffusePreview) materialUrls.push(material.mapDiffusePreview) 7 | 8 | if (material.mapNormal) materialUrls.push(material.mapNormal) 9 | if (material.mapNormalPreview) materialUrls.push(material.mapNormalPreview) 10 | 11 | if (material.mapSpecular) materialUrls.push(material.mapSpecular) 12 | if (material.mapSpecularPreview) materialUrls.push(material.mapSpecularPreview) 13 | 14 | if (material.mapAlpha) materialUrls.push(material.mapAlpha) 15 | if (material.mapAlphaPreview) materialUrls.push(material.mapAlphaPreview) 16 | 17 | if (material.mapLight) materialUrls.push(material.mapLight) 18 | if (material.mapLightPreview) materialUrls.push(material.mapLightPreview) 19 | }) 20 | 21 | return materialUrls 22 | } 23 | -------------------------------------------------------------------------------- /src/scene/structure/normalize.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from './apply-defaults.js' 2 | import removeUnknown from './remove-unknown.js' 3 | 4 | export default function normalizeSceneStructure(elements3d) { 5 | 6 | // model structure can be a sole element or array of element 7 | // make sure we return the same type 8 | var inputIsArray = Array.isArray(elements3d) 9 | // start recursive validation 10 | var normalizedSceneStructure = normalizeElements3d(inputIsArray ? elements3d : [elements3d]) 11 | 12 | return Promise.resolve(inputIsArray ? normalizedSceneStructure : normalizedSceneStructure[0]) 13 | } 14 | 15 | function normalizeElements3d(input) { 16 | return input.map(function(element3d) { 17 | element3d = removeUnknown(element3d) 18 | // recursive parsing through scene structure 19 | if (element3d && element3d.children && element3d.children.length) { 20 | element3d.children = normalizeElements3d(element3d.children) 21 | } 22 | return applyDefaults(element3d) 23 | }).filter(function(element3d) { 24 | return element3d !== undefined 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /test/furniture/search.js: -------------------------------------------------------------------------------- 1 | import search from '../../src/furniture/search' 2 | import getInfo from '../../src/furniture/get-info' 3 | import get from '../../src/furniture/get' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../src/core/runtime.js', () => ({isBrowser: false, isNode: true, require: require})) 7 | 8 | test('Furniture: search for chairs', async () => { 9 | const result = await search('chair') 10 | expect(result.length).toBeGreaterThan(10) 11 | }) 12 | 13 | test('Furniture: search for chairs - bad formatted', async () => { 14 | const result = await search(' -generic tags:chair task categories:office ') 15 | expect(result.length).toBeGreaterThan(10) 16 | }) 17 | 18 | test('Furniture: get name from specific chair', async () => { 19 | const result = await getInfo('097f03fe-1947-40ee-a176-45106c51460f') 20 | expect(result.name).toBe('Drop') 21 | }) 22 | 23 | test('Furniture: get data from specific chair', async () => { 24 | const result = await get('097f03fe-1947-40ee-a176-45106c51460f') 25 | expect(result.info.name).toBe('Drop') 26 | }) -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/floor.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import floor from '../../../../src/scene/structure/parametric-objects/floor' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true, require: require})) 7 | 8 | test('get floor data3d', async function() { 9 | const attributes = applyDefaults({type: 'floor'}) 10 | const data3d = await floor(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual([ 'top', 'sides', 'ceiling' ]) 14 | expect(data3d.meshes.top.positions).toBeDefined() 15 | expect(data3d.meshes.top.uvs).toBeDefined() 16 | expect(data3d.meshes.top.normals).toBeDefined() 17 | // check for valid vertices 18 | expect(data3d.meshes.top.positions.every(a => !isNaN(a))).toBeTruthy() 19 | expect(data3d.meshes.sides.positions.every(a => !isNaN(a))).toBeTruthy() 20 | expect(data3d.meshes.ceiling.positions.every(a => !isNaN(a))).toBeTruthy() 21 | }); 22 | -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/polyfloor.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import polyfloor from '../../../../src/scene/structure/parametric-objects/polyfloor' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true})) 7 | 8 | test('get polyfloor data3d', async function() { 9 | const attributes = applyDefaults({type: 'polyfloor'}) 10 | const data3d = await polyfloor(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual([ 'top', 'sides', 'ceiling' ]) 14 | expect(data3d.meshes.top.positions).toBeDefined() 15 | expect(data3d.meshes.top.uvs).toBeDefined() 16 | expect(data3d.meshes.top.normals).toBeDefined() 17 | // check for valid vertices 18 | expect(data3d.meshes.top.positions.every(a => !isNaN(a))).toBeTruthy() 19 | expect(data3d.meshes.sides.positions.every(a => !isNaN(a))).toBeTruthy() 20 | expect(data3d.meshes.ceiling.positions.every(a => !isNaN(a))).toBeTruthy() 21 | }); 22 | -------------------------------------------------------------------------------- /tasks/jshint-configs.js: -------------------------------------------------------------------------------- 1 | // current configs guide lines: 2 | // https://github.com/archilogic-com/3dio-js/issues/64 3 | // docs: 4 | // https://github.com/jshint/jshint/blob/master/src/messages.js 5 | // https://gist.github.com/haschek/2595796 6 | 7 | module.exports = { 8 | esversion: 6, 9 | undef: true, 10 | unused: false, 11 | asi: true, // Missing semicolon 12 | eqeqeq: false, 13 | '-W041': false, 14 | '-W002': false, 15 | '-W080': false, 16 | '-W032': false, 17 | '-W030': false, 18 | '-W046': false, 19 | '-W008': false, 20 | '-W083': false, 21 | '-W084': false, 22 | '-W054': false, 23 | '-W009': false, 24 | '-W010': false, 25 | '-W047': false, 26 | '-W018': false, 27 | '-W027': false, 28 | '-W055': false, 29 | '-W086': false, 30 | laxcomma : true, 31 | laxbreak : true, 32 | sub:true, 33 | shadow:true, 34 | browser: true, 35 | node: true, 36 | devel: true, 37 | validthis: true, 38 | globals: { 39 | 'escape': true, 40 | 'THREE': true, 41 | 'AFRAME': true, 42 | 'io3d': true, 43 | 'GIT_BRANCH': true, 44 | 'GIT_COMMIT': true, 45 | 'BUILD_DATE': true 46 | } 47 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 archilogic.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples-browser/staging/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | input { 7 | height: 30px; 8 | font-size: 16px; 9 | padding: 0px 12px; 10 | outline: none; 11 | border: 0; 12 | font-weight: bold; 13 | border-bottom: 2px solid #333; 14 | margin: 0 10px 20px 0; 15 | } 16 | button { 17 | outline: none; 18 | height: 32px; 19 | font-size: 16px; 20 | padding: 7px 12px; 21 | border: solid 2px black; 22 | background: transparent; 23 | font-weight: bold; 24 | cursor: pointer; 25 | margin: 0 10px 20px 0; 26 | } 27 | button:hover { 28 | background: #333; 29 | color: white; 30 | } 31 | 32 | .controls { 33 | position: absolute; 34 | z-index:1000; top: 20px; left: 20px; 35 | } 36 | 37 | .btn { 38 | position: absolute; 39 | top: 20px; 40 | left: 20px; 41 | border: solid 2px black; 42 | outline: none; 43 | background: transparent; 44 | color: black; 45 | font-weight: bold; 46 | font-size: 20px; 47 | letter-spacing: 1px; 48 | padding: 5px 15px; 49 | cursor: pointer; 50 | z-index: 1000; 51 | } 52 | .btn:hover { 53 | background: #222; 54 | color: white; 55 | } -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/common/get-schema.js: -------------------------------------------------------------------------------- 1 | import getDefaultsByType from '../../../../scene/structure/validate/get-defaults-by-type' 2 | 3 | function getSchema (type) { 4 | // get valid params and default values for each type 5 | var validProps = getDefaultsByType(type) 6 | let schema = {} 7 | var params = validProps.params 8 | var propKeys = Object.keys(params) 9 | propKeys.forEach(function (key) { 10 | // skip location, children, material and id params 11 | if (params[key].skipInAframe || key === 'materials') return 12 | // map defaults to aframe schema convention 13 | schema[key] = {} 14 | // check schema definition for custom parsing rules 15 | if (params[key].parse) { 16 | schema[key].parse = params[key].parse 17 | // or set the preset type 18 | } else { 19 | schema[key].type = params[key].aframeType || params[key].type 20 | } 21 | if (params[key].defaultValue) schema[key].default = params[key].aframeDefault || params[key].defaultValue 22 | if (params[key].possibleValues) schema[key].oneOf = params[key].possibleValues 23 | }) 24 | return schema 25 | } 26 | 27 | export default getSchema -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/column.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import column from '../../../../src/scene/structure/parametric-objects/column' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true})) 7 | 8 | test('get column data3d', async function() { 9 | const attributes = applyDefaults({type: 'column'}) 10 | const data3d = await column(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual([ 'top', 'sides', 'bottom' ]) 14 | expect(data3d.meshes.top.positions).toBeDefined() 15 | expect(data3d.meshes.top.uvs).toBeDefined() 16 | expect(data3d.meshes.top.normals).toBeDefined() 17 | expect(data3d.meshes.sides.positions).toBeDefined() 18 | expect(data3d.meshes.sides.uvs).toBeDefined() 19 | expect(data3d.meshes.sides.normals).toBeDefined() 20 | 21 | // check for valid vertices 22 | expect(data3d.meshes.top.positions.every(a => !isNaN(a))).toBeTruthy() 23 | expect(data3d.meshes.sides.positions.every(a => !isNaN(a))).toBeTruthy() 24 | }); 25 | -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/wall.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import wall from '../../../../src/scene/structure/parametric-objects/wall' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true, require: require})) 7 | 8 | test('get wall data3d', async function() { 9 | const attributes = applyDefaults({type: 'wall'}) 10 | const data3d = await wall(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual([ 'front', 'back', 'top', 'base' ]) 14 | expect(data3d.meshes.front.positions).toBeDefined() 15 | expect(data3d.meshes.front.uvs).toBeDefined() 16 | expect(data3d.meshes.front.normals).toBeDefined() 17 | // check for valid vertices 18 | expect(data3d.meshes.front.positions.every(a => !isNaN(a))).toBeTruthy() 19 | expect(data3d.meshes.back.positions.every(a => !isNaN(a))).toBeTruthy() 20 | expect(data3d.meshes.top.positions.every(a => !isNaN(a))).toBeTruthy() 21 | expect(data3d.meshes.base.positions.every(a => !isNaN(a))).toBeTruthy() 22 | }); 23 | -------------------------------------------------------------------------------- /src/io3d.js: -------------------------------------------------------------------------------- 1 | import './core/polyfills.js' 2 | import './core/bootstrap.js' 3 | 4 | import runtime from './core/runtime.js' 5 | import configs from './core/configs.js' 6 | 7 | import aframe from './aframe.js' 8 | import furniture from './furniture.js' 9 | import staging from './staging.js' 10 | import storage from './storage.js' 11 | import scene from './scene.js' 12 | import floorPlan from './floor-plan.js' 13 | import light from './light.js' 14 | import modify from './modify.js' 15 | 16 | import utils from './utils.js' 17 | 18 | var io3d = { 19 | 20 | // APIs 21 | aFrame: aframe, // alias for legacy support 22 | aframe: aframe, 23 | furniture: furniture, 24 | staging:staging, 25 | storage: storage, 26 | scene: scene, 27 | floorPlan: floorPlan, 28 | light: light, 29 | modify: modify, 30 | 31 | // utils 32 | auth: utils.auth, 33 | ui: utils.ui, 34 | utils: utils, 35 | 36 | // core 37 | runtime: runtime, 38 | configs: configs, 39 | config: configs // alias 40 | 41 | } 42 | 43 | // create globals for main lib object in browser environment 44 | if (runtime.isBrowser) { 45 | window.io3d = io3d 46 | window.IO3D = io3d // upper case alias 47 | } 48 | 49 | export default io3d -------------------------------------------------------------------------------- /src/utils/auth/activate-account.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import getSession from './get-session.js' 3 | import log from 'js-logger' 4 | import Promise from 'bluebird' 5 | 6 | /** 7 | * Activate account by providing token and password 8 | * @function io3d.auth.activateAccount 9 | * @param {object} args 10 | * @param {string} args.token - Token string provided by server 11 | * @param {string} args.password - New password 12 | */ 13 | export default function activateAccount (args) { 14 | 15 | var credentials = { 16 | token: args.token, 17 | password: args.password 18 | } 19 | 20 | log.debug('Sending account activation request...') 21 | return callServices('User.activateAccount', { arguments: credentials }) 22 | .then(function onSuccess (result) { 23 | log.debug('Account activation request successful.') 24 | // update session 25 | return getSession().then(function(session){ 26 | if (session.isAuthenticated) log.debug('Activated user is logged in.') 27 | return session 28 | }) 29 | }, function onError (error) { 30 | log.debug('Account activation failed.', error) 31 | return Promise.reject(error) 32 | }) 33 | } -------------------------------------------------------------------------------- /src/aframe/component/gblock/vertex.glsl: -------------------------------------------------------------------------------- 1 | uniform mat4 u_modelViewMatrix; 2 | uniform mat4 u_projectionMatrix; 3 | uniform mat3 u_normalMatrix; 4 | 5 | attribute vec3 a_position; 6 | attribute vec3 a_normal; 7 | 8 | varying vec3 v_normal; 9 | varying vec3 v_position; 10 | varying vec3 v_binormal; 11 | varying vec3 v_tangent; 12 | 13 | void main() { 14 | vec3 objPosition = a_position; 15 | vec4 worldPosition = vec4(objPosition, 1.0); 16 | 17 | // Our object space has no rotation and no scale, so this is fine. 18 | v_normal = a_normal; 19 | v_position = worldPosition.xyz; 20 | // Looking for an arbitrary vector that isn't parallel to the normal. Avoiding axis directions should improve our chances. 21 | vec3 arbitraryVector = normalize(vec3(0.42, -0.21, 0.15)); 22 | vec3 alternateArbitraryVector = normalize(vec3(0.43, 1.5, 0.15)); 23 | // If arbitrary vector is parallel to the normal, choose a different one. 24 | v_tangent = normalize(abs(dot(v_normal, arbitraryVector)) < 1.0 ? cross(v_normal, arbitraryVector) : cross(v_normal, alternateArbitraryVector)); 25 | v_binormal = normalize(cross(v_normal, v_tangent)); 26 | 27 | gl_Position = u_projectionMatrix * u_modelViewMatrix * vec4(objPosition, 1.0); 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/auth/set-password.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import getSession from './get-session.js' 3 | import Promise from 'bluebird' 4 | import log from 'js-logger' 5 | 6 | /** 7 | * Set password for a specific user 8 | * @function io3d.auth.setPassword 9 | * @param {object} args 10 | * @param {string} args.token 11 | * @param {string} args.password 12 | */ 13 | export default function setPassword (args) { 14 | 15 | var credentials = { 16 | token: args.token, 17 | password: args.password 18 | } 19 | 20 | // log out first 21 | return callServices('User.logOut').then(function(){ 22 | 23 | // send sign up request 24 | log.debug('Setting new password ...') 25 | return callServices('User.changePassword', { 26 | token: credentials.token, 27 | oldPassword: null, 28 | newPassword: credentials.password 29 | }) 30 | 31 | }).then(function onSuccess(result) { 32 | 33 | // success 34 | log.debug('API: setting password successful.') 35 | return getSession() 36 | 37 | }, function onError(error){ 38 | 39 | // denied 40 | log.debug('API: setting password failed.', error) 41 | return Promise.reject(error) 42 | 43 | }) 44 | 45 | } -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/door.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import door from '../../../../src/scene/structure/parametric-objects/door' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true})) 7 | 8 | test('get door data3d', async function() { 9 | const attributes = applyDefaults({type: 'door'}) 10 | const data3d = await door(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual(['frame', 'handle', 'leaf', 'threshold']) 14 | expect(data3d.meshes.frame.positions).toBeDefined() 15 | expect(data3d.meshes.frame.normals).toBeDefined() 16 | expect(data3d.meshes.handle.positions).toBeDefined() 17 | expect(data3d.meshes.handle.normals).toBeDefined() 18 | expect(data3d.meshes.leaf.positions).toBeDefined() 19 | expect(data3d.meshes.leaf.normals).toBeDefined() 20 | 21 | // check for valid vertices 22 | expect(data3d.meshes.frame.positions.every(a => !isNaN(a))).toBeTruthy() 23 | expect(data3d.meshes.handle.positions.every(a => !isNaN(a))).toBeTruthy() 24 | expect(data3d.meshes.leaf.positions.every(a => !isNaN(a))).toBeTruthy() 25 | }); 26 | -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/floor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'rectangular floor with optional ceiling', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 0, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | w: { // width in meters 11 | type: 'number', 12 | defaultValue: 4, 13 | optional: false, 14 | min: 0.01, 15 | description: 'width' 16 | }, 17 | h: { // height in meters 18 | type: 'number', 19 | defaultValue: 0.2, 20 | optional: false, 21 | min: 0.01, 22 | description: 'height' 23 | }, 24 | l: { // length in meters 25 | type: 'number', 26 | defaultValue: 4, 27 | optional: false, 28 | min: 0.01, 29 | description: 'length' 30 | }, 31 | hasCeiling: { // in meters 32 | type: 'boolean', 33 | defaultValue: true, 34 | optional: false, 35 | description: 'toggle ceiling' 36 | }, 37 | hCeiling: { // in meters 38 | type: 'number', 39 | defaultValue: 2.4, 40 | optional: false, 41 | description: 'ceiling height' 42 | } 43 | }, 44 | childrenTypes: [], 45 | parentTypes: ['level'], 46 | aframeComponent: { 47 | name: 'io3d-floor' 48 | } 49 | } -------------------------------------------------------------------------------- /src/utils/file/get-mime-type-from-filename.js: -------------------------------------------------------------------------------- 1 | var 2 | FALLBACK_MIME_TYPE = 'application/octet-stream', 3 | EXTENSION_TO_MIME_TYPE = { 4 | obj: 'text/plain', 5 | dds: 'application/octet-stream', 6 | dwg: 'application/acad', 7 | dxf: 'application/dxf', 8 | jpg: 'image/jpeg', 9 | jpeg: 'image/jpeg', 10 | png: 'image/png', 11 | gif: 'image/gif', 12 | txt: 'text/plain', 13 | log: 'text/plain', 14 | svg: 'svg+xml', 15 | html: 'text/html', 16 | htm: 'text/html', 17 | js: 'application/javascript', 18 | json: 'application/json', 19 | md: 'text/markdown', 20 | csv: 'text/csv', 21 | gz: 'application/x-gzip', 22 | gzip: 'application/x-gzip', 23 | zip:'application/x-zip', 24 | pdf: 'application/pdf', 25 | '3ds': 'application/x-3ds' 26 | } 27 | 28 | export default function getMimeTypeFromFileName (filename) { 29 | var 30 | result = FALLBACK_MIME_TYPE, 31 | extension 32 | 33 | // get extension if file has one 34 | if (filename.indexOf('.') > -1) { 35 | extension = filename.split('.').pop().toLowerCase() 36 | if (EXTENSION_TO_MIME_TYPE[extension]) { 37 | // set mime type if it exists in the map 38 | result = EXTENSION_TO_MIME_TYPE[extension] 39 | } 40 | } 41 | 42 | return result 43 | } -------------------------------------------------------------------------------- /examples-browser/staging/stage-room-ar/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | this demo has been moved to another repo 4 | 5 | repo: https://github.com/archilogic-com/web-xr-homestaging-ai 6 | 7 | demo: https://archilogic-com.github.io/web-xr-homestaging-ai/ 8 | 9 | --- 10 | 11 | # Home staging AR Demo 12 | 13 | This demo let's you draw a floor plan in AR 14 | 15 | ![](https://storage.3d.io/535e624259ee6b0200000484/2017-09-13_11-56-39_wW7wLF/draw-plan.gif) 16 | 17 | and then furnish it automatically. 18 | 19 | ![](https://storage.3d.io/535e624259ee6b0200000484/2017-09-13_11-42-23_XUM61N/home-staging-ai.gif) 20 | 21 | 22 | ## WebAR phone 23 | 24 | You'll need an WebAR enabled device
25 | Currently this limits your choices to: 26 | * Google Pixel 27 | * Samsung S8 28 | * iPhone with iOS 11 29 | 30 | Get started here to install WebARonARCore / WebARonARKit Browser: 31 | * Android: https://github.com/google-ar/WebARonARCore 32 | * iOS: https://github.com/google-ar/WebARonARKit 33 | 34 | ## Run the demo 35 | 36 | 1. Clone the repo 37 | 2. Get an publishable API Key from https://3d.io 38 | 3. Set it in app.js 39 | 4. build 3dio and run examples via: 40 | 41 | ``` 42 | npm run dev-browser 43 | ``` 44 | Navigate your phone ( using WebAR enabled browser ) to 45 | `:8080/examples-browser/staging/stage-room-ar/` 46 | -------------------------------------------------------------------------------- /src/utils/auth/get-session.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | import callServices from '../services/call.js' 3 | import normalizeSession from './common/normalize-session.js' 4 | import sessionStream from './session-stream.js' 5 | import Promise from 'bluebird' 6 | import log from 'js-logger' 7 | 8 | // init 9 | getSession().catch(function(error) { 10 | console.warn('Session info not available: ', error) 11 | }) 12 | 13 | // update session state every time when tab becomes visible 14 | if (runtime.isBrowser) { 15 | runtime.isFocused$.subscribe(function(isFocused){ 16 | if (isFocused) getSession() 17 | }) 18 | } 19 | 20 | // export 21 | 22 | /** 23 | * Get information about the current session. 24 | * @function io3d.auth.getSession 25 | */ 26 | export default function getSession () { 27 | log.debug('Sending API session request...') 28 | return callServices('User.getSession') 29 | .then(normalizeSession) 30 | .then(function onSuccess (session) { 31 | log.debug('API: session data:\n', session) 32 | // stream session object 33 | sessionStream.next(session) 34 | // return result 35 | return session 36 | }, function onError (error) { 37 | log.debug('API: error receiving session data.', error) 38 | return Promise.reject(error) 39 | }) 40 | } -------------------------------------------------------------------------------- /src/storage.js: -------------------------------------------------------------------------------- 1 | import put from './storage/put.js' 2 | import get from './storage/get.js' 3 | import getUrlFromStorageId from './storage/get-url-from-id.js' 4 | import getNoCdnUrlFromStorageId from './storage/get-no-cdn-url-from-id.js' 5 | import getStorageIdFromUrl from './storage/get-id-from-url.js' 6 | import importThreeObject from './storage/import-three-object.js' 7 | import importAframeElement from './storage/import-aframe-element.js' 8 | import modelExporter from './storage/model-exporter.js' 9 | 10 | var storage = { 11 | // low level 12 | get: get, 13 | put: put, 14 | // import 15 | importThreeObject: importThreeObject, 16 | importAframeElement: importAframeElement, 17 | // export 18 | export3ds: modelExporter.export3ds, 19 | exportBlend: modelExporter.exportBlend, 20 | exportDae: modelExporter.exportDae, 21 | exportFbx: modelExporter.exportFbx, 22 | exportObj: modelExporter.exportObj, 23 | exportDxf: modelExporter.exportDxf, 24 | // helpers 25 | getUrlFromStorageId: getUrlFromStorageId, 26 | getUrlFromId: getUrlFromStorageId, // alias 27 | getNoCdnUrlFromStorageId: getNoCdnUrlFromStorageId, 28 | getNoCdnUrlFromId: getNoCdnUrlFromStorageId, // alias 29 | getStorageIdFromUrl: getStorageIdFromUrl, 30 | getIdFromUrl: getStorageIdFromUrl // alias 31 | } 32 | 33 | export default storage 34 | -------------------------------------------------------------------------------- /tasks/dev-browser.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp') 2 | const build = require('./build') 3 | const chalk = require('chalk') 4 | const spawn = require('child_process').spawn 5 | const watch = require('gulp-watch') 6 | 7 | // tasks 8 | 9 | const runBrowserDevEnvironment = gulp.series( 10 | build, 11 | gulp.parallel( 12 | // watch source folder -> rebuild 13 | function watchSource () { 14 | watch('src/**/*', build).on('error', function(e){ 15 | console.error('ERROR: ',e) 16 | }) 17 | }, 18 | // watch build folder -> update browser 19 | runLiteServer 20 | ) 21 | ) 22 | 23 | function runLiteServer () { 24 | return new Promise((resolve, reject) => { 25 | const ls = spawn('lite-server', ['-c', 'tasks/lite-server.config.js'], {shell: true}) 26 | ls.stdout.on('data', (data) => { 27 | console.log(`lite-server: ${data}`) 28 | }) 29 | ls.stderr.on('data', (data) => { 30 | console.error(chalk.red(`lite-server: ${data}`)) 31 | }) 32 | ls.on('close', (code) => { 33 | if (code === 0) { 34 | console.log(`lite-server: closed`) 35 | resolve() 36 | } else { 37 | throw new Error(`lite-server: exited with code ${code}`) 38 | reject() 39 | } 40 | }) 41 | }) 42 | } 43 | 44 | // export 45 | 46 | module.exports = runBrowserDevEnvironment -------------------------------------------------------------------------------- /examples-browser/storage-app/style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Droid+Sans+Mono'); 2 | @import url('https://fonts.googleapis.com/css?family=Satisfy'); 3 | 4 | * { 5 | box-sizing: border-box; 6 | } 7 | 8 | html { 9 | position: relative; 10 | height: 100%; 11 | } 12 | 13 | body { 14 | 15 | color: white; 16 | font-family: 'Droid Sans Mono', monospace; 17 | font-size: 12px; 18 | line-height: 1.6; 19 | 20 | position: absolute; 21 | height: 100%; 22 | width: 100%; 23 | 24 | padding: 30px; 25 | margin: 0; 26 | overflow: hidden; 27 | 28 | background-repeat: no-repeat; 29 | background-attachment: fixed; 30 | background: #6c6bc9; /* fallback for old browsers */ 31 | background: -webkit-linear-gradient(-45deg, #6a5ea5, #6c6bc9); /* Chrome 10-25, Safari 5.1-6 */ 32 | background: linear-gradient(140deg, #594a96, #6c6bc5); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ 33 | 34 | } 35 | 36 | #file-drop-box { 37 | position: absolute; 38 | top: 60px; 39 | left: 60px; 40 | height: 300px; 41 | width: 300px; 42 | padding-top: 120px; 43 | text-align: center; 44 | border: 1px dotted white; 45 | background: rgba(255,255,255,0.1); 46 | } 47 | .file-drop-box-dragover { 48 | background: rgba(0,255,0,0.3) !important; 49 | } -------------------------------------------------------------------------------- /src/utils/auth/sign-up.js: -------------------------------------------------------------------------------- 1 | import callServices from '../services/call.js' 2 | import uuid from '../uuid.js' 3 | import Promise from 'bluebird' 4 | import log from 'js-logger' 5 | 6 | /** 7 | * Sign up: Create a new user 8 | * @function io3d.auth.signUp 9 | * @param {object} args 10 | * @param {string} args.email 11 | * @param {string} args.password (optional) 12 | * @param {string} args.emailOptIn (optional) 13 | */ 14 | export default function signUp (args) { 15 | 16 | var credentials = { 17 | email: args.email, 18 | password: args.password || uuid.generate(), 19 | accountSetup: '3dio', 20 | emailOptIn: args.emailOptIn || false 21 | } 22 | 23 | // log out first 24 | return callServices('User.logOut').then(function(){ 25 | 26 | // send sign up request 27 | log.debug('Sending API sign up request for email "' + credentials.email + '" ...') 28 | return callServices('User.create', credentials) 29 | 30 | }).then(function onSignUpSuccess(result) { 31 | 32 | // success 33 | log.debug('API: User sign up with email "' + credentials.email + '" was successful.') 34 | return Promise.resolve() 35 | 36 | }, function onSignUpError(error){ 37 | 38 | // denied 39 | log.debug('API: Could not sign up using email "' + credentials.email + '".', error) 40 | return Promise.reject(error) 41 | 42 | }) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /test/scene/structure/parametric-objects/kitchen.js: -------------------------------------------------------------------------------- 1 | import applyDefaults from '../../../../src/scene/structure/apply-defaults.js' 2 | import kitchen from '../../../../src/scene/structure/parametric-objects/kitchen' 3 | import { isNaN } from 'lodash' 4 | 5 | // mock runtime module to prevent from tests blowing up 6 | jest.mock('../../../../src/core/runtime.js', () => ({isBrowser: false, isNode: true, require: require })) 7 | 8 | test('get kitchen data3d', async function() { 9 | const attributes = applyDefaults({type: 'kitchen'}) 10 | const data3d = await kitchen(attributes) 11 | 12 | // mesh names 13 | expect(Object.keys(data3d.meshes)).toEqual(['sink_0', 'kitchen', 'counter', 'oven', 'cooktop', 'extractor', 'microwave']) 14 | expect(data3d.meshes.kitchen.positions).toBeDefined() 15 | expect(data3d.meshes.kitchen.uvs).toBeDefined() 16 | // check for valid vertices 17 | expect(data3d.meshes.sink_0.positions.every(a => !isNaN(a))).toBeTruthy() 18 | expect(data3d.meshes.kitchen.positions.every(a => !isNaN(a))).toBeTruthy() 19 | expect(data3d.meshes.counter.positions.every(a => !isNaN(a))).toBeTruthy() 20 | expect(data3d.meshes.oven.positions.every(a => !isNaN(a))).toBeTruthy() 21 | expect(data3d.meshes.cooktop.positions.every(a => !isNaN(a))).toBeTruthy() 22 | expect(data3d.meshes.extractor.positions.every(a => !isNaN(a))).toBeTruthy() 23 | }); 24 | -------------------------------------------------------------------------------- /src/utils/data3d/get-texture-keys.js: -------------------------------------------------------------------------------- 1 | import traverseData3d from './traverse.js' 2 | import textureAttributes from './texture-attributes.js' 3 | 4 | // main 5 | 6 | export default function getTextureKeys(data3d, options) { 7 | // API 8 | var options = options || {} 9 | var filter = options.filter 10 | 11 | // internals 12 | var cache = {} 13 | 14 | // iterate over materials 15 | traverseData3d.materials(data3d, function(material) { 16 | var filteredResult, attrName, type, format, textureKey 17 | // iterate over texture types 18 | for (var i = 0, l = textureAttributes.names.length; i < l; i++) { 19 | attrName = textureAttributes.names[i] 20 | textureKey = material[attrName] 21 | 22 | // material does not contain this type of texture - continue to next one 23 | if (!textureKey) continue 24 | 25 | // apply filter function if specified in options 26 | if (filter) { 27 | // provide info on type and format of texture to the filter function 28 | type = textureAttributes.nameToType[attrName] 29 | format = textureAttributes.nameToFormat[attrName] 30 | textureKey = filter(textureKey, type, format, material, data3d) 31 | } 32 | 33 | // filter function might return false in order to exclude textures from the results 34 | if (textureKey) cache[textureKey] = true 35 | } 36 | }) 37 | 38 | return Object.keys(cache) 39 | } -------------------------------------------------------------------------------- /src/light/bake.js: -------------------------------------------------------------------------------- 1 | import runtime from '../core/runtime.js' 2 | import callServices from '../utils/services/call.js' 3 | import whenDone from '../utils/processing/when-done.js' 4 | 5 | // main 6 | function bakeStage(stage) { 7 | return function bake(storageId, options) { 8 | // API 9 | options = options || {} 10 | 11 | // Optional bake parameters for API call 12 | var bakeSettings = { sunDirection: options.sunDirection || [ 0.75, -0.48, -0.46 ] } 13 | if (options.lightMapCount) bakeSettings.lightMapCount = options.lightMapCount 14 | if (options.samples) bakeSettings.samples = options.samples 15 | 16 | // internals 17 | // TODO: reimplement send "exportable" textures to bake 18 | var assetStorageIds = [] 19 | // TODO: reimplement caching mechanism on server side 20 | var cacheKey = null 21 | 22 | var bakeParams = { 23 | method: 'bake'.concat('.', stage), 24 | params: { 25 | inputFileKey: storageId, 26 | inputAssetKeys: assetStorageIds, 27 | settings: JSON.stringify(bakeSettings) 28 | } 29 | } 30 | 31 | if (cacheKey) bakeParams.params.cacheKey = cacheKey 32 | if (assetStorageIds.length > 0) bakeParams.params.inputAssetKeys = assetStorageIds 33 | 34 | return callServices('Processing.task.enqueue', bakeParams) 35 | } 36 | } 37 | 38 | // expose API 39 | 40 | export default { 41 | bakePreview: bakeStage('preview'), 42 | bakeRegular: bakeStage('regular') 43 | } 44 | -------------------------------------------------------------------------------- /examples-browser/api-playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | api playground 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 |

API playground

31 | 32 |
33 | Examples 34 |
35 |
36 | 37 |
38 |
39 | Code 40 |
41 |
42 | 43 |
44 | Console 45 |
46 |
47 | 48 | 49 | -------------------------------------------------------------------------------- /src/storage/get-url-from-id.js: -------------------------------------------------------------------------------- 1 | import configs from '../core/configs.js' 2 | 3 | // constants 4 | var IS_URL = new RegExp('^http:\\/\\/.*$|^https:\\/\\/.*$') 5 | var ID_TO_URL_CACHE = {} 6 | 7 | // main 8 | export default function getUrlFromStorageId (storageId, options) { 9 | 10 | // API 11 | options = options || {} 12 | var cdn = options.cdn !== undefined ? options.cdn : true 13 | var encode = options.encode !== undefined ? options.encode : true 14 | 15 | // check cache 16 | if (ID_TO_URL_CACHE[storageId + cdn + encode]) { 17 | return ID_TO_URL_CACHE[storageId + cdn + encode] 18 | } 19 | 20 | // check if storageId is URL already 21 | if (IS_URL.test(storageId)) { 22 | // add to cache 23 | ID_TO_URL_CACHE[ storageId + cdn + encode ] = storageId 24 | // return URL 25 | return storageId 26 | } 27 | 28 | // internals 29 | var processedStorageId = storageId 30 | 31 | // remove leading slash 32 | var startsWithSlash = /^\/(.*)$/.exec(processedStorageId) 33 | if (startsWithSlash) { 34 | processedStorageId = startsWithSlash[1] 35 | } 36 | 37 | // encode storageId if containig special chars 38 | if (encode && !/^[\.\-\_\/a-zA-Z0-9]+$/.test(processedStorageId)) { 39 | processedStorageId = encodeURIComponent(processedStorageId) 40 | } 41 | 42 | // compose url 43 | var url = 'https://' + (cdn ? configs.storageDomain : configs.storageDomainNoCdn) + '/' + processedStorageId 44 | 45 | // add to cache 46 | ID_TO_URL_CACHE[ storageId + cdn + encode ] = url 47 | 48 | return url 49 | } 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please read the [contribution guidelines](/.github/CONTRIBUTING.md) before submitting an Issue. 2 | 3 | This form is for bug reports and feature requests only. 4 | If you have a question on how to use 3D.io or the products, please direct these to [Stack Overflow](https://stackoverflow.com/questions/tagged/aframe%20and%203d.io%20or%20archilogic). It's the best way to reach us. 5 | 6 | ### Bug Report Template 7 | 8 | **How to Reproduce** 9 | - Describe in a few steps the actions to make the bug appear. 10 | - Provide a code snippet or a URL of with an example ([App Creator](https://appcreator.3d.io) or [glitch](https://glitch.com/)) 11 | 12 | **Actual Result** 13 | - Describe the bug in a few words. 14 | - Attach a screen shot of the graphical or console error if these are relevant. 15 | 16 | **Expected Result** 17 | - Describe what you would expect to happen. 18 | 19 | **Notes** 20 | - Share relevant details to your setup 21 | - Which OS is affected? 22 | - Which Browsers are affected? 23 | - What devices are affected? 24 | - What build are you using? 25 | 26 | ### Feature Request Template 27 | Please note by far the quickest way to get a new feature is to file a Pull Request. 28 | 29 | **Scope** 30 | - Describe the feature as good as possible 31 | - Explain why the feature is useful for the community 32 | 33 | **Behaviour** 34 | - What is the expected behaviour? 35 | - If possible, use sketches or code snippets to visualize the feature. 36 | 37 | **Implications** 38 | - Does the feature introduce breaking changes 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/utils/data3d/traverse.js: -------------------------------------------------------------------------------- 1 | function traverseData3d(data3d, callback) { 2 | 3 | callback(data3d) 4 | 5 | if (data3d.children) for (var i=0, l=data3d.children.length; i { 35 | removeEmptyMeshes(data3d.meshes) 36 | 37 | // create new one 38 | this_.mesh = new THREE.Object3D() 39 | this_.data3dView = new io3d.aFrame.three.Data3dView({parent: this_.mesh}) 40 | 41 | // update view 42 | this_.data3dView.set(data3d) 43 | this_.el.setObject3D('mesh', this_.mesh) 44 | // emit event 45 | this_.el.emit('mesh-updated') 46 | }) 47 | }, 48 | 49 | remove: function () { 50 | if (this.data3dView) { 51 | this.data3dView.destroy() 52 | this.data3dView = null 53 | } 54 | if (this.mesh) { 55 | this.el.removeObject3D('mesh') 56 | this.mesh = null 57 | } 58 | }, 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/poll.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird' 2 | 3 | export default function poll(callback, options) { 4 | 5 | // API 6 | options = options || {} 7 | var timeout = options.timeout || 10 * 60 * 1000 8 | var minInterval = options.minInterval || 1000 9 | var maxInterval = options.maxInterval || 5000 10 | var intervalIncreaseFactor = options.intervalIncreaseFactor || 1.05 11 | 12 | return new Promise(function( fulfill, reject, onCancel ){ 13 | var flags = { isCancelled: false } 14 | // cancellation is supported in bluebird version > 3.x 15 | // enable cancellation in Promise.config as it is off by default 16 | if (onCancel) onCancel(function(){ flags.isCancelled = true; }) 17 | // start recursive poll 18 | recursivePoll(callback, fulfill, reject, minInterval, maxInterval, intervalIncreaseFactor, 0, timeout, flags) 19 | }) 20 | 21 | } 22 | 23 | // helper 24 | 25 | function recursivePoll(callback, fulfill, reject, interval, maxInterval, intervalIncreaseFactor, timeElapsed, timeout, flags) { 26 | 27 | // return if poll has been cancelled in meanwhile 28 | if (flags.isCancelled) return reject('Poll request has been cancelled') 29 | // increase interval 30 | if (interval < maxInterval) interval *= intervalIncreaseFactor 31 | // check timeout 32 | if (timeElapsed > timeout) return reject('Poll request timed out') 33 | // count time 34 | timeElapsed += interval 35 | // call 36 | callback(fulfill, reject, function next() { 37 | setTimeout(function(){ 38 | recursivePoll(callback, fulfill, reject, interval, maxInterval, intervalIncreaseFactor, timeElapsed, timeout, flags) 39 | }, interval) 40 | }) 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/scene.js: -------------------------------------------------------------------------------- 1 | import getStructure from './scene/structure/get.js' 2 | import getAframeElements from './scene/get-aframe-elements.js' 3 | import getViewerUrl from './scene/get-viewer-url.js' 4 | import validateSceneStructure from './scene/structure/validate.js' 5 | import normalizeSceneStructure from './scene/structure/normalize.js' 6 | import getAframeElementsFromSceneStructure from './scene/structure/to-aframe-elements.js' 7 | import getSceneStructureFromAframeElements from './scene/structure/from-aframe-elements.js' 8 | import exportSvg from './scene/export-svg.js' 9 | import snapWalls from './scene/structure/utils/snap-walls.js' 10 | 11 | var scene = { 12 | getStructure: getStructure, 13 | getHtml: getHtml, 14 | getAframeElements: getAframeElements, 15 | getViewerUrl: getViewerUrl, 16 | validateSceneStructure: validateSceneStructure, 17 | normalizeSceneStructure: normalizeSceneStructure, 18 | getHtmlFromSceneStructure: getHtmlFromSceneStructure, 19 | getAframeElementsFromSceneStructure: getAframeElementsFromSceneStructure, 20 | getSceneStructureFromAframeElements: getSceneStructureFromAframeElements, 21 | snapWalls: snapWalls, 22 | exportSvg: exportSvg 23 | } 24 | 25 | function getHtml() { 26 | console.warn('io3d.scene.getHtml will be removed soon please use io3d.scene.getAframeElements') 27 | return getAframeElements.apply( getAframeElements, arguments ) 28 | } 29 | 30 | function getHtmlFromSceneStructure() { 31 | console.warn('io3d.scene.getHtmlFromSceneStructure will be removed soon please use io3d.scene.getAframeElementsFromSceneStructure') 32 | return getAframeElementsFromSceneStructure.apply( getAframeElementsFromSceneStructure, arguments ) 33 | } 34 | 35 | export default scene -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/kitchen.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import Promise from 'bluebird' 4 | import getSchema from './common/get-schema.js' 5 | import updateSchema from './common/update-schema.js' 6 | import cloneDeep from 'lodash/cloneDeep' 7 | import getKitchenData3d from '../../../scene/structure/parametric-objects/kitchen' 8 | import dataToMaterials from './common/data-to-materials' 9 | import removeEmptyMeshes from './common/remove-empty-meshes' 10 | 11 | export default { 12 | 13 | schema: getSchema('kitchen'), 14 | 15 | init: function () {}, 16 | 17 | updateSchema: updateSchema, 18 | 19 | update: function (oldData) { 20 | var this_ = this 21 | var data = this_.data 22 | 23 | // remove old mesh 24 | this.remove() 25 | 26 | let attributes = cloneDeep(data) 27 | 28 | attributes.materials = dataToMaterials(data) 29 | 30 | // get meshes and materials 31 | // promise base because it loads external meshes 32 | getKitchenData3d(attributes) 33 | .then(data3d => { 34 | removeEmptyMeshes(data3d.meshes) 35 | 36 | // create new one 37 | this_.mesh = new THREE.Object3D() 38 | this_.data3dView = new io3d.aFrame.three.Data3dView({parent: this_.mesh}) 39 | 40 | // update view 41 | this_.data3dView.set(data3d) 42 | this_.el.setObject3D('mesh', this_.mesh) 43 | // emit event 44 | this_.el.emit('mesh-updated') 45 | }) 46 | }, 47 | 48 | remove: function () { 49 | if (this.data3dView) { 50 | this.data3dView.destroy() 51 | this.data3dView = null 52 | } 53 | if (this.mesh) { 54 | this.el.removeObject3D('mesh') 55 | this.mesh = null 56 | } 57 | }, 58 | 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/data3d/texture-attributes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | names: [ 4 | 'mapDiffuse', 5 | 'mapDiffusePreview', 6 | 'mapDiffuseSource', 7 | // specular 8 | 'mapSpecular', 9 | 'mapSpecularPreview', 10 | 'mapSpecularSource', 11 | // normal 12 | 'mapNormal', 13 | 'mapNormalPreview', 14 | 'mapNormalSource', 15 | // alpha 16 | 'mapAlpha', 17 | 'mapAlphaPreview', 18 | 'mapAlphaSource', 19 | // lightmap 20 | 'mapLight', 21 | 'mapLightPreview', 22 | 'mapLightSource' 23 | ], 24 | 25 | nameToType: { 26 | // diffuse 27 | mapDiffuse: 'diffuse', 28 | mapDiffusePreview: 'diffuse', 29 | mapDiffuseSource: 'diffuse', 30 | // specular 31 | mapSpecular: 'specular', 32 | mapSpecularPreview: 'specular', 33 | mapSpecularSource: 'specular', 34 | // normal 35 | mapNormal: 'normal', 36 | mapNormalPreview: 'normal', 37 | mapNormalSource: 'normal', 38 | // alpha 39 | mapAlpha: 'alpha', 40 | mapAlphaPreview: 'alpha', 41 | mapAlphaSource: 'alpha', 42 | // light 43 | mapLight: 'light', 44 | mapLightPreview: 'light', 45 | mapLightSource: 'light', 46 | }, 47 | 48 | nameToFormat: { 49 | // loRes 50 | mapDiffusePreview: 'loRes', 51 | mapSpecularPreview: 'loRes', 52 | mapNormalPreview: 'loRes', 53 | mapAlphaPreview: 'loRes', 54 | mapLightPreview: 'loRes', 55 | // source 56 | mapDiffuseSource: 'source', 57 | mapSpecularSource: 'source', 58 | mapNormalSource: 'source', 59 | mapAlphaSource: 'source', 60 | mapLightSource: 'source', 61 | // dds 62 | mapDiffuse: 'dds', 63 | mapSpecular: 'dds', 64 | mapNormal: 'dds', 65 | mapAlpha: 'dds', 66 | mapLight: 'dds' 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /src/aframe/three/data3d-view/set-material/fetch-texture.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird' 2 | import fetchDdsTexture from './fetch-texture/fetch-dds-texture.js' 3 | import fetchImageTexture from './fetch-texture/fetch-image-texture.js' 4 | import queueManager from '../../../../utils/io/common/queue-manager.js' 5 | import PromiseCache from '../../../../utils/promise-cache.js' 6 | 7 | var cache = new PromiseCache() 8 | 9 | var fetchTextureByType = { 10 | '.dds': fetchDdsTexture, 11 | '.jpg': fetchImageTexture, 12 | '.jpeg': fetchImageTexture, 13 | '.jpe': fetchImageTexture, 14 | '.png': fetchImageTexture, 15 | '.gif': fetchImageTexture, 16 | '.svg': fetchImageTexture 17 | } 18 | 19 | export default function fetchTexture (url, queueName) { 20 | 21 | // internals 22 | var cacheKey = url 23 | 24 | // try cache 25 | var promiseFromCache = cache.get(cacheKey) 26 | if (promiseFromCache) return promiseFromCache 27 | 28 | // get file extension 29 | var extSearch = url.match(/\.[A-Za-z]+(?=\?|$)/i) 30 | var type = extSearch ? extSearch[0].toLowerCase() : '.jpg' 31 | 32 | if (!fetchTextureByType[type]) { 33 | // unknown texture type. fallback to JPG 34 | console.warn('Unknown texture type ' + type + '. Trying to load as JPG.') 35 | type = '.jpg' 36 | } 37 | 38 | var promise = queueManager.enqueue(queueName, url).then(function(){ 39 | 40 | return fetchTextureByType[type](url) 41 | 42 | }).then(function(texture){ 43 | 44 | queueManager.dequeue(queueName, url) 45 | return texture 46 | 47 | }).catch(function (error) { 48 | 49 | queueManager.dequeue(queueName, url) 50 | return Promise.reject(error) 51 | 52 | }) 53 | 54 | // add to cache 55 | cache.add(cacheKey, promise) 56 | 57 | return promise 58 | 59 | } -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/wall.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'structural wall, can contains doors and windows', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 0, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | w: { // width in meters 11 | type: 'number', 12 | defaultValue: 0.15, 13 | optional: false, 14 | min: 0.01, 15 | description: 'width' 16 | }, 17 | h: { // height in meters 18 | type: 'number', 19 | defaultValue: 2.4, 20 | optional: false, 21 | min: 0.01, 22 | description: 'height' 23 | }, 24 | l: { // length in meters 25 | type: 'number', 26 | defaultValue: 1, 27 | optional: false, 28 | min: 0.01, 29 | description: 'length' 30 | }, 31 | controlLine: { 32 | type: 'string', 33 | defaultValue: 'back', 34 | optional: true, 35 | possibleValues: ['back', 'center', 'front'], 36 | description: 'relative position of the control line to the wall' 37 | }, 38 | baseHeight: { 39 | type: 'number', 40 | defaultValue: 0, 41 | optional: true, 42 | description: 'height of the baseboard' 43 | }, 44 | frontHasBase: { 45 | type: 'boolean', 46 | defaultValue: false, 47 | optional: true, 48 | description: 'show baseboard on the front' 49 | }, 50 | backHasBase: { 51 | type: 'boolean', 52 | defaultValue: false, 53 | optional: true, 54 | description: 'show baseboard on the back' 55 | } 56 | }, 57 | childrenTypes: [ 58 | 'window', 59 | 'door' 60 | ], 61 | parentTypes: [ 62 | 'level', 63 | 'group' 64 | ], 65 | aframeComponent: { 66 | name: 'io3d-wall' 67 | } 68 | } -------------------------------------------------------------------------------- /src/utils/ui.js: -------------------------------------------------------------------------------- 1 | import runtime from '../core/runtime.js' 2 | import css from './ui/less/style.less' 3 | import createMessageUi from './ui/create-message-ui.js' 4 | import createFileDropUi from './ui/create-file-drop-ui.js' 5 | import createSignUpUi from './ui/create-sign-up-ui.js' 6 | import createLogInUi from './ui/create-log-in-ui.js' 7 | import createLogOutUi from './ui/create-log-out-ui.js' 8 | import requestPasswordResetUi from './ui/create-password-reset-request-ui.js' 9 | import createDevDashboardUi from './ui/create-dev-dashboard-ui.js' 10 | import createPublishableApiKeysUi from './ui/create-publishable-api-keys-ui.js' 11 | import createSecretApiKeyUi from './ui/create-secret-api-key-ui.js' 12 | import createConfirmUi from './ui/create-confirm-ui.js' 13 | import createAlertUi from './ui/create-alert-ui.js' 14 | import createPromptUi from './ui/create-prompt-ui.js' 15 | 16 | // add css to page 17 | if (runtime.isBrowser) { 18 | var style = document.createElement('style') 19 | style.setAttribute('media', 'screen') 20 | style.appendChild(document.createTextNode(css)) 21 | document.head.appendChild(style) 22 | } 23 | 24 | // export 25 | 26 | var ui = { 27 | fileDrop: createFileDropUi, 28 | // authentication 29 | signUp: createSignUpUi, 30 | signup: createSignUpUi, // alias 31 | logIn: createLogInUi, 32 | login: createLogInUi, // alias 33 | logOut: createLogOutUi, 34 | logout: createLogOutUi, // alias 35 | requestPasswordReset: requestPasswordResetUi, 36 | devDashboard: createDevDashboardUi, 37 | publishableApiKeys: createPublishableApiKeysUi, 38 | secretApiKey: createSecretApiKeyUi, 39 | // messages 40 | message: createMessageUi, 41 | alert: createAlertUi, 42 | confirm: createConfirmUi, 43 | prompt: createPromptUi 44 | } 45 | 46 | export default ui -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/polyfloor.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'polygonal floor with optional ceiling', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 0, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | h: { // height in meters 11 | type: 'number', 12 | defaultValue: 0.2, 13 | optional: false, 14 | min: 0.01, 15 | description: 'height' 16 | }, 17 | polygon: { 18 | //type: 'array-with-arrays-with-numbers', 19 | type: 'array', 20 | // aframeType: 'string', 21 | defaultValue: [[1.5,1.5], [1.5,-1.5], [-1.5,-1.5], [-1.5,1.5]], 22 | aframeDefault: '[[1.5,1.5], [1.5,-1.5], [-1.5,-1.5], [-1.5,1.5]]', 23 | optional: false, 24 | description: 'outer polygon', 25 | parse: function(val) { 26 | if (!/^\[.+\]/.test(val)) { 27 | console.warn('invalid input for polyfloor polygon', val) 28 | return [[1.5,1.5], [1.5,-1.5], [-1.5,-1.5], [-1.5,1.5]] 29 | } 30 | return JSON.parse(val) 31 | } 32 | }, 33 | polygonHoles: { 34 | type: 'array', 35 | optional: true, 36 | description: 'polygon holes' 37 | }, 38 | hasCeiling: { // in meters 39 | type: 'boolean', 40 | defaultValue: true, 41 | optional: false, 42 | description: 'toggle ceiling' 43 | }, 44 | hCeiling: { // in meters 45 | type: 'number', 46 | defaultValue: 2.4, 47 | optional: false, 48 | description: 'ceiling height' 49 | }, 50 | usage: { // in meters 51 | type: 'string', 52 | optional: true 53 | } 54 | }, 55 | childrenTypes: [], 56 | parentTypes: ['level'], 57 | aframeComponent: { 58 | name: 'io3d-polyfloor' 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/auth.js: -------------------------------------------------------------------------------- 1 | import signUp from './auth/sign-up.js' 2 | import activateAccount from './auth/activate-account.js' 3 | import setPassword from './auth/set-password.js' 4 | import requestPasswordReset from './auth/request-password-reset.js' 5 | import resendActivationEmail from './auth/resend-activation-email.js' 6 | import logIn from './auth/log-in.js' 7 | import logOut from './auth/log-out.js' 8 | import getSecretApiKey from './auth/get-secret-api-key.js' 9 | import regenerateSecretApiKey from './auth/regenerate-secret-api-key.js' 10 | import generatePublishableApiKey from './auth/generate-publishable-api-key.js' 11 | import listPublishableApiKeys from './auth/list-publishable-api-keys.js' 12 | import updatePublishableApiKeyDomains from './auth/update-publishable-api-key-domains.js' 13 | import revokePublishableApiKey from './auth/revoke-publishable-api-key.js' 14 | import getSession from './auth/get-session.js' 15 | import session$ from './auth/session-stream.js' 16 | 17 | // export 18 | 19 | var auth = { 20 | // user 21 | getSession: getSession, 22 | session$: session$, 23 | signUp: signUp, 24 | signup: signUp, // alias 25 | logIn: logIn, 26 | login: logIn, // alias 27 | logOut: logOut, 28 | logout: logOut, // alias 29 | setPassword: setPassword, 30 | requestPasswordReset: requestPasswordReset, 31 | resendActivationEmail: resendActivationEmail, 32 | // secret api key 33 | getSecretApiKey: getSecretApiKey, 34 | regenerateSecretApiKey: regenerateSecretApiKey, 35 | // publishable api keys 36 | generatePublishableApiKey: generatePublishableApiKey, 37 | listPublishableApiKeys: listPublishableApiKeys, 38 | updatePublishableApiKeyDomains: updatePublishableApiKeyDomains, 39 | revokePublishableApiKey: revokePublishableApiKey 40 | } 41 | 42 | export default auth -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/railing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import getSchema from './common/get-schema.js' 4 | import getMaterial from '../../../scene/structure/parametric-objects/common/get-material.js' 5 | import updateSchema from './common/update-schema.js' 6 | import generateNormals from '../../../utils/data3d/buffer/get-normals' 7 | import cloneDeep from 'lodash/cloneDeep' 8 | import getRailingData3d from '../../../scene/structure/parametric-objects/railing' 9 | import dataToMaterials from './common/data-to-materials' 10 | import removeEmptyMeshes from './common/remove-empty-meshes' 11 | 12 | export default { 13 | 14 | schema: getSchema('railing'), 15 | 16 | init: function () {}, 17 | 18 | updateSchema: updateSchema, 19 | 20 | update: function (oldData) { 21 | var this_ = this 22 | var data = this_.data 23 | 24 | // remove old mesh 25 | this.remove() 26 | 27 | // get defaults and 28 | let attributes = cloneDeep(data) 29 | 30 | attributes.materials = dataToMaterials(data) 31 | 32 | // construct data3d object 33 | getRailingData3d(attributes) 34 | .then(data3d => { 35 | removeEmptyMeshes(data3d.meshes) 36 | 37 | // create new one 38 | this_.mesh = new THREE.Object3D() 39 | this_.data3dView = new io3d.aFrame.three.Data3dView({parent: this_.mesh}) 40 | 41 | // update view 42 | this_.data3dView.set(data3d) 43 | this_.el.setObject3D('mesh', this_.mesh) 44 | // emit event 45 | this_.el.emit('mesh-updated') 46 | }) 47 | }, 48 | 49 | remove: function () { 50 | if (this.data3dView) { 51 | this.data3dView.destroy() 52 | this.data3dView = null 53 | } 54 | if (this.mesh) { 55 | this.el.removeObject3D('mesh') 56 | this.mesh = null 57 | } 58 | }, 59 | 60 | } 61 | -------------------------------------------------------------------------------- /test/scene/structure/to-aframe-elements.js: -------------------------------------------------------------------------------- 1 | import toAframeElements from '../../../src/scene/structure/to-aframe-elements.js'; 2 | import { getAttributes, parseCameraBookmarks } from '../../../src/scene/structure/to-aframe-elements.js'; 3 | 4 | // module is using assertBrowser to verify its environment 5 | jest.mock('../../../src/core/runtime.js', () => ({ 6 | isBrowser: false, 7 | assertBrowser: () => {}, 8 | isNode: true, 9 | require: require 10 | })) 11 | 12 | test('sceneStructure to a-entity', () => { 13 | const sceneStructure = { 14 | type: 'wall', 15 | x: 0, 16 | y: 0, 17 | l: 2 18 | } 19 | const el = toAframeElements(sceneStructure) 20 | const wallComponent = el.getAttribute('io3d-wall') 21 | expect(typeof wallComponent).toBe('string') 22 | expect(wallComponent.includes('l: 2')).toBe(true) 23 | }); 24 | 25 | test('getAttributes from sceneStructure element', () => { 26 | const el3d = { 27 | type: 'polyfloor', 28 | x: 0, 29 | y: 0, 30 | polygon: [[0,1], [1,1], [1,0], [0,0]] 31 | } 32 | const attr = getAttributes(el3d) 33 | expect(typeof attr).toBe('object') 34 | expect(typeof attr.position).toBe('string') 35 | }); 36 | 37 | test('parseCameraBookmarks', () => { 38 | const sceneStructure = { 39 | type: 'plan', 40 | x: 0, 41 | y: 0, 42 | ry: 12, 43 | children: [ 44 | { 45 | type: 'camera-bookmark', 46 | x: 0, 47 | y: 1.6, 48 | z: 5, 49 | mode: 'person', 50 | distance: 0 51 | } 52 | ] 53 | } 54 | const cameraEl = parseCameraBookmarks([sceneStructure]) 55 | 56 | expect(cameraEl.getAttribute('camera')).toBeDefined() 57 | expect(cameraEl.children.length).toBe(1) 58 | const wayPoints = cameraEl.querySelectorAll('[tour-waypoint]') 59 | expect(wayPoints.length).toBe(1) 60 | }); 61 | -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/closet.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import getSchema from './common/get-schema.js' 4 | import getMaterial from '../../../scene/structure/parametric-objects/common/get-material.js' 5 | import updateSchema from './common/update-schema.js' 6 | import generateNormals from '../../../utils/data3d/buffer/get-normals' 7 | import generateUvs from '../../../utils/data3d/buffer/get-uvs' 8 | import cloneDeep from 'lodash/cloneDeep' 9 | import getClosetData3d from '../../../scene/structure/parametric-objects/closet' 10 | import dataToMaterials from './common/data-to-materials' 11 | import removeEmptyMeshes from './common/remove-empty-meshes' 12 | 13 | export default { 14 | 15 | schema: getSchema('closet'), 16 | 17 | init: function () {}, 18 | 19 | updateSchema: updateSchema, 20 | 21 | update: function (oldData) { 22 | var this_ = this 23 | var data = this_.data 24 | // remove old mesh 25 | this.remove() 26 | 27 | // get defaults and 28 | let attributes = cloneDeep(data) 29 | 30 | attributes.materials = dataToMaterials(data) 31 | 32 | getClosetData3d(attributes) 33 | .then(data3d => { 34 | removeEmptyMeshes(data3d.meshes) 35 | 36 | // create new one 37 | this_.mesh = new THREE.Object3D() 38 | this_.data3dView = new io3d.aFrame.three.Data3dView({parent: this_.mesh}) 39 | 40 | // update view 41 | this_.data3dView.set(data3d) 42 | this_.el.setObject3D('mesh', this_.mesh) 43 | // emit event 44 | this_.el.emit('mesh-updated'); 45 | }) 46 | }, 47 | 48 | remove: function () { 49 | if (this.data3dView) { 50 | this.data3dView.destroy() 51 | this.data3dView = null 52 | } 53 | if (this.mesh) { 54 | this.el.removeObject3D('mesh') 55 | this.mesh = null 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/scene/structure/validate/generic.js: -------------------------------------------------------------------------------- 1 | export default { 2 | params: { 3 | type: { 4 | type: 'string', 5 | possibleValues: [ 6 | 'box', 7 | 'camera-bookmark', 8 | 'closet', 9 | 'column', 10 | 'curtain', 11 | 'door', 12 | 'floor', 13 | 'floorplan', 14 | 'group', 15 | 'interior', 16 | 'kitchen', 17 | 'level', 18 | 'object', 19 | 'plan', 20 | 'polybox', 21 | 'polyfloor', 22 | 'railing', 23 | 'stairs', 24 | 'tag', 25 | 'wall', 26 | 'window', 27 | ], 28 | optional: false, 29 | skipInAframe: true 30 | }, 31 | x: { // x position in meters 32 | type: 'number', 33 | defaultValue: 0, 34 | optional: false, 35 | skipInAframe: true 36 | }, 37 | y: { // y position in meters 38 | type: 'number', 39 | defaultValue: 0, 40 | optional: false, 41 | skipInAframe: true 42 | }, 43 | z: { // z position in meters 44 | type: 'number', 45 | defaultValue: 0, 46 | optional: false, 47 | skipInAframe: true 48 | }, 49 | ry: { // y rotation in angle degrees 50 | type: 'number', 51 | defaultValue: 0, 52 | optional: false, 53 | skipInAframe: true, 54 | description: 'rotation around y axis' 55 | }, 56 | children: { 57 | //type: 'array-with-objects', 58 | type: 'array', 59 | defaultValue: [], 60 | optional: true, 61 | skipInAframe: true 62 | }, 63 | id: { 64 | type: 'string', 65 | optional: false, 66 | skipInAframe: true, 67 | description: 'unique identifier: UUID v4' 68 | }, 69 | materials: { 70 | type: 'object', 71 | optional: true 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/stairs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import getSchema from './common/get-schema.js' 4 | import getMaterial from '../../../scene/structure/parametric-objects/common/get-material.js' 5 | import updateSchema from './common/update-schema.js' 6 | import generateNormals from '../../../utils/data3d/buffer/get-normals' 7 | import generateUvs from '../../../utils/data3d/buffer/get-uvs' 8 | import cloneDeep from 'lodash/cloneDeep' 9 | import getStairsData3d from '../../../scene/structure/parametric-objects/stairs' 10 | import dataToMaterials from './common/data-to-materials' 11 | import removeEmptyMeshes from './common/remove-empty-meshes' 12 | 13 | export default { 14 | 15 | schema: getSchema('stairs'), 16 | 17 | init: function () {}, 18 | 19 | updateSchema: updateSchema, 20 | 21 | update: function (oldData) { 22 | var this_ = this 23 | var data = this_.data 24 | 25 | // remove old mesh 26 | this.remove() 27 | 28 | // get defaults and 29 | var attributes = cloneDeep(data) 30 | 31 | attributes.materials = dataToMaterials(data) 32 | 33 | getStairsData3d(attributes) 34 | .then(data3d => { 35 | removeEmptyMeshes(data3d.meshes) 36 | 37 | // create new one 38 | this_.mesh = new THREE.Object3D() 39 | this_.data3dView = new io3d.aFrame.three.Data3dView({parent: this_.mesh}) 40 | 41 | // update view 42 | this_.data3dView.set(data3d) 43 | this_.el.setObject3D('mesh', this_.mesh) 44 | // emit event 45 | this_.el.emit('mesh-updated') 46 | }) 47 | }, 48 | 49 | remove: function () { 50 | if (this.data3dView) { 51 | this.data3dView.destroy() 52 | this.data3dView = null 53 | } 54 | if (this.mesh) { 55 | this.el.removeObject3D('mesh') 56 | this.mesh = null 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /test/scene/structure/validate.js: -------------------------------------------------------------------------------- 1 | import validate from '../../../src/scene/structure/validate.js'; 2 | 3 | // sceneStructure validation 4 | 5 | test('Scene: test invalid type', async () => { 6 | const sceneStructure = { 7 | type: 'blurb', 8 | x: 1, 9 | y: 2, 10 | z: 0 11 | } 12 | const result = await validate(sceneStructure) 13 | expect(result.isValid).toBe(false) 14 | }) 15 | 16 | test('Scene: test NaN', async () => { 17 | const sceneStructure = { 18 | type: 'wall', 19 | l: 'a', 20 | h: 1, 21 | w: 0.1 22 | } 23 | const result = await validate(sceneStructure) 24 | expect(result.isValid).toBe(false) 25 | }) 26 | 27 | test('Scene: invalid children', async () => { 28 | const sceneStructure = { 29 | type: 'wall', 30 | l: 2, 31 | h: 1, 32 | w: 0.1, 33 | id: "2335fa98-2c0c-4168-8cde-9b2347789029", 34 | x: 0, 35 | y: 0, 36 | z: 0, 37 | ry: 0, 38 | children: [ 39 | { 40 | type: 'wall', 41 | l: 2, 42 | h: 1, 43 | w: 0.1 44 | } 45 | ] 46 | } 47 | const result = await validate(sceneStructure) 48 | expect(result.isValid).toBe(false) 49 | expect(result.errors[0].code).toBe(7) 50 | }) 51 | 52 | test('Scene: test valid scene structure', async () => { 53 | const sceneStructure = { 54 | type: 'wall', 55 | l: 2, 56 | h: 1, 57 | w: 0.1, 58 | x: 0, 59 | y: 0, 60 | z: 0, 61 | ry: 0, 62 | id: "a3ad7e08-66f9-40ce-a480-827220f8c52b", 63 | children: [ 64 | { 65 | type: 'window', 66 | x: 0.8, 67 | y: 0.5, 68 | z: 0, 69 | ry: 0, 70 | l: 0.5, 71 | h: 1, 72 | id: "177f8aee-dbc6-4398-b9a7-f7b820eb9fa9" 73 | } 74 | ] 75 | } 76 | const result = await validate(sceneStructure) 77 | expect(result.isValid).toBe(true) 78 | }) 79 | -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/stairs.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'all kinds of stairs types', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 1, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | w: { // width in meters 11 | type: 'number', 12 | defaultValue: 1.2, 13 | optional: false, 14 | min: 0.01 15 | }, 16 | h: { // height in meters 17 | type: 'number', 18 | defaultValue: 2.4, 19 | optional: false, 20 | min: 0.01 21 | }, 22 | l: { // length in meters 23 | type: 'number', 24 | defaultValue: 4, 25 | optional: false, 26 | min: 0.01 27 | }, 28 | stepWidth: { 29 | type: 'number', 30 | defaultValue: 1.2, 31 | optional: false, 32 | min: 0.01 33 | }, 34 | stairType: { 35 | type: 'string', 36 | defaultValue: 'straight', 37 | optional: false, 38 | possibleValues: ['straight', 'straightLanding', 'lShaped', 'halfLanding', '2QuarterLanding', 'winder', 'doubleWinder', 'spiral'] 39 | }, 40 | treadHeight: { 41 | type: 'number', 42 | defaultValue: 0.02, 43 | optional: false 44 | }, 45 | stepThickness: { 46 | type: 'number', 47 | defaultValue: 0.17, 48 | optional: false 49 | }, 50 | railing: { 51 | type: 'string', 52 | defaultValue: 'right', 53 | optional: false, 54 | possibleValues: ['none', 'left', 'right', 'both'] 55 | }, 56 | railingType: { 57 | type: 'string', 58 | defaultValue: 'verticalBars', 59 | optional: false, 60 | possibleValues: ['verticalBars'] 61 | } 62 | // TODO: add all default values 63 | }, 64 | childrenTypes: [], 65 | parentTypes: ['level'], 66 | aframeComponent: { 67 | name: 'io3d-stairs' 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /examples-browser/millennium-falcon/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 |
55 | Millennium Falcon by 3d.io for A-Frame modeled by David Tran 56 |
Change Camera or Open visual inspector 57 |
58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/column.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import getSchema from './common/get-schema.js' 4 | import getMaterial from '../../../scene/structure/parametric-objects/common/get-material.js' 5 | import updateSchema from './common/update-schema.js' 6 | import generateNormals from '../../../utils/data3d/buffer/get-normals' 7 | import generateUvs from '../../../utils/data3d/buffer/get-uvs' 8 | import cloneDeep from 'lodash/cloneDeep' 9 | import getColumnData3d from '../../../scene/structure/parametric-objects/column' 10 | import dataToMaterials from './common/data-to-materials' 11 | import removeEmptyMeshes from './common/remove-empty-meshes' 12 | 13 | export default { 14 | 15 | schema: getSchema('column'), 16 | 17 | init: function () {}, 18 | 19 | updateSchema: updateSchema, 20 | 21 | update: function (oldData) { 22 | var this_ = this 23 | var data = this_.data 24 | 25 | // remove old mesh 26 | this.remove() 27 | 28 | // get defaults and 29 | var attributes = cloneDeep(data) 30 | 31 | attributes.materials = dataToMaterials(data) 32 | 33 | // construct data3d object 34 | 35 | getColumnData3d(attributes) 36 | .then(data3d => { 37 | removeEmptyMeshes(data3d.meshes) 38 | 39 | // create new one 40 | this_.mesh = new THREE.Object3D() 41 | this_.data3dView = new io3d.aFrame.three.Data3dView({parent: this_.mesh}) 42 | 43 | // update view 44 | this_.data3dView.set(data3d) 45 | this_.el.setObject3D('mesh', this_.mesh) 46 | // emit event 47 | this_.el.emit('mesh-updated'); 48 | }) 49 | }, 50 | 51 | remove: function () { 52 | if (this.data3dView) { 53 | this.data3dView.destroy() 54 | this.data3dView = null 55 | } 56 | if (this.mesh) { 57 | this.el.removeObject3D('mesh') 58 | this.mesh = null 59 | } 60 | }, 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/railing.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'segmented or solid railing', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 0, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | w: { // width in meters 11 | type: 'number', 12 | defaultValue: 0.05, 13 | optional: false, 14 | min: 0.01, 15 | description: 'width' 16 | }, 17 | h: { // height in meters 18 | type: 'number', 19 | defaultValue: 1, 20 | optional: false, 21 | min: 0.01, 22 | description: 'height' 23 | }, 24 | l: { // length in meters 25 | type: 'number', 26 | defaultValue: 1, 27 | optional: false, 28 | min: 0.01, 29 | description: 'length' 30 | }, 31 | pailing: { 32 | type: 'number', 33 | defaultValue: 0.01, 34 | optional: false, 35 | description: 'strength of the posts' 36 | }, 37 | railCount: { 38 | type: 'int', 39 | defaultValue: 2, 40 | optional: true, 41 | description: 'horizontal rail count' 42 | }, 43 | segmentation: { 44 | type: 'string', 45 | defaultValue: 'distance', 46 | possibleValues: ['distance', 'number', 'none'], 47 | optional: false, 48 | description: 'vertical segmentation type' 49 | }, 50 | segments: { 51 | type: 'int', 52 | defaultValue: 5, 53 | optional: true, 54 | description: 'number of vertical segments, for segmentation = \'number\'' 55 | }, 56 | segmentDistance: { 57 | type: 'number', 58 | defaultValue: 0.14, 59 | optional: true, 60 | description: 'distance between vertical segments, for segmentation = \'distance\'' 61 | } 62 | }, 63 | childrenTypes: [], 64 | parentTypes: ['level'], 65 | aframeComponent: { 66 | name: 'io3d-railing' 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/scene/structure/validate/by-type/closet.js: -------------------------------------------------------------------------------- 1 | export default { 2 | description: 'parametric closet with segmentation targeting 0.6m', 3 | params: { 4 | v: { 5 | type: 'number', 6 | defaultValue: 1, 7 | optional: true, 8 | description: 'version' 9 | }, 10 | l: { // length in meters 11 | type: 'number', 12 | defaultValue: 1.8, 13 | optional: false, 14 | min: 0.01, 15 | description: 'length' 16 | }, 17 | w: { // width in meters 18 | type: 'number', 19 | defaultValue: 0.6, 20 | optional: false, 21 | min: 0.01, 22 | description: 'width' 23 | }, 24 | h: { // height in meters 25 | type: 'number', 26 | defaultValue: 2.4, 27 | optional: false, 28 | min: 0.01, 29 | description: 'height' 30 | }, 31 | baseboard: { 32 | type: 'number', 33 | defaultValue: 0.1, 34 | optional: true, 35 | min: 0.01, 36 | description: 'height of baseboard' 37 | }, 38 | doorWidth: { 39 | type: 'number', 40 | defaultValue: 0.02, 41 | optional: true, 42 | min: 0.01, 43 | description: 'thickness of closet door' 44 | }, 45 | handleLength: { 46 | type: 'number', 47 | defaultValue: 0.02, 48 | optional: true, 49 | min: 0.01, 50 | description: 'length of closet door handle' 51 | }, 52 | handleWidth: { 53 | type: 'number', 54 | defaultValue: 0.02, 55 | optional: true, 56 | min: 0.01, 57 | description: 'thickness of closet door handle' 58 | }, 59 | handleHeight: { 60 | type: 'number', 61 | defaultValue: 0.3, 62 | optional: true, 63 | min: 0.01, 64 | description: 'height of closet door handle' 65 | } 66 | }, 67 | childrenTypes: [], 68 | parentTypes: ['level'], 69 | aframeComponent: { 70 | name: 'io3d-closet' 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/floor-plan/recognize.js: -------------------------------------------------------------------------------- 1 | import callService from '../utils/services/call.js' 2 | import normalizeSceneStructure from '../scene/structure/normalize.js' 3 | 4 | export default function recognize (args) { 5 | var el = typeof args === 'string' ? document.querySelector(args) : null 6 | 7 | var url, width, height, pixelsPerMeter 8 | 9 | if (el) { 10 | // get floor plan image date info from aframe element 11 | url = el.attributes.src.value 12 | width = el.attributes.width.value 13 | height = el.attributes.height.value 14 | 15 | // TODO: fetch image directly to make sure we get it 16 | var texture = el.components.material.material.map.image 17 | pixelsPerMeter = getPixelPerMeterRatio(texture.width, texture.height, width, height) 18 | 19 | } else { 20 | return 21 | // TODO: add option for user provided arguments & fetch image to get dimensions 22 | /* 23 | url = args.url 24 | width = args.width 25 | height = args.height 26 | */ 27 | } 28 | 29 | var args = { 30 | floorPlanUrl: url, 31 | pixelsPerMeter: pixelsPerMeter, 32 | colorCoded: true 33 | } 34 | 35 | return callService('Recognizer.recognize', {arguments: args}) 36 | .then(function(result) { 37 | // normalize scene structure to add ids and default values 38 | return normalizeSceneStructure(result.planStructure) 39 | }) 40 | .catch(function(error) { 41 | console.error('Recognition error:', error) 42 | return Promise.reject('Recognition failed - check console for details') 43 | }) 44 | } 45 | 46 | function getPixelPerMeterRatio(pxWidth, pxHeight, width, height) { 47 | 48 | // from pixels 49 | var areaPx2 = pxWidth * pxHeight //this.getPixelArea() 50 | 51 | // from input 52 | var areaM2 = width * height //parseFloat(this.$distanceInput.val()) 53 | 54 | var pixelPerMeterRatio = Math.sqrt(areaPx2 / areaM2) 55 | 56 | return pixelPerMeterRatio 57 | } -------------------------------------------------------------------------------- /examples-browser/no-view/js/run-code.js: -------------------------------------------------------------------------------- 1 | function runCode (str, options) { 2 | // API 3 | options = options || {} 4 | var catchErrors = options.catchErrors !== undefined ? options.catchErrors : false 5 | var encapsulate = options.encapsulate !== undefined ? options.encapsulate : false 6 | var isUrl = options.isUrl !== undefined ? options.isUrl : (str.substr(0,4) === 'http' || str[0] === '.') 7 | 8 | return Promise.resolve().then(function () { 9 | 10 | return isUrl ? $.ajax({type: 'GET', dataType: 'text', url: str}) : str 11 | 12 | }).then(function (code) { 13 | 14 | // internals 15 | var injectLineNumberFunction = 'function getLineNumber (e) { var line;' + 16 | 'if (e) { line=e.stack.split("\\n")[1]; }' + 17 | 'else { line = new Error().stack.split("\\n")[3]; }' + 18 | 'var match = /^.*:(\\d*):(\\d)*\\)?$/.exec(line);' + 19 | 'if (match) { return match[1] + ": "; } else { return ""; }' + 20 | '};' 21 | 22 | // Encapsulate script. New line after code is important in case last line 23 | // is a comment. 24 | if (encapsulate) code = '(function(){' + injectLineNumberFunction + code + '\n})()' 25 | 26 | if (catchErrors) code = 'try {' + code + '\n} catch (e) { ' + injectLineNumberFunction + ' window.proxyConsole("error", [e.toString()], getLineNumber(e)); }' 27 | 28 | // run script 29 | 30 | // check for existing script node and remove it before running 31 | var existingScriptTag = document.getElementById('sdk-script') 32 | var script = document.createElement('script') 33 | 34 | if (existingScriptTag) existingScriptTag.parentNode.removeChild(existingScriptTag) 35 | 36 | script.id = 'injected-script' 37 | 38 | try { 39 | script.appendChild(document.createTextNode(code)) 40 | document.body.appendChild(script) 41 | } catch (e) { 42 | script.text = code 43 | document.body.appendChild(script) 44 | } 45 | 46 | }) 47 | } -------------------------------------------------------------------------------- /src/utils/io/fetch-script.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | import PromiseCache from '../promise-cache.js' 3 | import fetch from './fetch.js' 4 | 5 | var promiseCache = new PromiseCache() 6 | 7 | export default function fetchScript (url) { 8 | runtime.assertBrowser('Please use "require()" to fetch modules in server environment.') 9 | 10 | // module wrapper 11 | window.___modules = window.___modules || {} 12 | 13 | // return module if it has been loaded already 14 | if (window.___modules[url]) { 15 | return Promise.resolve(window.___modules[url]) 16 | 17 | } else { 18 | 19 | // try promise cache (could be in loading state) 20 | var promiseFromCache = promiseCache.get(url) 21 | if (promiseFromCache) return promiseFromCache 22 | 23 | // load code and use module wrapper 24 | var fetchPromise = fetch(url).then(function(response){ 25 | if (!response.ok) throw 'Could not load script from URL: '+url 26 | return response.text() 27 | }).then(function(code){ 28 | 29 | // check module type 30 | var moduleWrapper 31 | if (code.indexOf('define(function()') > -1) { 32 | // AMD 33 | moduleWrapper = code+'\nfunction define(cb){ window.___modules["'+url+'"] = cb(); };' 34 | } else { 35 | // CommonJS 36 | moduleWrapper = 'window.___modules["'+url+'"] = (function(){ var exports = {}, module = {exports:exports};'+code+'\nreturn module.exports\n})()' 37 | } 38 | 39 | var script = document.createElement('script') 40 | try { 41 | script.appendChild(document.createTextNode(moduleWrapper)) 42 | document.body.appendChild(script) 43 | } catch (e) { 44 | script.text = moduleWrapper 45 | document.body.appendChild(script) 46 | } 47 | return window.___modules[url] 48 | }) 49 | 50 | // add to cache 51 | promiseCache.add(url, fetchPromise) 52 | 53 | return fetchPromise 54 | 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/furniture/common/normalize-furniture-info.js: -------------------------------------------------------------------------------- 1 | import configs from '../../core/configs.js' 2 | 3 | export default function normalizeFurnitureInfo (rawInfo) { 4 | // normalizes furniture definitions from server side endpoints 5 | 6 | var indexImageUrl = convertKeyToUrl(rawInfo.preview) 7 | 8 | return { 9 | // main info 10 | id: rawInfo.productResourceId, 11 | name: rawInfo.productDisplayName, 12 | description: rawInfo.description, 13 | manufacturer: rawInfo.manufacturer, 14 | designer: rawInfo.designer, 15 | // cloudinary API reference: http://cloudinary.com/documentation/image_transformation_reference#format_parameter 16 | thumb: 'https://res.cloudinary.com/archilogic/image/fetch/c_limit,h_150,w_150/' + indexImageUrl, 17 | indexImage: indexImageUrl, 18 | images: rawInfo.images.map(convertKeyToUrl), 19 | url: rawInfo.link, 20 | year: rawInfo.year, 21 | // grouping 22 | collectionIds: rawInfo.productCollectionResourceIds, 23 | tags: cleanUpArrays(rawInfo.tags), 24 | styles: cleanUpArrays(rawInfo.styles), 25 | categories: cleanUpArrays(rawInfo.categories), 26 | colors: cleanUpArrays(rawInfo.colours), 27 | // geometry 28 | boundingBox: rawInfo.boundingBox, 29 | boundingPoints: rawInfo.boundingPoints, 30 | data3dStorageId: rawInfo.fileKey, 31 | data3dUrl: convertKeyToUrl(rawInfo.fileKey), 32 | // scene Structure definition 33 | sceneStructure: rawInfo.modelStructure, 34 | // data info 35 | created: rawInfo.createdAt, 36 | updated: rawInfo.updatedAt 37 | } 38 | } 39 | 40 | // helpers 41 | 42 | function convertKeyToUrl (key) { 43 | if (!key) return 44 | // add leading slash 45 | if (key[0] !== '/') key = '/'+key 46 | return 'https://' + configs.storageDomain + key 47 | } 48 | 49 | function cleanUpArrays (arr) { 50 | // TODO: remove this once #252 is resolved https://github.com/archilogic-com/services/issues/252 51 | return arr[0] === '' ? arr.slice(1) : arr 52 | } -------------------------------------------------------------------------------- /src/aframe/component/architecture-toolkit/polyfloor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import getSchema from './common/get-schema.js' 4 | import getMaterial from '../../../scene/structure/parametric-objects/common/get-material.js' 5 | import updateSchema from './common/update-schema.js' 6 | import generatePolygonBuffer from '../../../utils/data3d/buffer/get-polygon' 7 | import generateExtrusionBuffer from '../../../utils/data3d/buffer/get-extrusion' 8 | import generateNormals from '../../../utils/data3d/buffer/get-normals' 9 | import cloneDeep from 'lodash/cloneDeep' 10 | import getPolyfloorData3d from '../../../scene/structure/parametric-objects/polyfloor' 11 | import dataToMaterials from './common/data-to-materials' 12 | import removeEmptyMeshes from './common/remove-empty-meshes' 13 | 14 | export default { 15 | 16 | schema: getSchema('polyfloor'), 17 | 18 | init: function () {}, 19 | 20 | updateSchema: updateSchema, 21 | 22 | update: function (oldData) { 23 | var this_ = this 24 | var data = this_.data 25 | 26 | // remove old mesh 27 | this.remove() 28 | 29 | // get defaults and 30 | let attributes = cloneDeep(data) 31 | 32 | attributes.materials = dataToMaterials(data) 33 | 34 | // construct data3d object 35 | getPolyfloorData3d(attributes) 36 | .then(data3d => { 37 | removeEmptyMeshes(data3d.meshes) 38 | 39 | // create new one 40 | this_.mesh = new THREE.Object3D() 41 | this_.data3dView = new io3d.aFrame.three.Data3dView({parent: this_.mesh}) 42 | 43 | // update view 44 | this_.data3dView.set(data3d) 45 | this_.el.setObject3D('mesh', this_.mesh) 46 | // emit event 47 | this_.el.emit('mesh-updated') 48 | }) 49 | }, 50 | 51 | remove: function () { 52 | if (this.data3dView) { 53 | this.data3dView.destroy() 54 | this.data3dView = null 55 | } 56 | if (this.mesh) { 57 | this.el.removeObject3D('mesh') 58 | this.mesh = null 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/storage/model-exporter.js: -------------------------------------------------------------------------------- 1 | import getConvertibleTextureIds from '../utils/processing/get-convertible-texture-ids.js' 2 | import callServices from '../utils/services/call.js' 3 | 4 | function getExporter(format) { 5 | return function exportModel(storageId, options) { 6 | 7 | // API 8 | options = options || {} 9 | 10 | return getConvertibleTextureIds(storageId).then(function(textureIds) { 11 | 12 | var convertParams = { 13 | method: 'convert'.concat('.', format), 14 | params: { 15 | inputFileKey: storageId, 16 | inputAssetKeys: textureIds 17 | } 18 | } 19 | 20 | // Optional convert parameters for API call 21 | if (options.filename) { 22 | convertParams.params.settings = JSON.stringify( { outputFileName: options.filename } ) 23 | } 24 | 25 | return callServices('Processing.task.enqueue', convertParams) 26 | 27 | }) 28 | } 29 | } 30 | 31 | function exportDxf(storageId, options) { 32 | // API 33 | options = options || {} 34 | 35 | var dxfParams = { 36 | method: 'convert.dxf', 37 | params: { 38 | inputFileKey: storageId 39 | } 40 | } 41 | 42 | // Optional convert parameters for API call 43 | if (options.filename || options.projection) { 44 | var dxfSettings = {} 45 | if (options.filename) { 46 | dxfSettings.outputFileName = options.filename 47 | } 48 | if (options.projection) { 49 | dxfSettings.projection = options.projection 50 | } 51 | 52 | dxfParams.params.settings = JSON.stringify(dxfSettings) 53 | 54 | } 55 | 56 | 57 | return callServices('Processing.task.enqueue', dxfParams) 58 | } 59 | 60 | 61 | // expose API 62 | 63 | export default { 64 | export3ds: getExporter('3ds'), 65 | exportBlend: getExporter('blend'), 66 | exportDae: getExporter('dae'), 67 | exportFbx: getExporter('fbx'), 68 | exportObj: getExporter('obj'), 69 | exportDxf 70 | } 71 | -------------------------------------------------------------------------------- /examples-browser/scene/export-svg.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generate svg floor plan 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 |

17 |
18 | 19 |
20 |
21 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /test/scene/structure/from-aframe-elements.js: -------------------------------------------------------------------------------- 1 | import fromAframeElements from '../../../src/scene/structure/from-aframe-elements.js'; 2 | import { stringToCoordinate, parseComponent } from '../../../src/scene/structure/from-aframe-elements.js'; 3 | 4 | test('a-entity to sceneStructure', () => { 5 | const el = document.createElement('a-entity') 6 | el.setAttribute('io3d-wall', 'w: 0.15; l: 4; h: 2') 7 | el.setAttribute('position', '2 0 -5') 8 | el.setAttribute('rotation', '0 0 0') 9 | el.setAttribute('io3d-uuid', '7078987a-d67c-4d01-bd7d-a3c4bb51244b') 10 | const sceneStructure = fromAframeElements(el) 11 | expect(sceneStructure.l).toBe(4) 12 | expect(sceneStructure.w).toBe(0.15) 13 | expect(sceneStructure.x).toBe(2) 14 | expect(sceneStructure.z).toBe(-5) 15 | expect(sceneStructure.id).toBe('7078987a-d67c-4d01-bd7d-a3c4bb51244b') 16 | }); 17 | 18 | test('a-entity children to sceneStructure', () => { 19 | const el = document.createElement('a-entity') 20 | el.setAttribute('io3d-wall', 'w: 0.15; l: 4; h: 2') 21 | el.setAttribute('position', '2 0 -5') 22 | el.setAttribute('rotation', '0 0 0') 23 | el.setAttribute('io3d-uuid', '7078987a-d67c-4d01-bd7d-a3c4bb51244b') 24 | const doorEl = document.createElement('a-entity') 25 | doorEl.setAttribute('io3d-door', 'l: 1.2; hinge: left; side: front') 26 | el.appendChild(doorEl) 27 | const sceneStructure = fromAframeElements(el) 28 | const children = sceneStructure.children 29 | expect(children.length).toBe(1) 30 | expect(children[0].l).toBe(1.2) 31 | expect(children[0].hinge).toBe('left') 32 | }); 33 | 34 | test('stringToCoordinate', () => { 35 | const str = '12 0.12 0' 36 | const obj = stringToCoordinate(str) 37 | expect(obj.x).toBe(12) 38 | expect(obj.y).toBe(0.12) 39 | expect(obj.z).toBe(0) 40 | }); 41 | 42 | test('parse component', () => { 43 | const str = 'w: 0.15; hinge: left; h: 2' 44 | const type = 'door' 45 | const obj = parseComponent(str, type) 46 | expect(obj.w).toBe(0.15) 47 | expect(obj.hinge).toBe('left') 48 | expect(obj.h).toBe(2) 49 | }); -------------------------------------------------------------------------------- /src/utils/data3d/buffer/get-uvs.js: -------------------------------------------------------------------------------- 1 | // methods 2 | 3 | function projectAxisY (v) { 4 | 5 | var uvs = new Float32Array(v.length / 1.5) 6 | var uvPos = 0 7 | 8 | var i, l 9 | for (i = 0, l = v.length; i < l; i += 9) { 10 | 11 | uvs[uvPos] = v[i + 2] 12 | uvs[uvPos + 1] = v[i] 13 | uvs[uvPos + 2] = v[i + 5] 14 | uvs[uvPos + 3] = v[i + 3] 15 | uvs[uvPos + 4] = v[i + 8] 16 | uvs[uvPos + 5] = v[i + 6] 17 | uvPos += 6 18 | 19 | } 20 | 21 | return uvs 22 | 23 | } 24 | projectAxisY.title = 'Project Top Down' 25 | 26 | function architectural (v) { 27 | 28 | var uvs = new Float32Array(v.length / 1.5) 29 | var uvPos = 0 30 | 31 | var i, l, n, components 32 | for (i = 0, l = v.length; i < l; i += 9) { 33 | 34 | // calculate face normal 35 | // cross product (a-b) x (c-b) 36 | n = [ 37 | (v[i + 7] - v[i + 4]) * (v[i + 2] - v[i + 5]) - (v[i + 8] - v[i + 5]) * (v[i + 1] - v[i + 4]), 38 | (v[i + 8] - v[i + 5]) * (v[i] - v[i + 3]) - (v[i + 6] - v[i + 3]) * (v[i + 2] - v[i + 5]), 39 | (v[i + 6] - v[i + 3]) * (v[i + 1] - v[i + 4]) - (v[i + 7] - v[i + 4]) * (v[i] - v[i + 3]) 40 | ] 41 | 42 | // normals should be absolute 43 | if (n[0] < 0) { 44 | n[0] *= -1 45 | } 46 | if (n[1] < 0) { 47 | n[1] *= -1 48 | } 49 | if (n[2] < 0) { 50 | n[2] *= -1 51 | } 52 | 53 | // highest first? 54 | components = [1, 0, 2].sort(function (a, b) { 55 | return n[a] - n[b] 56 | }) 57 | 58 | uvs[uvPos] = v[i + components[1]] 59 | uvs[uvPos + 1] = v[i + components[0]] 60 | uvs[uvPos + 2] = v[i + 3 + components[1]] 61 | uvs[uvPos + 3] = v[i + 3 + components[0]] 62 | uvs[uvPos + 4] = v[i + 6 + components[1]] 63 | uvs[uvPos + 5] = v[i + 6 + components[0]] 64 | uvPos += 6 65 | 66 | } 67 | 68 | return uvs 69 | 70 | } 71 | architectural.title = 'Architectural' 72 | 73 | // API 74 | 75 | var getUvsBuffer = { 76 | architectural: architectural, 77 | projectAxisY: projectAxisY 78 | } 79 | 80 | export default getUvsBuffer 81 | -------------------------------------------------------------------------------- /src/utils/processing/when-hi-res-textures-ready.js: -------------------------------------------------------------------------------- 1 | import getUrlFromStorageId from '../../storage/get-url-from-id.js' 2 | import getNoCdnUrlFromStorageId from '../../storage/get-no-cdn-url-from-id.js' 3 | import poll from '../poll.js' 4 | import checkIfFileExists from '../io/check-if-file-exists.js' 5 | import loadData3d from '../data3d/load.js' 6 | import decodeBinaryData3d from '../data3d/decode-binary.js' 7 | import getTextureKeys from '../data3d/get-texture-keys.js' 8 | 9 | /* 10 | input: data3d (object or binary) or storageId (referencing data3d) 11 | returns promise 12 | */ 13 | 14 | export default function whenHiResTexturesReady (input) { 15 | 16 | // resolves when hi-res textures are available 17 | // - DXT (DDS) for hires on desktop 18 | // - PVRTC for iOS (not yet implemented) 19 | // - ETC1 for Android (not yet implemented) 20 | 21 | return normalizeInput(input).then(function (data3d) { 22 | 23 | var values = getTextureKeys(data3d, { 24 | filter: function (value, type, format, material, data3d) { 25 | return format === 'dds' ? value : null 26 | } 27 | }) 28 | 29 | return Promise.all(values.map(pollTexture)) 30 | 31 | }) 32 | 33 | } 34 | 35 | // helpers 36 | 37 | function normalizeInput(input) { 38 | var inputType = typeof input 39 | if (inputType === 'string') { 40 | // load data3d from URL 41 | var url = getUrlFromStorageId(input) 42 | return loadData3d(url) 43 | } else if (input instanceof Blob) { 44 | // decode binary data3d 45 | return decodeBinaryData3d(input) 46 | } else if (inputType === 'object') { 47 | // data3d object 48 | return Promise.resolve(input) 49 | } else { 50 | return Promise.reject('Unknown param type') 51 | } 52 | } 53 | 54 | // poll for DDS storageIds 55 | 56 | function pollTexture(storageId) { 57 | 58 | var url = getNoCdnUrlFromStorageId(storageId) 59 | 60 | return poll(function (resolve, reject, next) { 61 | 62 | checkIfFileExists(url).then(function(exists){ 63 | exists ? resolve() : next() 64 | }) 65 | 66 | }) 67 | 68 | } -------------------------------------------------------------------------------- /src/utils/auth/log-in.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird' 2 | import log from 'js-logger' 3 | import callServices from '../services/call.js' 4 | import uuid from '../uuid.js' 5 | import getSession from './get-session.js' 6 | import runtime from '../../core/runtime.js' 7 | /** 8 | * Login in user using credentials 9 | * @function io3d.auth.logIn 10 | * @param {object} args 11 | * @param {string} args.username - User username or email 12 | * @param {string} args.password - User password 13 | */ 14 | 15 | export default function logIn(args) { 16 | var credentials = { 17 | username: args.username || args.email, 18 | password: args.password || uuid.generate() 19 | } 20 | 21 | // log out first 22 | log.debug( 23 | 'Sending API login request for user "' + credentials.username + '" ...' 24 | ) 25 | return callServices('User.logOut') 26 | .then(function onLogoutSuccess() { 27 | // send log in request 28 | return callServices('User.logIn', { 29 | loginData: { 30 | resourceName: credentials.username, 31 | password: credentials.password 32 | } 33 | }) 34 | }) 35 | .then(function onLoginSuccess() { 36 | // request session to verify login with a separate request 37 | return getSession() 38 | }) 39 | .then(function onSessionSuccess(session) { 40 | if (session.isAuthenticated) { 41 | log.debug( 42 | 'API: User "' + session.user.email + '" logged in successfully.' 43 | ) 44 | return session 45 | } else { 46 | if(runtime.isNode) return Promise.reject('io3d.auth.logIn cannot be used in node.js. Please use secret key authentication instead. See https://3d.io/docs/api/1/get-started-node-server.html#using-secret-api-key for further info.') 47 | else return Promise.reject('Log in error: Session could not been established.') 48 | } 49 | }) 50 | .catch(function onError(error) { 51 | // login failed 52 | log.debug( 53 | 'API: Could not log in user "' + credentials.username + '".', 54 | error 55 | ) 56 | return Promise.reject(error) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /examples-browser/change-material/index.html: -------------------------------------------------------------------------------- 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 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/utils/ui/create-message-ui.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | import el from './common/dom-el.js' 3 | import Promise from 'bluebird' 4 | 5 | // container DOM element 6 | var mainEl 7 | if (runtime.isBrowser) runtime.domReady(function(){ 8 | mainEl = el('
',{ class: 'io3d-message-list' }).appendTo('body') 9 | }) 10 | 11 | // main 12 | function message (message, expire, type) { 13 | runtime.assertBrowser() 14 | 15 | // do nothing if there is no message 16 | if (!message || message === '') return Promise.resolve() 17 | // default expire value is 4 secs 18 | expire = expire !== undefined ? expire : 4000 // ms 19 | // default message type 20 | type = type || 'neutral' // can be: neutral, success, warning, error 21 | 22 | // internals 23 | var isClosed = false 24 | 25 | // create main html element 26 | var messageEl = el('
',{ 27 | class: 'message' 28 | }).prependTo(mainEl).hide() 29 | el('
',{ class: 'spacer' }).appendTo(messageEl) 30 | 31 | // insert content 32 | var contentEl = el('
',{ 33 | class: 'text '+type 34 | }).appendTo(messageEl) 35 | el.isElement(message) ? contentEl.append(message) : contentEl.innerHTML = message 36 | 37 | // create message object 38 | var resolve 39 | var result = new Promise(function(resolve_, reject_){ 40 | resolve = resolve_ 41 | }) 42 | 43 | // close method 44 | result.close = function close () { 45 | if (isClosed) return 46 | isClosed = true 47 | messageEl.toggleSlide() 48 | setTimeout(function(){ 49 | messageEl.remove() 50 | }, 500) 51 | resolve() 52 | } 53 | 54 | // init 55 | messageEl.toggleSlide() 56 | 57 | // close message on expire 58 | if (expire) setTimeout(result.close, expire) 59 | 60 | // expose message object 61 | return result 62 | 63 | } 64 | 65 | // shortcuts for convenience 66 | message.success = function createErrorMessage (str, expire) { 67 | return message (str, expire, 'success') 68 | } 69 | message.warning = function createErrorMessage (str, expire) { 70 | return message (str, expire, 'warning') 71 | } 72 | message.error = function createErrorMessage (str, expire) { 73 | return message (str, expire !== undefined ? expire : 5000, 'error') 74 | } 75 | 76 | export default message -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > 3dio.js is deprecated 2 | > visit [https://developers.archilogic.com](https://developers.archilogic.com) for new developements 3 | 4 | 5 | 6 | ## Basic Example 7 | 8 | [Run Demo](https://3dio-aframe.glitch.me) 9 | ```html 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ``` 25 | 26 | Learn more about using 3d.io data at https://furniture.3d.io 27 | 28 | ## Documentation 29 | 30 | https://3d.io/docs/api/2/ 31 | 32 | ## Install 33 | 34 | ### Browser 35 | 36 | Requirements: A-frame v0.8 or lower 37 | 38 | ```html 39 | 40 | 41 | 42 | 43 | ``` 44 | 45 | ### Server 46 | 47 | `npm install 3dio --save` 48 | 49 | Installation tutorial for beginners: https://www.npmjs.com/package/3dio/tutorial 50 | 51 | ## Features 52 | 53 | https://3d.io/#products 54 | 55 | ## Use Cases 56 | 57 | https://3d.io/#use-cases 58 | 59 | ## Contribute 60 | 61 | Install local dev environment: 62 | 63 | 1. Clone repository: 64 | 65 | `git clone https://github.com/archilogic-com/3dio-js.git ; cd ./3dio-js` 66 | 2. Install global packages: 67 | 68 | `npm install rollup -g ; npm install lite-server -g` 69 | 3. Install local packages: 70 | 71 | `npm install` 72 | 4. Run local dev server: 73 | 74 | `npm start` 75 | 5. Run tests: 76 | 77 | `npm test` 78 | 79 | Please follow our [Contribution guidelines](.github/CONTRIBUTING.md) 80 | 81 | [![Build Status](https://travis-ci.org/archilogic-com/3dio-js.svg?branch=master)](https://travis-ci.org/archilogic-com/3dio-js) 82 | -------------------------------------------------------------------------------- /src/utils/ui/create-prompt-ui.js: -------------------------------------------------------------------------------- 1 | import runtime from '../../core/runtime.js' 2 | import el from './common/dom-el.js' 3 | import createConfirmUi from './create-confirm-ui.js' 4 | 5 | // main 6 | 7 | export default function createPromptUi (a, b) { 8 | runtime.assertBrowser() 9 | 10 | var options 11 | if (el.isElement(a) || typeof a === 'string') { 12 | options = b || {} 13 | options.message = a 14 | } else if (typeof a === 'object') { 15 | options = a 16 | } else { 17 | throw 'Argument mismatch https://3d.io/docs/api/1/ui.html' 18 | } 19 | 20 | var value = options.value || '' 21 | var inputMessage = options.message 22 | var multiLine = options.multiLine 23 | var multiLineHeight = options.multiLineHeight && typeof options.multiLineHeight === 'number' ? options.multiLineHeight+'px' : options.multiLineHeight 24 | options.width = options.width ? options.width : '500px' 25 | 26 | // override message with new one 27 | options.message = el('
') 28 | // append input message 29 | if (inputMessage) options.message.append(inputMessage) 30 | // create input box for prompt 31 | if (multiLine) { 32 | var style = inputMessage && !el.isElement(inputMessage) ? 'margin: 1em 0 0 0;' : 'margin: 0 0 0 0;' 33 | style += 'min-height: '+(multiLineHeight ? multiLineHeight : '150px')+';' 34 | var inputEl = el('