rehome loaders as module
This commit is contained in:
parent
a16986ef9d
commit
d769111380
20 changed files with 699 additions and 690 deletions
52
app.js
52
app.js
|
|
@ -10,7 +10,7 @@ const fs = require('fs');
|
||||||
const uglify = require('uglify-es');
|
const uglify = require('uglify-es');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const agent = require('express-useragent');
|
const agent = require('express-useragent');
|
||||||
const license = require_fresh('./src/add/license.js');
|
const license = require_fresh('./src/moto/license.js');
|
||||||
const version = license.VERSION || "rogue";
|
const version = license.VERSION || "rogue";
|
||||||
|
|
||||||
const fileCache = {};
|
const fileCache = {};
|
||||||
|
|
@ -227,7 +227,7 @@ const script = {
|
||||||
"ext/three-bgu",
|
"ext/three-bgu",
|
||||||
"ext/three-svg",
|
"ext/three-svg",
|
||||||
"ext/jszip",
|
"ext/jszip",
|
||||||
"add/license",
|
"moto/license",
|
||||||
"ext/clip2",
|
"ext/clip2",
|
||||||
"ext/tween",
|
"ext/tween",
|
||||||
"ext/fsave",
|
"ext/fsave",
|
||||||
|
|
@ -251,12 +251,12 @@ const script = {
|
||||||
"moto/ctrl",
|
"moto/ctrl",
|
||||||
"moto/pack",
|
"moto/pack",
|
||||||
"moto/space",
|
"moto/space",
|
||||||
"moto/load-3mf",
|
"load/3mf",
|
||||||
"moto/load-obj",
|
"load/obj",
|
||||||
"moto/load-stl",
|
"load/stl",
|
||||||
"moto/load-svg",
|
"load/svg",
|
||||||
"moto/load-url",
|
"load/url",
|
||||||
"moto/load-file",
|
"load/file",
|
||||||
"moto/broker",
|
"moto/broker",
|
||||||
"moto/idb",
|
"moto/idb",
|
||||||
"kiri/ui",
|
"kiri/ui",
|
||||||
|
|
@ -295,7 +295,7 @@ const script = {
|
||||||
"ext/three",
|
"ext/three",
|
||||||
"ext/pngjs",
|
"ext/pngjs",
|
||||||
"ext/jszip",
|
"ext/jszip",
|
||||||
"add/license",
|
"moto/license",
|
||||||
"ext/clip2",
|
"ext/clip2",
|
||||||
"ext/earcut",
|
"ext/earcut",
|
||||||
"add/array",
|
"add/array",
|
||||||
|
|
@ -344,7 +344,7 @@ const script = {
|
||||||
],
|
],
|
||||||
minion : [
|
minion : [
|
||||||
"main/kiri",
|
"main/kiri",
|
||||||
"add/license",
|
"moto/license",
|
||||||
"ext/clip2",
|
"ext/clip2",
|
||||||
"ext/earcut",
|
"ext/earcut",
|
||||||
"add/array",
|
"add/array",
|
||||||
|
|
@ -370,7 +370,7 @@ const script = {
|
||||||
],
|
],
|
||||||
engine : [
|
engine : [
|
||||||
"main/kiri",
|
"main/kiri",
|
||||||
"add/license",
|
"moto/license",
|
||||||
"ext/three",
|
"ext/three",
|
||||||
"add/array",
|
"add/array",
|
||||||
"add/three",
|
"add/three",
|
||||||
|
|
@ -385,8 +385,8 @@ const script = {
|
||||||
"geo/polygons",
|
"geo/polygons",
|
||||||
"moto/kv",
|
"moto/kv",
|
||||||
"moto/broker",
|
"moto/broker",
|
||||||
"moto/load-obj",
|
"load/obj",
|
||||||
"moto/load-stl",
|
"load/stl",
|
||||||
"kiri/conf",
|
"kiri/conf",
|
||||||
"kiri/client",
|
"kiri/client",
|
||||||
"kiri/slice",
|
"kiri/slice",
|
||||||
|
|
@ -400,7 +400,7 @@ const script = {
|
||||||
],
|
],
|
||||||
meta : [
|
meta : [
|
||||||
"ext/three",
|
"ext/three",
|
||||||
"add/license",
|
"moto/license",
|
||||||
"ext/tween",
|
"ext/tween",
|
||||||
"ext/fsave",
|
"ext/fsave",
|
||||||
"ext/earcut",
|
"ext/earcut",
|
||||||
|
|
@ -419,38 +419,38 @@ const script = {
|
||||||
"moto/ajax",
|
"moto/ajax",
|
||||||
"moto/ctrl",
|
"moto/ctrl",
|
||||||
"moto/space",
|
"moto/space",
|
||||||
"moto/load-obj",
|
"load/obj",
|
||||||
"moto/load-stl",
|
"load/stl",
|
||||||
"moto/idb",
|
"moto/idb",
|
||||||
"moto/ui",
|
"moto/ui",
|
||||||
"kiri/catalog",
|
"kiri/catalog",
|
||||||
"main/meta"
|
"main/meta"
|
||||||
],
|
],
|
||||||
mesh : [
|
mesh : [
|
||||||
"main/mesh",
|
|
||||||
"moto/client",
|
"moto/client",
|
||||||
|
"moto/license",
|
||||||
"ext/three",
|
"ext/three",
|
||||||
"ext/three-bgu",
|
"ext/three-bgu",
|
||||||
"ext/three-svg",
|
"ext/three-svg",
|
||||||
"add/license",
|
|
||||||
"ext/tween",
|
"ext/tween",
|
||||||
"ext/fsave",
|
"ext/fsave",
|
||||||
"add/array",
|
"add/array",
|
||||||
"add/three",
|
"add/three",
|
||||||
"moto/ctrl",
|
"moto/ctrl",
|
||||||
"moto/space",
|
"moto/space",
|
||||||
"moto/load-3mf",
|
"load/3mf",
|
||||||
"moto/load-obj",
|
"load/obj",
|
||||||
"moto/load-stl",
|
"load/stl",
|
||||||
"moto/load-svg",
|
"load/svg",
|
||||||
"moto/load-url",
|
"load/url",
|
||||||
"moto/load-file",
|
"load/file",
|
||||||
"moto/broker",
|
"moto/broker",
|
||||||
"moto/idb"
|
"moto/idb",
|
||||||
|
"main/mesh"
|
||||||
],
|
],
|
||||||
mesh_work : [
|
mesh_work : [
|
||||||
"moto/worker",
|
"moto/worker",
|
||||||
"add/license",
|
"moto/license",
|
||||||
"ext/jszip",
|
"ext/jszip",
|
||||||
"ext/clip2",
|
"ext/clip2",
|
||||||
"ext/earcut",
|
"ext/earcut",
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,11 @@
|
||||||
|
|
||||||
if (self.kiri.init) return;
|
if (self.kiri.init) return;
|
||||||
|
|
||||||
const KIRI = self.kiri,
|
let KIRI = self.kiri,
|
||||||
BASE = self.base,
|
BASE = self.base,
|
||||||
MOTO = self.moto,
|
|
||||||
CONF = KIRI.conf,
|
CONF = KIRI.conf,
|
||||||
WIN = self.window,
|
WIN = self.window,
|
||||||
DOC = self.document,
|
DOC = self.document,
|
||||||
LOC = self.location,
|
|
||||||
API = KIRI.api,
|
API = KIRI.api,
|
||||||
SDB = API.sdb,
|
SDB = API.sdb,
|
||||||
UI = API.ui,
|
UI = API.ui,
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
|
|
||||||
let MOTO = moto,
|
let MOTO = self.moto,
|
||||||
KIRI = self.kiri,
|
KIRI = self.kiri,
|
||||||
|
LOAD = self.load,
|
||||||
BASE = self.base,
|
BASE = self.base,
|
||||||
UTIL = BASE.util,
|
UTIL = BASE.util,
|
||||||
LANG = KIRI.lang.current,
|
LANG = KIRI.lang.current,
|
||||||
|
|
@ -912,7 +913,7 @@
|
||||||
} else {
|
} else {
|
||||||
// dialog
|
// dialog
|
||||||
$('load-file').onchange = function(event) {
|
$('load-file').onchange = function(event) {
|
||||||
MOTO.File.load(event.target.files[0])
|
LOAD.File.load(event.target.files[0])
|
||||||
.then(data => onload(data[0].mesh))
|
.then(data => onload(data[0].mesh))
|
||||||
.catch(error => console.log({error}));
|
.catch(error => console.log({error}));
|
||||||
};
|
};
|
||||||
|
|
@ -2040,7 +2041,7 @@
|
||||||
|
|
||||||
function platformLoadSTL(url, onload, formdata) {
|
function platformLoadSTL(url, onload, formdata) {
|
||||||
let scale = 1 / unitScale();
|
let scale = 1 / unitScale();
|
||||||
new MOTO.STL().load(url, function(vertices, filename) {
|
new LOAD.STL().load(url, function(vertices, filename) {
|
||||||
if (vertices) {
|
if (vertices) {
|
||||||
let widget = newWidget().loadVertices(vertices);
|
let widget = newWidget().loadVertices(vertices);
|
||||||
widget.meta.file = filename;
|
widget.meta.file = filename;
|
||||||
|
|
@ -2054,7 +2055,7 @@
|
||||||
|
|
||||||
function platformLoadURL(url, options = {}) {
|
function platformLoadURL(url, options = {}) {
|
||||||
platform.group();
|
platform.group();
|
||||||
MOTO.URL.load(url, options).then((objects) => {
|
LOAD.URL.load(url, options).then((objects) => {
|
||||||
let widgets = [];
|
let widgets = [];
|
||||||
for (let object of objects) {
|
for (let object of objects) {
|
||||||
let widget = newWidget(undefined, options.group).loadVertices(object.mesh);
|
let widget = newWidget(undefined, options.group).loadVertices(object.mesh);
|
||||||
|
|
@ -2705,14 +2706,14 @@
|
||||||
} else {
|
} else {
|
||||||
platform.add(
|
platform.add(
|
||||||
newWidget(undefined,group)
|
newWidget(undefined,group)
|
||||||
.loadVertices(new MOTO.STL().parse(e.target.result,unitScale()))
|
.loadVertices(new LOAD.STL().parse(e.target.result,unitScale()))
|
||||||
.saveToCatalog(e.target.file.name)
|
.saveToCatalog(e.target.file.name)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
load_dec();
|
load_dec();
|
||||||
}
|
}
|
||||||
else if (isobj) {
|
else if (isobj) {
|
||||||
let objs = MOTO.OBJ.parse(e.target.result);
|
let objs = LOAD.OBJ.parse(e.target.result);
|
||||||
let odon = function() {
|
let odon = function() {
|
||||||
for (let obj of objs) {
|
for (let obj of objs) {
|
||||||
let name = e.target.file.name;
|
let name = e.target.file.name;
|
||||||
|
|
@ -2756,7 +2757,7 @@
|
||||||
API.hide.alert(msg);
|
API.hide.alert(msg);
|
||||||
}
|
}
|
||||||
let msg = API.show.alert('Decoding 3MF');
|
let msg = API.show.alert('Decoding 3MF');
|
||||||
MOTO.TMF.parseAsync(e.target.result).then(models => {
|
LOAD.TMF.parseAsync(e.target.result).then(models => {
|
||||||
API.hide.alert(msg);
|
API.hide.alert(msg);
|
||||||
if (models.length > 1 && !group) {
|
if (models.length > 1 && !group) {
|
||||||
UC.confirm(`group ${models.length} objects?`).then(ok => {
|
UC.confirm(`group ${models.length} objects?`).then(ok => {
|
||||||
|
|
@ -2779,7 +2780,7 @@
|
||||||
loadCode(e.target.result, 'svg');
|
loadCode(e.target.result, 'svg');
|
||||||
} else {
|
} else {
|
||||||
let name = e.target.file.name;
|
let name = e.target.file.name;
|
||||||
let svg = MOTO.SVG.parse(e.target.result);
|
let svg = LOAD.SVG.parse(e.target.result);
|
||||||
let ind = 0;
|
let ind = 0;
|
||||||
for (let v of svg) {
|
for (let v of svg) {
|
||||||
let num = ind++;
|
let num = ind++;
|
||||||
|
|
|
||||||
112
src/load/3mf.js
Normal file
112
src/load/3mf.js
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
let load = self.load = self.load || {};
|
||||||
|
if (load.TMF) return;
|
||||||
|
|
||||||
|
load.TMF = {
|
||||||
|
parseAsync
|
||||||
|
};
|
||||||
|
|
||||||
|
function query(node, path, fn) {
|
||||||
|
let collect = false;
|
||||||
|
let match = path[0];
|
||||||
|
if (match[0] === '+') {
|
||||||
|
match = match.slice(1);
|
||||||
|
collect = true;
|
||||||
|
}
|
||||||
|
for (let child of [...node.childNodes]) {
|
||||||
|
if (child.tagName === match) {
|
||||||
|
if (collect) {
|
||||||
|
fn(match, child);
|
||||||
|
}
|
||||||
|
if (path.length > 1) {
|
||||||
|
query(child, path.slice(1), fn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadModel(doc) {
|
||||||
|
let models = [];
|
||||||
|
function emitModel(model, faces) {
|
||||||
|
if (faces && faces.length) {
|
||||||
|
models.push({name: model, faces});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let model;
|
||||||
|
let faces;
|
||||||
|
let units;
|
||||||
|
let scale = 1;
|
||||||
|
let scaleMap = {
|
||||||
|
"inch": (1 / 25.4),
|
||||||
|
"foot": (1 / 304.8),
|
||||||
|
"micron": (1 / 1000),
|
||||||
|
"meter": 1000,
|
||||||
|
"millimeter": 1,
|
||||||
|
"centimeter": (1 / 10)
|
||||||
|
};
|
||||||
|
|
||||||
|
query(doc, ["+model","resources","+object","+mesh"], (type, node) => {
|
||||||
|
switch (type) {
|
||||||
|
case "model":
|
||||||
|
units = node.getAttribute("unit");
|
||||||
|
if (units) {
|
||||||
|
scale = scaleMap[units] || 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "object":
|
||||||
|
emitModel(model, faces);
|
||||||
|
faces = [];
|
||||||
|
model = node.getAttribute("name") || undefined;
|
||||||
|
break;
|
||||||
|
case "mesh":
|
||||||
|
let vertices = [];
|
||||||
|
query(node, ["vertices","+vertex"], (type, vertex) => {
|
||||||
|
vertices.push([
|
||||||
|
parseFloat(vertex.getAttribute("x")) * scale,
|
||||||
|
parseFloat(vertex.getAttribute("y")) * scale,
|
||||||
|
parseFloat(vertex.getAttribute("z")) * scale
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
query(node, ["triangles","+triangle"], (type, triangle) => {
|
||||||
|
let v1 = parseInt(triangle.getAttribute("v1"));
|
||||||
|
let v2 = parseInt(triangle.getAttribute("v2"));
|
||||||
|
let v3 = parseInt(triangle.getAttribute("v3"));
|
||||||
|
faces.appendAll(vertices[v1]);
|
||||||
|
faces.appendAll(vertices[v2]);
|
||||||
|
faces.appendAll(vertices[v3]);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
emitModel(model, faces);
|
||||||
|
resolve(models);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} data binary file
|
||||||
|
* @returns {Array} vertex face array
|
||||||
|
*/
|
||||||
|
function parseAsync(data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
JSZip.loadAsync(data).then(zip => {
|
||||||
|
for (let [key,value] of Object.entries(zip.files)) {
|
||||||
|
if (key.indexOf(".model") > 0) {
|
||||||
|
value.async("string").then(xml => {
|
||||||
|
resolve(loadModel(new DOMParser().parseFromString(xml, "text/xml")));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
63
src/load/file.js
Normal file
63
src/load/file.js
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
let load = self.load = self.load || {};
|
||||||
|
if (load.File) return;
|
||||||
|
|
||||||
|
load.File = {
|
||||||
|
load_data, load_data,
|
||||||
|
load: load_file
|
||||||
|
};
|
||||||
|
|
||||||
|
function load_data(data, file, ext) {
|
||||||
|
ext = ext || name.toLowerCase().split('.').pop();
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
switch (ext) {
|
||||||
|
case "stl":
|
||||||
|
resolve([{
|
||||||
|
mesh: new load.STL().parse(data), file
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "obj":
|
||||||
|
resolve(load.OBJ.parse(data).map(m => { return {mesh: m.toFloat32(), file} }));
|
||||||
|
break;
|
||||||
|
case "3mf":
|
||||||
|
load.TMF.parseAsync(data).then((meshes) => {
|
||||||
|
resolve(meshes.map(m => {
|
||||||
|
return { mesh: m.faces.toFloat32(), file }
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "svg":
|
||||||
|
resolve(load.SVG.parse(data).map(m => { return {mesh: m.toFloat32(), file} }));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reject(`unknown file type: "${ext}" from ${url}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_file(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let reader = new FileReader();
|
||||||
|
let name = file.name;
|
||||||
|
let ext = name.toLowerCase().split('.').pop();
|
||||||
|
reader.file = file;
|
||||||
|
reader.onloadend = function (event) {
|
||||||
|
load_data(event.target.result, name, ext)
|
||||||
|
.then(data => resolve(data))
|
||||||
|
.catch(e => reject(e));
|
||||||
|
};
|
||||||
|
if (ext === 'stl') {
|
||||||
|
reader.readAsArrayBuffer(reader.file);
|
||||||
|
} else {
|
||||||
|
reader.readAsBinaryString(reader.file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
64
src/load/obj.js
Normal file
64
src/load/obj.js
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
let load = self.load = self.load || {};
|
||||||
|
if (load.OBJ) return;
|
||||||
|
|
||||||
|
load.OBJ = {
|
||||||
|
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 objs = [ faces ];
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
let toks = line.split(' ');
|
||||||
|
switch (toks.shift()) {
|
||||||
|
case 'v':
|
||||||
|
let v = toks.map(v => parseFloat(v)).slice(0,3);
|
||||||
|
if (v.length < 3) {
|
||||||
|
console.log('??',toks,line);
|
||||||
|
}
|
||||||
|
verts.push(v);
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
let tok = toks.map(f => parseInt(f.split('/')[0]));
|
||||||
|
// add support for negative indices (offset from last vertex array point)
|
||||||
|
faces.appendAll(verts[tok[0]-1]);
|
||||||
|
faces.appendAll(verts[tok[1]-1]);
|
||||||
|
faces.appendAll(verts[tok[2]-1]);
|
||||||
|
break;
|
||||||
|
case 'g':
|
||||||
|
if (faces.length) {
|
||||||
|
objs.push(faces = []);
|
||||||
|
}
|
||||||
|
if (toks[0]) {
|
||||||
|
faces.name = toks[0];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return objs;
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
271
src/load/stl.js
Normal file
271
src/load/stl.js
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
let load = self.load = self.load || {};
|
||||||
|
if (load.STL) return;
|
||||||
|
|
||||||
|
const SP = STL.prototype;
|
||||||
|
const CDH = 'Content-Disposition';
|
||||||
|
|
||||||
|
load.STL = STL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function STL() {
|
||||||
|
this.vertices = null;
|
||||||
|
this.normals = null;
|
||||||
|
this.colors = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
SP.load = function(url, callback, formdata, scale) {
|
||||||
|
let stl = this,
|
||||||
|
xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
function onloaded (event) {
|
||||||
|
if (event.target.status === 200 || event.target.status === 0) {
|
||||||
|
stl.parse(event.target.response || event.target.responseText, scale);
|
||||||
|
let cd = undefined;
|
||||||
|
if (xhr.getAllResponseHeaders().indexOf(CDH) > 0) {
|
||||||
|
cd = 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 (callback) callback(stl.vertices, cd);
|
||||||
|
} else {
|
||||||
|
if (callback) callback(null, 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 = 'arraybuffer';
|
||||||
|
xhr.send(formdata);
|
||||||
|
};
|
||||||
|
|
||||||
|
SP.encode = function(vertices, normals) {
|
||||||
|
if (!(vertices && vertices.length % 3 === 0)) throw "invalid vertices";
|
||||||
|
|
||||||
|
let vc = vertices.length / 3,
|
||||||
|
bs = (vc * 16) + (vc * (2/3)) + 84,
|
||||||
|
bin = new ArrayBuffer(bs),
|
||||||
|
writer = new DataView(bin),
|
||||||
|
i = 0,
|
||||||
|
j = 0,
|
||||||
|
pos = 80;
|
||||||
|
|
||||||
|
function writeInt16(val) {
|
||||||
|
writer.setUint16(pos, val, true);
|
||||||
|
pos += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeInt32(val) {
|
||||||
|
writer.setUint32(pos, val, true);
|
||||||
|
pos += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFloat(val) {
|
||||||
|
writer.setFloat32(pos, val, true);
|
||||||
|
pos += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeVertex() {
|
||||||
|
writeFloat(vertices[i++]); // x
|
||||||
|
writeFloat(vertices[i++]); // y
|
||||||
|
writeFloat(vertices[i++]); // z
|
||||||
|
}
|
||||||
|
|
||||||
|
writeInt32(vc / 3);
|
||||||
|
while (i < vertices.length) {
|
||||||
|
writeFloat(normals ? normals[j++] : 0); // norm x
|
||||||
|
writeFloat(normals ? normals[j++] : 0); // norm y
|
||||||
|
writeFloat(normals ? normals[j++] : 0); // norm z
|
||||||
|
writeVertex(); // p1
|
||||||
|
writeVertex(); // p2
|
||||||
|
writeVertex(); // p3
|
||||||
|
writeInt16(0); // attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
return bin;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ArrayBuffer} data
|
||||||
|
* @param {number} scale
|
||||||
|
* @returns vertex array
|
||||||
|
*/
|
||||||
|
SP.parse = function(data, scale) {
|
||||||
|
let binData = this.convertToBinary(data);
|
||||||
|
|
||||||
|
let isBinary = function () {
|
||||||
|
let expect, face_size, n_faces, reader;
|
||||||
|
reader = new DataView(binData);
|
||||||
|
face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8);
|
||||||
|
n_faces = reader.getUint32(80,true);
|
||||||
|
expect = 80 + (32 / 8) + (n_faces * face_size);
|
||||||
|
return expect === reader.byteLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
return isBinary()
|
||||||
|
? this.parseBinary(binData, scale)
|
||||||
|
: 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),
|
||||||
|
r, g, b, hasColors = false, colors,
|
||||||
|
defaultR, defaultG, defaultB, alpha;
|
||||||
|
|
||||||
|
// check for default color in STL header ("COLOR=rgba" sequence).
|
||||||
|
for (let index = 0; index < 80 - 10; index++) {
|
||||||
|
if ((reader.getUint32(index, false) == 0x434F4C4F /*COLO*/) &&
|
||||||
|
(reader.getUint8(index + 4) == 0x52 /*'R'*/) &&
|
||||||
|
(reader.getUint8(index + 5) == 0x3D /*'='*/)) {
|
||||||
|
hasColors = true;
|
||||||
|
colors = new Float32Array(faces * 3 * 3);
|
||||||
|
defaultR = reader.getUint8(index + 6) / 255;
|
||||||
|
defaultG = reader.getUint8(index + 7) / 255;
|
||||||
|
defaultB = reader.getUint8(index + 8) / 255;
|
||||||
|
alpha = reader.getUint8(index + 9) / 255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let offset = 0,
|
||||||
|
dataOffset = 84,
|
||||||
|
faceLength = 12 * 4 + 2,
|
||||||
|
vertices = new Float32Array(faces * 3 * 3),
|
||||||
|
normals = new Float32Array(faces * 3 * 3);
|
||||||
|
|
||||||
|
colors = hasColors ? new Uint16Array(faces * 3 * 3) : null;
|
||||||
|
|
||||||
|
for (let face = 0; face < faces; face ++) {
|
||||||
|
|
||||||
|
let start = dataOffset + face * faceLength,
|
||||||
|
normalX = reader.getFloat32(start, true),
|
||||||
|
normalY = reader.getFloat32(start + 4, true),
|
||||||
|
normalZ = reader.getFloat32(start + 8, true);
|
||||||
|
|
||||||
|
if (hasColors) {
|
||||||
|
let packedColor = reader.getUint16(start + 48, true);
|
||||||
|
if ((packedColor & 0x8000) === 0) { // facet has its own unique color
|
||||||
|
r = (packedColor & 0x1F) / 31;
|
||||||
|
g = ((packedColor >> 5) & 0x1F) / 31;
|
||||||
|
b = ((packedColor >> 10) & 0x1F) / 31;
|
||||||
|
} else {
|
||||||
|
r = defaultR;
|
||||||
|
g = defaultG;
|
||||||
|
b = defaultB;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let i = 1, vertexstart;
|
||||||
|
|
||||||
|
while (i <= 3) {
|
||||||
|
vertexstart = start + (i++) * 12;
|
||||||
|
vertices[offset ] = reader.getFloat32 (vertexstart, true) * scale;
|
||||||
|
vertices[offset + 1] = reader.getFloat32 (vertexstart + 4, true) * scale;
|
||||||
|
vertices[offset + 2] = reader.getFloat32 (vertexstart + 8, true) * scale;
|
||||||
|
normals[offset ] = normalX;
|
||||||
|
normals[offset + 1] = normalY;
|
||||||
|
normals[offset + 2] = normalZ;
|
||||||
|
if (hasColors) {
|
||||||
|
colors[offset ] = r;
|
||||||
|
colors[offset + 1] = g;
|
||||||
|
colors[offset + 2] = b;
|
||||||
|
}
|
||||||
|
offset += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vertices = vertices;
|
||||||
|
this.normals = normals;
|
||||||
|
this.colors = colors;
|
||||||
|
|
||||||
|
return vertices;
|
||||||
|
};
|
||||||
|
|
||||||
|
SP.parseASCII = function(data, scale = 1) {
|
||||||
|
let result,
|
||||||
|
resultText,
|
||||||
|
patternNormal,
|
||||||
|
patternVertex,
|
||||||
|
vertices = [],
|
||||||
|
normals = [],
|
||||||
|
patternFace = /facet([\s\S]*?)endfacet/g;
|
||||||
|
|
||||||
|
while ((result = patternFace.exec(data)) !== null) {
|
||||||
|
resultText = result[0];
|
||||||
|
patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;
|
||||||
|
patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;
|
||||||
|
while ((result = patternNormal.exec(resultText)) !== null) {
|
||||||
|
normals.push(parseFloat(result[1]));
|
||||||
|
normals.push(parseFloat(result[3]));
|
||||||
|
normals.push(parseFloat(result[5]));
|
||||||
|
}
|
||||||
|
while ((result = patternVertex.exec(resultText)) !== null) {
|
||||||
|
vertices.push(parseFloat(result[1]) * scale);
|
||||||
|
vertices.push(parseFloat(result[3]) * scale);
|
||||||
|
vertices.push(parseFloat(result[5]) * scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let vToFloat32 = new Float32Array(vertices.length),
|
||||||
|
nToFloat32 = new Float32Array(normals.length),
|
||||||
|
i;
|
||||||
|
|
||||||
|
for (i=0; i<vertices.length; i++) vToFloat32[i] = vertices[i];
|
||||||
|
for (i=0; i<normals.length; i++) nToFloat32[i] = normals[i];
|
||||||
|
|
||||||
|
this.vertices = vToFloat32;
|
||||||
|
this.normals = nToFloat32;
|
||||||
|
|
||||||
|
return vToFloat32;
|
||||||
|
};
|
||||||
|
|
||||||
|
SP.convertToString = function (buf) {
|
||||||
|
if (typeof buf !== "string") {
|
||||||
|
let array_buffer = new Uint8Array(buf);
|
||||||
|
let str = '';
|
||||||
|
for (let i = 0; i < buf.byteLength; i++) {
|
||||||
|
str += String.fromCharCode(array_buffer[i]);
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
} else {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
SP.convertToBinary = function (buf) {
|
||||||
|
if (typeof buf === "string") {
|
||||||
|
let array_buffer = new Uint8Array(buf.length);
|
||||||
|
for (let i = 0; i < buf.length; i++) {
|
||||||
|
array_buffer[i] = buf.charCodeAt(i) & 0xff;
|
||||||
|
}
|
||||||
|
return array_buffer.buffer || array_buffer;
|
||||||
|
} else {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
54
src/load/svg.js
Normal file
54
src/load/svg.js
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
let load = self.load = self.load || {};
|
||||||
|
if (load.SVG) return;
|
||||||
|
|
||||||
|
load.SVG = {
|
||||||
|
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 faces = [ ];
|
||||||
|
let objs = [ faces ];
|
||||||
|
|
||||||
|
let data = new THREE.SVGLoader().parse(text);
|
||||||
|
let paths = data.paths;
|
||||||
|
|
||||||
|
for (let i = 0; i < paths.length; i++) {
|
||||||
|
let path = paths[i];
|
||||||
|
let shapes = path.toShapes(true);
|
||||||
|
let geom = new THREE.ExtrudeGeometry(shapes, {
|
||||||
|
steps: 1,
|
||||||
|
depth: 5,
|
||||||
|
bevelEnabled: false
|
||||||
|
});
|
||||||
|
let array = geom.attributes.position.array;
|
||||||
|
// invert y
|
||||||
|
for (let i=0; i<array.length; i+=3) {
|
||||||
|
array[i+1] = -array[i+1];
|
||||||
|
}
|
||||||
|
// objs.push(geom.attributes.position.array);
|
||||||
|
faces.appendAll(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
return objs;
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
87
src/load/url.js
Normal file
87
src/load/url.js
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
let load = self.load = self.load || {};
|
||||||
|
if (load.URL) return;
|
||||||
|
|
||||||
|
load.URL = {
|
||||||
|
load: load_url
|
||||||
|
};
|
||||||
|
|
||||||
|
const CDH = 'Content-Disposition';
|
||||||
|
|
||||||
|
function load_url(url, options = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
let file = options.file || options.filename || (((url.split('?')[0]).split('#')[0]).split('/')).pop();
|
||||||
|
let ext = file.split('.').pop().toLowerCase();
|
||||||
|
let deftype = ext === "obj" || ext === 'svg' ? "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 load.STL().parse(data), file
|
||||||
|
}]);
|
||||||
|
break;
|
||||||
|
case "obj":
|
||||||
|
resolve(load.OBJ.parse(data).map(m => { return {mesh: m.toFloat32(), file} }));
|
||||||
|
break;
|
||||||
|
case "3mf":
|
||||||
|
load.TMF.parseAsync(data).then((meshes) => {
|
||||||
|
resolve(meshes.map(m => {
|
||||||
|
return { mesh: m.faces.toFloat32(), file }
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "svg":
|
||||||
|
resolve(load.SVG.parse(data).map(m => { return {mesh: m.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);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// required for license and other grid app dependencies
|
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||||
self.gapp = self.gapp || {};
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
function $(id) {
|
function $(id) {
|
||||||
return document.getElementById(id);
|
return document.getElementById(id);
|
||||||
|
|
@ -32,12 +33,13 @@ function init() {
|
||||||
space.sky.showGrid(sky);
|
space.sky.showGrid(sky);
|
||||||
space.sky.setColor(dark ? 0 : 0xffffff);
|
space.sky.setColor(dark ? 0 : 0xffffff);
|
||||||
space.init($('container'), delta => { }, ortho);
|
space.init($('container'), delta => { }, ortho);
|
||||||
platform.onMove(delta => { } );
|
platform.onMove(delta => { });
|
||||||
platform.setRound(false);
|
platform.setRound(false);
|
||||||
platform.setZOff(0.2);
|
platform.setZOff(0.2);
|
||||||
platform.setSize(300,300,2.5,300);
|
platform.setSize(300,300,2.5,300);
|
||||||
platform.setGrid(25,5,0x999999,0xcccccc);
|
platform.setGrid(25,5,0x999999,0xcccccc);
|
||||||
platform.setColor(0xcccccc);
|
platform.setColor(0xcccccc);
|
||||||
|
platform.showVolume(false);
|
||||||
platform.opacity(0.3);
|
platform.opacity(0.3);
|
||||||
space.view.setZoom(zoomrev, zoomspd);
|
space.view.setZoom(zoomrev, zoomspd);
|
||||||
space.useDefaultKeys(true);
|
space.useDefaultKeys(true);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
let moto = self.moto = self.moto || {},
|
let gapp = self.gapp = self.gapp || {},
|
||||||
|
moto = self.moto = self.moto || {},
|
||||||
time = function() { return new Date().getTime() },
|
time = function() { return new Date().getTime() },
|
||||||
restarting = false,
|
restarting = false,
|
||||||
running = {},
|
running = {},
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
let moto = self.moto = self.moto || {};
|
|
||||||
if (moto.TMF) return;
|
|
||||||
|
|
||||||
moto.TMF = {
|
|
||||||
parseAsync
|
|
||||||
};
|
|
||||||
|
|
||||||
function query(node, path, fn) {
|
|
||||||
let collect = false;
|
|
||||||
let match = path[0];
|
|
||||||
if (match[0] === '+') {
|
|
||||||
match = match.slice(1);
|
|
||||||
collect = true;
|
|
||||||
}
|
|
||||||
for (let child of [...node.childNodes]) {
|
|
||||||
if (child.tagName === match) {
|
|
||||||
if (collect) {
|
|
||||||
fn(match, child);
|
|
||||||
}
|
|
||||||
if (path.length > 1) {
|
|
||||||
query(child, path.slice(1), fn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadModel(doc) {
|
|
||||||
let models = [];
|
|
||||||
function emitModel(model, faces) {
|
|
||||||
if (faces && faces.length) {
|
|
||||||
models.push({name: model, faces});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let model;
|
|
||||||
let faces;
|
|
||||||
let units;
|
|
||||||
let scale = 1;
|
|
||||||
let scaleMap = {
|
|
||||||
"inch": (1 / 25.4),
|
|
||||||
"foot": (1 / 304.8),
|
|
||||||
"micron": (1 / 1000),
|
|
||||||
"meter": 1000,
|
|
||||||
"millimeter": 1,
|
|
||||||
"centimeter": (1 / 10)
|
|
||||||
};
|
|
||||||
|
|
||||||
query(doc, ["+model","resources","+object","+mesh"], (type, node) => {
|
|
||||||
switch (type) {
|
|
||||||
case "model":
|
|
||||||
units = node.getAttribute("unit");
|
|
||||||
if (units) {
|
|
||||||
scale = scaleMap[units] || 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "object":
|
|
||||||
emitModel(model, faces);
|
|
||||||
faces = [];
|
|
||||||
model = node.getAttribute("name") || undefined;
|
|
||||||
break;
|
|
||||||
case "mesh":
|
|
||||||
let vertices = [];
|
|
||||||
query(node, ["vertices","+vertex"], (type, vertex) => {
|
|
||||||
vertices.push([
|
|
||||||
parseFloat(vertex.getAttribute("x")) * scale,
|
|
||||||
parseFloat(vertex.getAttribute("y")) * scale,
|
|
||||||
parseFloat(vertex.getAttribute("z")) * scale
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
query(node, ["triangles","+triangle"], (type, triangle) => {
|
|
||||||
let v1 = parseInt(triangle.getAttribute("v1"));
|
|
||||||
let v2 = parseInt(triangle.getAttribute("v2"));
|
|
||||||
let v3 = parseInt(triangle.getAttribute("v3"));
|
|
||||||
faces.appendAll(vertices[v1]);
|
|
||||||
faces.appendAll(vertices[v2]);
|
|
||||||
faces.appendAll(vertices[v3]);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
emitModel(model, faces);
|
|
||||||
resolve(models);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} data binary file
|
|
||||||
* @returns {Array} vertex face array
|
|
||||||
*/
|
|
||||||
function parseAsync(data) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
JSZip.loadAsync(data).then(zip => {
|
|
||||||
for (let [key,value] of Object.entries(zip.files)) {
|
|
||||||
if (key.indexOf(".model") > 0) {
|
|
||||||
value.async("string").then(xml => {
|
|
||||||
resolve(loadModel(new DOMParser().parseFromString(xml, "text/xml")));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
let moto = self.moto = self.moto || {};
|
|
||||||
if (moto.File) return;
|
|
||||||
|
|
||||||
moto.File = {
|
|
||||||
load_data,
|
|
||||||
load
|
|
||||||
};
|
|
||||||
|
|
||||||
function load_data(data, file, ext) {
|
|
||||||
ext = ext || name.toLowerCase().split('.').pop();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
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;
|
|
||||||
case "svg":
|
|
||||||
resolve(moto.SVG.parse(data).map(m => { return {mesh: m.toFloat32(), file} }));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
reject(`unknown file type: "${ext}" from ${url}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function load(file) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let reader = new FileReader();
|
|
||||||
let name = file.name;
|
|
||||||
let ext = name.toLowerCase().split('.').pop();
|
|
||||||
reader.file = file;
|
|
||||||
reader.onloadend = function (event) {
|
|
||||||
load_data(event.target.result, name, ext)
|
|
||||||
.then(data => resolve(data))
|
|
||||||
.catch(e => reject(e));
|
|
||||||
};
|
|
||||||
if (ext === 'stl') {
|
|
||||||
reader.readAsArrayBuffer(reader.file);
|
|
||||||
} else {
|
|
||||||
reader.readAsBinaryString(reader.file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
let moto = self.moto = self.moto || {};
|
|
||||||
if (moto.OBJ) return;
|
|
||||||
|
|
||||||
moto.OBJ = {
|
|
||||||
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 objs = [ faces ];
|
|
||||||
|
|
||||||
for (let line of lines) {
|
|
||||||
let toks = line.split(' ');
|
|
||||||
switch (toks.shift()) {
|
|
||||||
case 'v':
|
|
||||||
let v = toks.map(v => parseFloat(v)).slice(0,3);
|
|
||||||
if (v.length < 3) {
|
|
||||||
console.log('??',toks,line);
|
|
||||||
}
|
|
||||||
verts.push(v);
|
|
||||||
break;
|
|
||||||
case 'f':
|
|
||||||
let tok = toks.map(f => parseInt(f.split('/')[0]));
|
|
||||||
// add support for negative indices (offset from last vertex array point)
|
|
||||||
faces.appendAll(verts[tok[0]-1]);
|
|
||||||
faces.appendAll(verts[tok[1]-1]);
|
|
||||||
faces.appendAll(verts[tok[2]-1]);
|
|
||||||
break;
|
|
||||||
case 'g':
|
|
||||||
if (faces.length) {
|
|
||||||
objs.push(faces = []);
|
|
||||||
}
|
|
||||||
if (toks[0]) {
|
|
||||||
faces.name = toks[0];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return objs;
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
@ -1,275 +0,0 @@
|
||||||
/**
|
|
||||||
* adapted from THREE.STL-LOADER example
|
|
||||||
*
|
|
||||||
* https://en.wikipedia.org/wiki/STL_(file_format)
|
|
||||||
*/
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
let moto = self.moto = self.moto || {};
|
|
||||||
if (moto.STL) return;
|
|
||||||
|
|
||||||
const SP = STL.prototype;
|
|
||||||
const CDH = 'Content-Disposition';
|
|
||||||
|
|
||||||
moto.STL = STL;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function STL() {
|
|
||||||
this.vertices = null;
|
|
||||||
this.normals = null;
|
|
||||||
this.colors = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
SP.load = function(url, callback, formdata, scale) {
|
|
||||||
let stl = this,
|
|
||||||
xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
function onloaded (event) {
|
|
||||||
if (event.target.status === 200 || event.target.status === 0) {
|
|
||||||
stl.parse(event.target.response || event.target.responseText, scale);
|
|
||||||
let cd = undefined;
|
|
||||||
if (xhr.getAllResponseHeaders().indexOf(CDH) > 0) {
|
|
||||||
cd = 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 (callback) callback(stl.vertices, cd);
|
|
||||||
} else {
|
|
||||||
if (callback) callback(null, 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 = 'arraybuffer';
|
|
||||||
xhr.send(formdata);
|
|
||||||
};
|
|
||||||
|
|
||||||
SP.encode = function(vertices, normals) {
|
|
||||||
if (!(vertices && vertices.length % 3 === 0)) throw "invalid vertices";
|
|
||||||
|
|
||||||
let vc = vertices.length / 3,
|
|
||||||
bs = (vc * 16) + (vc * (2/3)) + 84,
|
|
||||||
bin = new ArrayBuffer(bs),
|
|
||||||
writer = new DataView(bin),
|
|
||||||
i = 0,
|
|
||||||
j = 0,
|
|
||||||
pos = 80;
|
|
||||||
|
|
||||||
function writeInt16(val) {
|
|
||||||
writer.setUint16(pos, val, true);
|
|
||||||
pos += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeInt32(val) {
|
|
||||||
writer.setUint32(pos, val, true);
|
|
||||||
pos += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeFloat(val) {
|
|
||||||
writer.setFloat32(pos, val, true);
|
|
||||||
pos += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeVertex() {
|
|
||||||
writeFloat(vertices[i++]); // x
|
|
||||||
writeFloat(vertices[i++]); // y
|
|
||||||
writeFloat(vertices[i++]); // z
|
|
||||||
}
|
|
||||||
|
|
||||||
writeInt32(vc / 3);
|
|
||||||
while (i < vertices.length) {
|
|
||||||
writeFloat(normals ? normals[j++] : 0); // norm x
|
|
||||||
writeFloat(normals ? normals[j++] : 0); // norm y
|
|
||||||
writeFloat(normals ? normals[j++] : 0); // norm z
|
|
||||||
writeVertex(); // p1
|
|
||||||
writeVertex(); // p2
|
|
||||||
writeVertex(); // p3
|
|
||||||
writeInt16(0); // attributes
|
|
||||||
}
|
|
||||||
|
|
||||||
return bin;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {ArrayBuffer} data
|
|
||||||
* @param {number} scale
|
|
||||||
* @returns vertex array
|
|
||||||
*/
|
|
||||||
SP.parse = function(data, scale) {
|
|
||||||
let binData = this.convertToBinary(data);
|
|
||||||
|
|
||||||
let isBinary = function () {
|
|
||||||
let expect, face_size, n_faces, reader;
|
|
||||||
reader = new DataView(binData);
|
|
||||||
face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8);
|
|
||||||
n_faces = reader.getUint32(80,true);
|
|
||||||
expect = 80 + (32 / 8) + (n_faces * face_size);
|
|
||||||
return expect === reader.byteLength;
|
|
||||||
};
|
|
||||||
|
|
||||||
return isBinary()
|
|
||||||
? this.parseBinary(binData, scale)
|
|
||||||
: 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),
|
|
||||||
r, g, b, hasColors = false, colors,
|
|
||||||
defaultR, defaultG, defaultB, alpha;
|
|
||||||
|
|
||||||
// check for default color in STL header ("COLOR=rgba" sequence).
|
|
||||||
for (let index = 0; index < 80 - 10; index++) {
|
|
||||||
if ((reader.getUint32(index, false) == 0x434F4C4F /*COLO*/) &&
|
|
||||||
(reader.getUint8(index + 4) == 0x52 /*'R'*/) &&
|
|
||||||
(reader.getUint8(index + 5) == 0x3D /*'='*/)) {
|
|
||||||
hasColors = true;
|
|
||||||
colors = new Float32Array(faces * 3 * 3);
|
|
||||||
defaultR = reader.getUint8(index + 6) / 255;
|
|
||||||
defaultG = reader.getUint8(index + 7) / 255;
|
|
||||||
defaultB = reader.getUint8(index + 8) / 255;
|
|
||||||
alpha = reader.getUint8(index + 9) / 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let offset = 0,
|
|
||||||
dataOffset = 84,
|
|
||||||
faceLength = 12 * 4 + 2,
|
|
||||||
vertices = new Float32Array(faces * 3 * 3),
|
|
||||||
normals = new Float32Array(faces * 3 * 3);
|
|
||||||
|
|
||||||
colors = hasColors ? new Uint16Array(faces * 3 * 3) : null;
|
|
||||||
|
|
||||||
for (let face = 0; face < faces; face ++) {
|
|
||||||
|
|
||||||
let start = dataOffset + face * faceLength,
|
|
||||||
normalX = reader.getFloat32(start, true),
|
|
||||||
normalY = reader.getFloat32(start + 4, true),
|
|
||||||
normalZ = reader.getFloat32(start + 8, true);
|
|
||||||
|
|
||||||
if (hasColors) {
|
|
||||||
let packedColor = reader.getUint16(start + 48, true);
|
|
||||||
if ((packedColor & 0x8000) === 0) { // facet has its own unique color
|
|
||||||
r = (packedColor & 0x1F) / 31;
|
|
||||||
g = ((packedColor >> 5) & 0x1F) / 31;
|
|
||||||
b = ((packedColor >> 10) & 0x1F) / 31;
|
|
||||||
} else {
|
|
||||||
r = defaultR;
|
|
||||||
g = defaultG;
|
|
||||||
b = defaultB;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let i = 1, vertexstart;
|
|
||||||
|
|
||||||
while (i <= 3) {
|
|
||||||
vertexstart = start + (i++) * 12;
|
|
||||||
vertices[offset ] = reader.getFloat32 (vertexstart, true) * scale;
|
|
||||||
vertices[offset + 1] = reader.getFloat32 (vertexstart + 4, true) * scale;
|
|
||||||
vertices[offset + 2] = reader.getFloat32 (vertexstart + 8, true) * scale;
|
|
||||||
normals[offset ] = normalX;
|
|
||||||
normals[offset + 1] = normalY;
|
|
||||||
normals[offset + 2] = normalZ;
|
|
||||||
if (hasColors) {
|
|
||||||
colors[offset ] = r;
|
|
||||||
colors[offset + 1] = g;
|
|
||||||
colors[offset + 2] = b;
|
|
||||||
}
|
|
||||||
offset += 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.vertices = vertices;
|
|
||||||
this.normals = normals;
|
|
||||||
this.colors = colors;
|
|
||||||
|
|
||||||
return vertices;
|
|
||||||
};
|
|
||||||
|
|
||||||
SP.parseASCII = function(data, scale = 1) {
|
|
||||||
let result,
|
|
||||||
resultText,
|
|
||||||
patternNormal,
|
|
||||||
patternVertex,
|
|
||||||
vertices = [],
|
|
||||||
normals = [],
|
|
||||||
patternFace = /facet([\s\S]*?)endfacet/g;
|
|
||||||
|
|
||||||
while ((result = patternFace.exec(data)) !== null) {
|
|
||||||
resultText = result[0];
|
|
||||||
patternNormal = /normal[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;
|
|
||||||
patternVertex = /vertex[\s]+([\-+]?[0-9]+\.?[0-9]*([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+[\s]+([\-+]?[0-9]*\.?[0-9]+([eE][\-+]?[0-9]+)?)+/g;
|
|
||||||
while ((result = patternNormal.exec(resultText)) !== null) {
|
|
||||||
normals.push(parseFloat(result[1]));
|
|
||||||
normals.push(parseFloat(result[3]));
|
|
||||||
normals.push(parseFloat(result[5]));
|
|
||||||
}
|
|
||||||
while ((result = patternVertex.exec(resultText)) !== null) {
|
|
||||||
vertices.push(parseFloat(result[1]) * scale);
|
|
||||||
vertices.push(parseFloat(result[3]) * scale);
|
|
||||||
vertices.push(parseFloat(result[5]) * scale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let vToFloat32 = new Float32Array(vertices.length),
|
|
||||||
nToFloat32 = new Float32Array(normals.length),
|
|
||||||
i;
|
|
||||||
|
|
||||||
for (i=0; i<vertices.length; i++) vToFloat32[i] = vertices[i];
|
|
||||||
for (i=0; i<normals.length; i++) nToFloat32[i] = normals[i];
|
|
||||||
|
|
||||||
this.vertices = vToFloat32;
|
|
||||||
this.normals = nToFloat32;
|
|
||||||
|
|
||||||
return vToFloat32;
|
|
||||||
};
|
|
||||||
|
|
||||||
SP.convertToString = function (buf) {
|
|
||||||
if (typeof buf !== "string") {
|
|
||||||
let array_buffer = new Uint8Array(buf);
|
|
||||||
let str = '';
|
|
||||||
for (let i = 0; i < buf.byteLength; i++) {
|
|
||||||
str += String.fromCharCode(array_buffer[i]);
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
} else {
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SP.convertToBinary = function (buf) {
|
|
||||||
if (typeof buf === "string") {
|
|
||||||
let array_buffer = new Uint8Array(buf.length);
|
|
||||||
for (let i = 0; i < buf.length; i++) {
|
|
||||||
array_buffer[i] = buf.charCodeAt(i) & 0xff;
|
|
||||||
}
|
|
||||||
return array_buffer.buffer || array_buffer;
|
|
||||||
} else {
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
let moto = self.moto = self.moto || {};
|
|
||||||
if (moto.SVG) return;
|
|
||||||
|
|
||||||
moto.SVG = {
|
|
||||||
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 faces = [ ];
|
|
||||||
let objs = [ faces ];
|
|
||||||
|
|
||||||
let data = new THREE.SVGLoader().parse(text);
|
|
||||||
let paths = data.paths;
|
|
||||||
|
|
||||||
for (let i = 0; i < paths.length; i++) {
|
|
||||||
let path = paths[i];
|
|
||||||
let shapes = path.toShapes(true);
|
|
||||||
let geom = new THREE.ExtrudeGeometry(shapes, {
|
|
||||||
steps: 1,
|
|
||||||
depth: 5,
|
|
||||||
bevelEnabled: false
|
|
||||||
});
|
|
||||||
let array = geom.attributes.position.array;
|
|
||||||
// invert y
|
|
||||||
for (let i=0; i<array.length; i+=3) {
|
|
||||||
array[i+1] = -array[i+1];
|
|
||||||
}
|
|
||||||
// objs.push(geom.attributes.position.array);
|
|
||||||
faces.appendAll(array);
|
|
||||||
}
|
|
||||||
|
|
||||||
return objs;
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
@ -1,85 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
let moto = self.moto = self.moto || {};
|
|
||||||
if (moto.URL) return;
|
|
||||||
|
|
||||||
moto.URL = {
|
|
||||||
load
|
|
||||||
};
|
|
||||||
|
|
||||||
const CDH = 'Content-Disposition';
|
|
||||||
|
|
||||||
function load(url, options = {}) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
let xhr = new XMLHttpRequest();
|
|
||||||
let file = options.file || options.filename || (((url.split('?')[0]).split('#')[0]).split('/')).pop();
|
|
||||||
let ext = file.split('.').pop().toLowerCase();
|
|
||||||
let deftype = ext === "obj" || ext === 'svg' ? "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;
|
|
||||||
case "svg":
|
|
||||||
resolve(moto.SVG.parse(data).map(m => { return {mesh: m.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);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
})();
|
|
||||||
|
|
@ -340,9 +340,9 @@
|
||||||
function setRulers(
|
function setRulers(
|
||||||
xon = ruler.xon,
|
xon = ruler.xon,
|
||||||
yon = ruler.yon,
|
yon = ruler.yon,
|
||||||
factor = ruler.factor,
|
factor = ruler.factor || 1,
|
||||||
xl = ruler.xlabel,
|
xl = ruler.xlabel || 'X',
|
||||||
yl = ruler.ylabel)
|
yl = ruler.ylabel || 'Y')
|
||||||
{
|
{
|
||||||
if (xon !== ruler.xon || yon !== ruler.yon || factor !== ruler.factor || xl !== ruler.xlabel || yl !== ruler.ylabel) {
|
if (xon !== ruler.xon || yon !== ruler.yon || factor !== ruler.factor || xl !== ruler.xlabel || yl !== ruler.ylabel) {
|
||||||
ruler.factor = factor;
|
ruler.factor = factor;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
|
|
||||||
let moto = self.moto = self.moto || {},
|
let gapp = self.gapp = self.gapp || {},
|
||||||
|
moto = self.moto = self.moto || {},
|
||||||
time = Date.now;
|
time = Date.now;
|
||||||
|
|
||||||
if (moto.worker) return;
|
if (moto.worker) return;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue