diff --git a/app.js b/app.js index 6e4a8850..23851022 100644 --- a/app.js +++ b/app.js @@ -248,6 +248,7 @@ const script = { "moto/load-3mf", "moto/load-obj", "moto/load-stl", + "moto/load-url", "moto/db", "kiri/ui", "kiri/lang", diff --git a/src/kiri/main.js b/src/kiri/main.js index 0f2d6598..c32ba88a 100644 --- a/src/kiri/main.js +++ b/src/kiri/main.js @@ -95,6 +95,7 @@ console.log/** Copyright Stewart Allen -- All Rights Reserved */ layout: platformLayout, load: platformLoad, load_stl: platformLoadSTL, + load_url: platformLoadURL, deselect: platformDeselect, select: platformSelect, select_all: platformSelectAll, @@ -1874,6 +1875,20 @@ console.log/** Copyright Stewart Allen -- All Rights Reserved */ }, formdata, scale); } + function platformLoadURL(url, group) { + platform.group(); + MOTO.URL.load(url).then((objects) => { + for (let object of objects) { + let widget = newWidget(undefined, group).loadVertices(object.mesh); + widget.meta.file = object.file; + platform.add(widget); + } + platform.group_done(); + }).catch(error => { + API.show.alert(error); + }); + } + function platformComputeMaxZ() { topZ = 0; forAllWidgets(function(widget) { @@ -2501,7 +2516,7 @@ console.log/** Copyright Stewart Allen -- All Rights Reserved */ } } else if (is3mf) { - MOTO.TMF.parse(e.target.result).then(models => { + MOTO.TMF.parseAsync(e.target.result).then(models => { console.log({models}); for (let model of models) { let name = e.target.file.name; diff --git a/src/moto/load-3mf.js b/src/moto/load-3mf.js index b7c6025b..63e8143f 100644 --- a/src/moto/load-3mf.js +++ b/src/moto/load-3mf.js @@ -6,7 +6,7 @@ if (self.moto.OBJ) return; self.moto.TMF = { - parse : parse + parseAsync }; function query(node, path, fn) { @@ -30,7 +30,6 @@ function loadModel(doc) { let models = []; - function emitModel(model, faces) { if (faces && faces.length) { models.push({name: model, faces}); @@ -82,7 +81,7 @@ * @param {Object} data binary file * @returns {Array} vertex face array */ - function parse(data) { + function parseAsync(data) { return new Promise((resolve, reject) => { JSZip.loadAsync(data).then(zip => { for (let [key,value] of Object.entries(zip.files)) { diff --git a/src/moto/load-obj.js b/src/moto/load-obj.js index e8de2f6c..059bdb24 100644 --- a/src/moto/load-obj.js +++ b/src/moto/load-obj.js @@ -6,19 +6,27 @@ if (self.moto.OBJ) return; self.moto.OBJ = { - parse : parse + parse, + parseAsync }; /** * @param {String} text * @returns {Array} vertex face array */ + + function parseAsync(text) { + return new Promise((resolve,reject) => { + resolve(parse(text)); + }); + } + function parse(text) { let lines = text.split('\n').map(l => l.trim()); let verts = []; let faces = []; - let obj = [ faces ]; + let objs = [ faces ]; for (let line of lines) { let toks = line.split(' '); @@ -35,7 +43,7 @@ break; case 'g': if (faces.length) { - obj.push(faces = []); + objs.push(faces = []); } if (toks[0]) { faces.name = toks[0]; @@ -44,7 +52,7 @@ } } - return obj; + return objs; } })(); diff --git a/src/moto/load-stl.js b/src/moto/load-stl.js index e97a1c1c..2e3f2a43 100644 --- a/src/moto/load-stl.js +++ b/src/moto/load-stl.js @@ -107,6 +107,11 @@ return bin; }; + /** + * @param {ArrayBuffer} data + * @param {number} scale + * @returns vertex array + */ SP.parse = function(data, scale) { let binData = this.convertToBinary(data); @@ -124,6 +129,12 @@ : this.parseASCII(this.convertToString(data), scale); }; + SP.parseAsync = function(data, scale) { + return new Promise((resolve, reject) => { + resolve(this.parse(data, scale)); + }); + }; + SP.parseBinary = function(data, scale = 1) { let reader = new DataView(data), faces = reader.getUint32 (80, true), diff --git a/src/moto/load-url.js b/src/moto/load-url.js new file mode 100644 index 00000000..8a5553e3 --- /dev/null +++ b/src/moto/load-url.js @@ -0,0 +1,81 @@ +'use strict'; + +(function() { + if (!self.moto) self.moto = {}; + if (self.moto.URL) return; + + const CDH = 'Content-Disposition'; + + function load(url, options = {}) { + return new Promise((resolve, reject) => { + let xhr = new XMLHttpRequest(); + let file = (((url.split('?')[0]).split('#')[0]).split('/')).pop(); + let ext = file.split('.').pop().toLowerCase(); + let deftype = ext === "obj" ? "text" : "arraybuffer"; + let datatype = options.datatype || deftype; + let formdata = options.formdata; + + function onloaded(event) { + if (event.target.status === 200 || event.target.status === 0) { + if (xhr.getAllResponseHeaders().indexOf(CDH) > 0) { + // attempt to extract filename from content disposition + let fname = xhr.getResponseHeader(CDH) + .split(';').map(v => v.trim()).filter(v => { + return v.indexOf('filename=') === 0; + }).map(v => { + return v.substring(10,v.length-1); + })[0]; + if (fname) { + file = fname; + ext = file.split('.').pop().toLowerCase(); + } + } + let data = event.target.response || event.target.responseText; + // return raw data if indicated + if (options.parse === false) { + return resolve(data, ext); + } + switch (ext) { + case "stl": + resolve([{ + mesh: new moto.STL().parse(data), file + }]); + break; + case "obj": + resolve(moto.OBJ.parse(data).map(m => { return {mesh: m.toFloat32(), file} })); + break; + case "3mf": + moto.TMF.parseAsync(data).then((meshes) => { + resolve(meshes.map(m => { + return { mesh: m.faces.toFloat32(), file } + })); + }); + break; + default: + reject(`unknown file type: "${ext}" from ${url}`); + break; + } + } else { + reject(event.target.statusText); + } + } + + xhr.addEventListener('load', onloaded, false); + xhr.addEventListener('progress', function (event) { }, false); + xhr.addEventListener('error', function () { }, false); + + if (xhr.overrideMimeType) { + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + } + + xhr.open(formdata ? 'POST' : 'GET', url, true); + xhr.responseType = datatype; + xhr.send(formdata); + }); + }; + + self.moto.URL = { + load + }; + +})(); \ No newline at end of file