initial commit
This commit is contained in:
commit
bd51ac38f0
90 changed files with 29458 additions and 0 deletions
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
persist
|
||||
cache
|
||||
mod
|
||||
*.log
|
||||
obj/*.stl
|
||||
110
js/add-array.js
Normal file
110
js/add-array.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
"use strict";
|
||||
|
||||
(function() {
|
||||
var AP = Array.prototype;
|
||||
|
||||
/** ******************************************************************
|
||||
* array prototype helpers
|
||||
******************************************************************* */
|
||||
|
||||
AP.peek = function() {
|
||||
return this[this.length-1];
|
||||
};
|
||||
|
||||
/**
|
||||
* allow chaining of push() calls
|
||||
*
|
||||
* @param v
|
||||
* @returns {Array}
|
||||
*/
|
||||
AP.append = function(v) {
|
||||
this.push(v);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* append all array elements to this array
|
||||
*
|
||||
* @param {Array} arr
|
||||
* @returns {Array}
|
||||
*/
|
||||
AP.appendAll = function(arr) {
|
||||
if (arr && arr.length > 0) this.push.apply(this,arr);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* shallow cloning with clone(arg) call on each new element
|
||||
*
|
||||
* @param {Array} [arg]
|
||||
* @returns {Array}
|
||||
*/
|
||||
AP.clone = function(arg) {
|
||||
var na = this.slice(),
|
||||
ln = na.length,
|
||||
i = 0;
|
||||
while (i < ln) na[i] = na[i++].clone(arg);
|
||||
return na;
|
||||
};
|
||||
|
||||
/**
|
||||
* remove and return element from array, if present
|
||||
*
|
||||
* @param val
|
||||
* @returns {*}
|
||||
*/
|
||||
AP.remove = function(val) {
|
||||
var idx = this.indexOf(val);
|
||||
if (idx >= 0) return this.splice(idx,1);
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* return last array element if array length > 0
|
||||
*
|
||||
* @returns {*}
|
||||
*/
|
||||
AP.last = function() {
|
||||
if (this.length === 0) return null;
|
||||
return this[this.length-1];
|
||||
};
|
||||
|
||||
AP.contains = function(val) {
|
||||
return this.indexOf(val) >= 0;
|
||||
};
|
||||
|
||||
AP.toFloat32 = function() {
|
||||
var i = 0, f32 = new Float32Array(this.length);
|
||||
while (i < this.length) {
|
||||
f32[i] = this[i++];
|
||||
}
|
||||
return f32;
|
||||
};
|
||||
|
||||
AP.forEachPair = function(fn, incr) {
|
||||
var scope = this,
|
||||
idx = 0,
|
||||
inc = incr || 2,
|
||||
len = scope.length;
|
||||
while (idx < len) {
|
||||
fn(scope[idx], scope[(idx+1)%len], idx);
|
||||
idx += inc;
|
||||
}
|
||||
};
|
||||
|
||||
AP.show = function(fn,f) {
|
||||
f = f || function(v) { console.log(v) };
|
||||
this.forEach(function(av) {
|
||||
f(av[fn]());
|
||||
})
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* string prototype helpers
|
||||
******************************************************************* */
|
||||
|
||||
String.prototype.reverse = function() {
|
||||
return this.split('').reverse().join('');
|
||||
};
|
||||
|
||||
})();
|
||||
119
js/add-three.js
Normal file
119
js/add-three.js
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
"use strict";
|
||||
|
||||
(function() {
|
||||
|
||||
var MP = THREE.Mesh.prototype,
|
||||
GP = THREE.Geometry.prototype,
|
||||
BP = THREE.BufferGeometry.prototype;
|
||||
|
||||
MP.getBoundingBox = function(update) {
|
||||
return this.geometry.getBoundingBox(update);
|
||||
};
|
||||
|
||||
MP.center = function() {
|
||||
this.geometry.center();
|
||||
return this;
|
||||
};
|
||||
|
||||
MP.mirrorX = function() {
|
||||
return this.mirror(0);
|
||||
};
|
||||
|
||||
MP.mirrorY = function() {
|
||||
return this.mirror(1);
|
||||
};
|
||||
|
||||
MP.mirrorZ = function() {
|
||||
return this.mirror(2);
|
||||
};
|
||||
|
||||
MP.mirror = function(start) {
|
||||
var i,
|
||||
geo = this.geometry,
|
||||
at = geo.attributes,
|
||||
pa = at.position.array,
|
||||
nm = at.normal.array;
|
||||
for (i = start || 0 ; i < pa.length; i += 3) {
|
||||
pa[i] = -pa[i];
|
||||
nm[i] = -nm[i];
|
||||
}
|
||||
geo.computeVertexNormals();
|
||||
return this;
|
||||
};
|
||||
|
||||
GP.center = function(x,y,z) {
|
||||
var box = this.getBoundingBox(),
|
||||
mid = box.dim.clone().multiplyScalar(0.5),
|
||||
dif = mid.clone().add(box.min),
|
||||
pos = this.attributes.position,
|
||||
arr = pos.array,
|
||||
maxx = Math.max(mid.x, mid.y, mid.z),
|
||||
i = 0;
|
||||
if (x) dif.x -= (maxx - mid.x) * x;
|
||||
if (y) dif.y -= (maxx - mid.y) * y;
|
||||
if (z) dif.z -= (maxx - mid.z) * z;
|
||||
while (i < arr.length) {
|
||||
arr[i++] -= dif.x;
|
||||
arr[i++] -= dif.y;
|
||||
arr[i++] -= dif.z;
|
||||
}
|
||||
pos.needsUpdate = true;
|
||||
// force update of (obsolete) bounding box
|
||||
this.getBoundingBox(true);
|
||||
return this;
|
||||
};
|
||||
|
||||
GP.getBoundingBox = function(update) {
|
||||
//if (update) this.boundingBox = null;
|
||||
if (update || !this.boundingBox) this.computeBoundingBox();
|
||||
this.boundingBox.dim = this.boundingBox.max.clone().sub(this.boundingBox.min);
|
||||
return this.boundingBox;
|
||||
};
|
||||
|
||||
GP.unitScale = function(unit) {
|
||||
var bbox = this.getBoundingBox(),
|
||||
scale = unit || 1;
|
||||
this.applyMatrix(
|
||||
new THREE.Matrix3().identity().multiplyScalar(scale /
|
||||
Math.max(
|
||||
bbox.max.x - bbox.min.x,
|
||||
bbox.max.y - bbox.min.y,
|
||||
bbox.max.z - bbox.min.z
|
||||
)
|
||||
)
|
||||
);
|
||||
// force update of (obsolete) bounding box
|
||||
this.getBoundingBox(true);
|
||||
return this;
|
||||
};
|
||||
|
||||
BP.center = GP.center;
|
||||
BP.getBoundingBox = GP.getBoundingBox;
|
||||
BP.unitScale = GP.unitScale;
|
||||
|
||||
BP.fixNormals = function() {
|
||||
this.computeVertexNormals();
|
||||
return this;
|
||||
};
|
||||
|
||||
THREE.Geometry.fromVertices = function(vertices) {
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
||||
return geometry;
|
||||
};
|
||||
|
||||
THREE.Object3D.prototype.newGroup = function() {
|
||||
var group = new THREE.Group();
|
||||
this.add(group);
|
||||
return group;
|
||||
};
|
||||
|
||||
THREE.Object3D.prototype.removeAll = function() {
|
||||
this.children.slice().forEach(function (c) {
|
||||
c.parent = undefined;
|
||||
c.dispatchEvent( { type: 'removed' } );
|
||||
});
|
||||
this.children = [];
|
||||
};
|
||||
|
||||
})();
|
||||
6986
js/ext-clip.js
Executable file
6986
js/ext-clip.js
Executable file
File diff suppressed because it is too large
Load diff
270
js/ext-fsave.js
Normal file
270
js/ext-fsave.js
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/* FileSaver.js
|
||||
* A saveAs() FileSaver implementation.
|
||||
* 1.1.20151003
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
* License: MIT
|
||||
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/*global self */
|
||||
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
|
||||
|
||||
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
|
||||
|
||||
var saveAs = saveAs || (function(view) {
|
||||
"use strict";
|
||||
// IE <10 is explicitly unsupported
|
||||
if (typeof navigator !== "undefined" && /MSIE [1-9]\./.test(navigator.userAgent)) {
|
||||
return;
|
||||
}
|
||||
var
|
||||
doc = view.document
|
||||
// only get URL when necessary in case Blob.js hasn't overridden it yet
|
||||
, get_URL = function() {
|
||||
return view.URL || view.webkitURL || view;
|
||||
}
|
||||
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
|
||||
, can_use_save_link = "download" in save_link
|
||||
, click = function(node) {
|
||||
var event = new MouseEvent("click");
|
||||
node.dispatchEvent(event);
|
||||
}
|
||||
, is_safari = /Version\/[\d\.]+.*Safari/.test(navigator.userAgent)
|
||||
, webkit_req_fs = view.webkitRequestFileSystem
|
||||
, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
|
||||
, throw_outside = function(ex) {
|
||||
(view.setImmediate || view.setTimeout)(function() {
|
||||
throw ex;
|
||||
}, 0);
|
||||
}
|
||||
, force_saveable_type = "application/octet-stream"
|
||||
, fs_min_size = 0
|
||||
// See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and
|
||||
// https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047
|
||||
// for the reasoning behind the timeout and revocation flow
|
||||
, arbitrary_revoke_timeout = 500 // in ms
|
||||
, revoke = function(file) {
|
||||
var revoker = function() {
|
||||
if (typeof file === "string") { // file is an object URL
|
||||
get_URL().revokeObjectURL(file);
|
||||
} else { // file is a File
|
||||
file.remove();
|
||||
}
|
||||
};
|
||||
if (view.chrome) {
|
||||
revoker();
|
||||
} else {
|
||||
setTimeout(revoker, arbitrary_revoke_timeout);
|
||||
}
|
||||
}
|
||||
, dispatch = function(filesaver, event_types, event) {
|
||||
event_types = [].concat(event_types);
|
||||
var i = event_types.length;
|
||||
while (i--) {
|
||||
var listener = filesaver["on" + event_types[i]];
|
||||
if (typeof listener === "function") {
|
||||
try {
|
||||
listener.call(filesaver, event || filesaver);
|
||||
} catch (ex) {
|
||||
throw_outside(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
, auto_bom = function(blob) {
|
||||
// prepend BOM for UTF-8 XML and text/* types (including HTML)
|
||||
if (/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
|
||||
return new Blob(["\ufeff", blob], {type: blob.type});
|
||||
}
|
||||
return blob;
|
||||
}
|
||||
, FileSaver = function(blob, name, no_auto_bom) {
|
||||
if (!no_auto_bom) {
|
||||
blob = auto_bom(blob);
|
||||
}
|
||||
// First try a.download, then web filesystem, then object URLs
|
||||
var
|
||||
filesaver = this
|
||||
, type = blob.type
|
||||
, blob_changed = false
|
||||
, object_url
|
||||
, target_view
|
||||
, dispatch_all = function() {
|
||||
dispatch(filesaver, "writestart progress write writeend".split(" "));
|
||||
}
|
||||
// on any filesys errors revert to saving with object URLs
|
||||
, fs_error = function() {
|
||||
if (target_view && is_safari && typeof FileReader !== "undefined") {
|
||||
// Safari doesn't allow downloading of blob urls
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function() {
|
||||
var base64Data = reader.result;
|
||||
target_view.location.href = "data:attachment/file" + base64Data.slice(base64Data.search(/[,;]/));
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch_all();
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
filesaver.readyState = filesaver.INIT;
|
||||
return;
|
||||
}
|
||||
// don't create more object URLs than needed
|
||||
if (blob_changed || !object_url) {
|
||||
object_url = get_URL().createObjectURL(blob);
|
||||
}
|
||||
if (target_view) {
|
||||
target_view.location.href = object_url;
|
||||
} else {
|
||||
var new_tab = view.open(object_url, "_blank");
|
||||
if (new_tab == undefined && is_safari) {
|
||||
//Apple do not allow window.open, see http://bit.ly/1kZffRI
|
||||
view.location.href = object_url
|
||||
}
|
||||
}
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch_all();
|
||||
revoke(object_url);
|
||||
}
|
||||
, abortable = function(func) {
|
||||
return function() {
|
||||
if (filesaver.readyState !== filesaver.DONE) {
|
||||
return func.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
, create_if_not_found = {create: true, exclusive: false}
|
||||
, slice
|
||||
;
|
||||
filesaver.readyState = filesaver.INIT;
|
||||
if (!name) {
|
||||
name = "download";
|
||||
}
|
||||
if (can_use_save_link) {
|
||||
object_url = get_URL().createObjectURL(blob);
|
||||
setTimeout(function() {
|
||||
save_link.href = object_url;
|
||||
save_link.download = name;
|
||||
click(save_link);
|
||||
dispatch_all();
|
||||
revoke(object_url);
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Object and web filesystem URLs have a problem saving in Google Chrome when
|
||||
// viewed in a tab, so I force save with application/octet-stream
|
||||
// http://code.google.com/p/chromium/issues/detail?id=91158
|
||||
// Update: Google errantly closed 91158, I submitted it again:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=389642
|
||||
if (view.chrome && type && type !== force_saveable_type) {
|
||||
slice = blob.slice || blob.webkitSlice;
|
||||
blob = slice.call(blob, 0, blob.size, force_saveable_type);
|
||||
blob_changed = true;
|
||||
}
|
||||
// Since I can't be sure that the guessed media type will trigger a download
|
||||
// in WebKit, I append .download to the filename.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=65440
|
||||
if (webkit_req_fs && name !== "download") {
|
||||
name += ".download";
|
||||
}
|
||||
if (type === force_saveable_type || webkit_req_fs) {
|
||||
target_view = view;
|
||||
}
|
||||
if (!req_fs) {
|
||||
fs_error();
|
||||
return;
|
||||
}
|
||||
fs_min_size += blob.size;
|
||||
req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
|
||||
fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
|
||||
var save = function() {
|
||||
dir.getFile(name, create_if_not_found, abortable(function(file) {
|
||||
file.createWriter(abortable(function(writer) {
|
||||
writer.onwriteend = function(event) {
|
||||
target_view.location.href = file.toURL();
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch(filesaver, "writeend", event);
|
||||
revoke(file);
|
||||
};
|
||||
writer.onerror = function() {
|
||||
var error = writer.error;
|
||||
if (error.code !== error.ABORT_ERR) {
|
||||
fs_error();
|
||||
}
|
||||
};
|
||||
"writestart progress write abort".split(" ").forEach(function(event) {
|
||||
writer["on" + event] = filesaver["on" + event];
|
||||
});
|
||||
writer.write(blob);
|
||||
filesaver.abort = function() {
|
||||
writer.abort();
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
};
|
||||
filesaver.readyState = filesaver.WRITING;
|
||||
}), fs_error);
|
||||
}), fs_error);
|
||||
};
|
||||
dir.getFile(name, {create: false}, abortable(function(file) {
|
||||
// delete file if it already exists
|
||||
file.remove();
|
||||
save();
|
||||
}), abortable(function(ex) {
|
||||
if (ex.code === ex.NOT_FOUND_ERR) {
|
||||
save();
|
||||
} else {
|
||||
fs_error();
|
||||
}
|
||||
}));
|
||||
}), fs_error);
|
||||
}), fs_error);
|
||||
}
|
||||
, FS_proto = FileSaver.prototype
|
||||
, saveAs = function(blob, name, no_auto_bom) {
|
||||
return new FileSaver(blob, name, no_auto_bom);
|
||||
}
|
||||
;
|
||||
// IE 10+ (native saveAs)
|
||||
if (typeof navigator !== "undefined" && navigator.msSaveOrOpenBlob) {
|
||||
return function(blob, name, no_auto_bom) {
|
||||
if (!no_auto_bom) {
|
||||
blob = auto_bom(blob);
|
||||
}
|
||||
return navigator.msSaveOrOpenBlob(blob, name || "download");
|
||||
};
|
||||
}
|
||||
|
||||
FS_proto.abort = function() {
|
||||
var filesaver = this;
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch(filesaver, "abort");
|
||||
};
|
||||
FS_proto.readyState = FS_proto.INIT = 0;
|
||||
FS_proto.WRITING = 1;
|
||||
FS_proto.DONE = 2;
|
||||
|
||||
FS_proto.error =
|
||||
FS_proto.onwritestart =
|
||||
FS_proto.onprogress =
|
||||
FS_proto.onwrite =
|
||||
FS_proto.onabort =
|
||||
FS_proto.onerror =
|
||||
FS_proto.onwriteend =
|
||||
null;
|
||||
|
||||
return saveAs;
|
||||
}(
|
||||
typeof self !== "undefined" && self
|
||||
|| typeof window !== "undefined" && window
|
||||
|| this.content
|
||||
));
|
||||
// `self` is undefined in Firefox for Android content script context
|
||||
// while `this` is nsIContentFrameMessageManager
|
||||
// with an attribute `content` that corresponds to the window
|
||||
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports.saveAs = saveAs;
|
||||
} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
|
||||
define([], function() {
|
||||
return saveAs;
|
||||
});
|
||||
}
|
||||
1
js/ext-n3d.js
Symbolic link
1
js/ext-n3d.js
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../node_modules/n3d-threejs/index.js
|
||||
1
js/ext-pixi.js
Symbolic link
1
js/ext-pixi.js
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../node_modules/pixi.js/bin/pixi.js
|
||||
1
js/ext-tween.js
Symbolic link
1
js/ext-tween.js
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../node_modules/tween.js/index.js
|
||||
141
js/geo-bounds.js
Normal file
141
js/geo-bounds.js
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
"use strict";
|
||||
|
||||
var gs_base_bounds = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.base) self.base = {};
|
||||
if (self.base.Bounds) return;
|
||||
|
||||
/**
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function Bounds() {
|
||||
this.minx = 10e7;
|
||||
this.miny = 10e7;
|
||||
this.maxx = -10e7;
|
||||
this.maxy = -10e7;
|
||||
this.leftMost = null;
|
||||
}
|
||||
|
||||
var BASE = self.base,
|
||||
UTIL = BASE.util,
|
||||
CONF = BASE.config,
|
||||
ABS = Math.abs,
|
||||
MIN = Math.min,
|
||||
MAX = Math.max,
|
||||
BoP = Bounds.prototype;
|
||||
|
||||
BASE.Bounds = Bounds;
|
||||
BASE.newBounds = newBounds;
|
||||
|
||||
/** ******************************************************************
|
||||
* Bounds Prototype Functions
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
* @returns {Bounds}
|
||||
*/
|
||||
BoP.clone = function() {
|
||||
var b = new Bounds();
|
||||
b.minx = this.minx;
|
||||
b.miny = this.miny;
|
||||
b.maxx = this.maxx;
|
||||
b.maxy = this.maxy;
|
||||
return b;
|
||||
};
|
||||
|
||||
BoP.equals = function(bounds, margin) {
|
||||
if (!margin) margin = BASE.config.precision_offset;
|
||||
return UTIL.isCloseTo(this.minx, bounds.minx, margin) &&
|
||||
UTIL.isCloseTo(this.miny, bounds.miny, margin) &&
|
||||
UTIL.isCloseTo(this.maxx, bounds.maxx, margin) &&
|
||||
UTIL.isCloseTo(this.maxy, bounds.maxy, margin);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Bounds} b
|
||||
*/
|
||||
BoP.merge = function(b) {
|
||||
this.minx = MIN(this.minx, b.minx);
|
||||
this.maxx = MAX(this.maxx, b.maxx);
|
||||
this.miny = MIN(this.miny, b.miny);
|
||||
this.maxy = MAX(this.maxy, b.maxy);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point} p
|
||||
*/
|
||||
BoP.update = function(p) {
|
||||
this.minx = MIN(this.minx, p.x);
|
||||
this.maxx = MAX(this.maxx, p.x);
|
||||
this.miny = MIN(this.miny, p.y);
|
||||
this.maxy = MAX(this.maxy, p.y);
|
||||
if (this.minx === p.x) this.leftMost = p;
|
||||
};
|
||||
|
||||
BoP.contains = function(bounds) {
|
||||
return bounds.isNested(this);
|
||||
};
|
||||
|
||||
BoP.containsXY = function(x,y) {
|
||||
return x >= this.minx && x <= this.maxx && y >= this.miny && y <= this.maxy;
|
||||
};
|
||||
|
||||
BoP.containsOffsetXY = function(x,y,offset) {
|
||||
return x >= this.minx-offset && x <= this.maxx+offset && y >= this.miny-offset && y <= this.maxy+offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Bounds} parent
|
||||
* @returns {boolean} true if fully inside parent bounds
|
||||
*/
|
||||
BoP.isNested = function(parent) {
|
||||
return (
|
||||
this.minx >= parent.minx - CONF.precision_bounds && // min-x
|
||||
this.maxx <= parent.maxx + CONF.precision_bounds && // max-x
|
||||
this.miny >= parent.miny - CONF.precision_bounds && // min-y
|
||||
this.maxy <= parent.maxy + CONF.precision_bounds // max-y
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Bounds} b
|
||||
* @param {number} precision
|
||||
* @returns {boolean}
|
||||
*/
|
||||
BoP.overlaps = function(b, precision) {
|
||||
return (
|
||||
ABS(this.centerx() - b.centerx()) * 2 - precision < this.width() + b.width() &&
|
||||
ABS(this.centery() - b.centery()) * 2 - precision < this.height() + b.height()
|
||||
);
|
||||
};
|
||||
|
||||
BoP.width = function() {
|
||||
return this.maxx - this.minx;
|
||||
};
|
||||
|
||||
BoP.height = function() {
|
||||
return this.maxy - this.miny;
|
||||
};
|
||||
|
||||
BoP.centerx = function() {
|
||||
return this.minx + this.width() / 2;
|
||||
};
|
||||
|
||||
BoP.centery = function() {
|
||||
return this.miny + this.height() / 2;
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Connect to base and Helpers
|
||||
******************************************************************* */
|
||||
|
||||
function newBounds() {
|
||||
return new Bounds();
|
||||
}
|
||||
|
||||
})();
|
||||
221
js/geo-debug.js
Normal file
221
js/geo-debug.js
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
"use strict";
|
||||
|
||||
var gs_base_debug = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.base) self.base = {};
|
||||
if (self.base.debug) return;
|
||||
|
||||
var base = self.base,
|
||||
enabled = false,
|
||||
flags = {},
|
||||
stash = [],
|
||||
size = 20,
|
||||
next_debug_color = 0,
|
||||
debug_colors = [0xff0000, 0x00ff00, 0x0000ff, 0x00ffff, 0xff00ff, 0xffff00];
|
||||
|
||||
/** ******************************************************************
|
||||
* Debug Functions
|
||||
******************************************************************* */
|
||||
|
||||
function log(o) {
|
||||
console.log(o);
|
||||
if (enabled) {
|
||||
stash.push(o);
|
||||
while (stash.length > size) stash.shift();
|
||||
}
|
||||
}
|
||||
|
||||
function enable(history) {
|
||||
enabled = true;
|
||||
if (history) size = Math.abs(history);
|
||||
}
|
||||
|
||||
function disable() {
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
function on() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
function history() {
|
||||
return stash;
|
||||
}
|
||||
|
||||
function last() {
|
||||
return stash[stash.length-1];
|
||||
}
|
||||
|
||||
function trace(msg) {
|
||||
if (msg && typeof msg != 'string') msg = JSON.stringify(msg);
|
||||
log(new Error(msg).stack);
|
||||
}
|
||||
|
||||
function view() {
|
||||
return base.debug.view;
|
||||
}
|
||||
|
||||
function setView(view) {
|
||||
base.debug.view = view;
|
||||
}
|
||||
|
||||
function get(f) {
|
||||
return flags[f];
|
||||
}
|
||||
|
||||
function set(f, value) {
|
||||
if (Array.isArray(f)) {
|
||||
for (var i=0; i<f.length; i++) flags[f[i]] = (value || 1);
|
||||
} else {
|
||||
flags[f] = (value || 1);
|
||||
}
|
||||
}
|
||||
|
||||
function clear(f) {
|
||||
if (Array.isArray(f)) {
|
||||
for (var i = 0; i < f.length; i++) delete flags[f[i]];
|
||||
} else {
|
||||
delete flags[f];
|
||||
}
|
||||
}
|
||||
|
||||
function test(isSet, isNotSet) {
|
||||
var i;
|
||||
if (isNotSet) {
|
||||
if (Array.isArray(isNotSet)) {
|
||||
for (i=0; i<isNotSet.length; i++) {
|
||||
if (flags[isNotSet[i]]) return null;
|
||||
}
|
||||
} else {
|
||||
if (flags[isNotSet]) return null;
|
||||
}
|
||||
}
|
||||
if (isSet) {
|
||||
if (Array.isArray(isSet)) {
|
||||
for (i=0; i<isSet.length; i++) {
|
||||
if (!flags[isSet[i]]) return null;
|
||||
}
|
||||
} else {
|
||||
if (!flags[isSet]) return null;
|
||||
}
|
||||
}
|
||||
return view() || true;
|
||||
}
|
||||
|
||||
function nextDebugColor() {
|
||||
return debug_colors[next_debug_color++ % debug_colors.length];
|
||||
}
|
||||
|
||||
/**
|
||||
* render point array as discrete points
|
||||
*
|
||||
* @param {Point[]} points
|
||||
* @param {number} [color]
|
||||
* @param {number} [opacity]
|
||||
* @param {number} [size]
|
||||
* @returns {THREE.PointCloud}
|
||||
*/
|
||||
function points(points, color, opacity, size) {
|
||||
view().points(points, color, size);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Point[]} points
|
||||
* @param {number} color
|
||||
* @returns {THREE.Line}
|
||||
*/
|
||||
function lines(points, color) {
|
||||
view().lines(points, color);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Polygon} poly
|
||||
* @param {number} color
|
||||
* @param {boolean} [recurse]
|
||||
* @returns {THREE.Object}
|
||||
*/
|
||||
function polygon(poly, color, recurse) {
|
||||
return poly.render(view(), color, recurse);
|
||||
}
|
||||
|
||||
/**
|
||||
* debug polygon
|
||||
*
|
||||
* @param {Polygon} poly
|
||||
* @param {number} [z]
|
||||
* @param {Layer} [v]
|
||||
* @param {boolean} [deep]
|
||||
* @param {boolean} [creep]
|
||||
*/
|
||||
function xray(poly, z, v, deep, creep) {
|
||||
var colors = [
|
||||
0xaa0000,
|
||||
0x0,
|
||||
0xaaaa00,
|
||||
0x444444,
|
||||
0x00ff00,
|
||||
0x888888,
|
||||
0x00aaaa,
|
||||
0x0,
|
||||
0x0000aa,
|
||||
0x444444,
|
||||
0xaa00aa,
|
||||
0x888888
|
||||
],
|
||||
cidx = 0,
|
||||
point,
|
||||
next,
|
||||
layer = v || view();
|
||||
if (typeof(z) === 'number') poly.setZ(z);
|
||||
poly.forEachPoint(function(next) {
|
||||
if (!point) return next = point;
|
||||
if (creep) next.z = (z = z + 0.1);
|
||||
layer.lines([point.clone(), next.clone()], colors[cidx++ % colors.length]);
|
||||
point = next;
|
||||
});
|
||||
layer.lines([poly.last().clone(), poly.first().clone()], 0xffffff);
|
||||
layer.points(poly.points, 0x000000, 0.1);
|
||||
layer.points([poly.first()], 0xffffff, 0.4);
|
||||
layer.points([poly.last()], 0x555555, 0.45);
|
||||
if (deep && poly.inner) {
|
||||
poly.inner.forEach(function(p) { xray(p, z, v, false, creep) });
|
||||
}
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Connect to base
|
||||
******************************************************************* */
|
||||
|
||||
base.debug = {
|
||||
on : on,
|
||||
enable : enable,
|
||||
disable : disable,
|
||||
history : history,
|
||||
last : last,
|
||||
|
||||
get : get,
|
||||
set : set,
|
||||
clear : clear,
|
||||
|
||||
log : log,
|
||||
trace : trace,
|
||||
test : test,
|
||||
view : null,
|
||||
setView : setView,
|
||||
color : nextDebugColor,
|
||||
|
||||
xray : xray,
|
||||
points : points,
|
||||
lines : lines,
|
||||
polygon : polygon,
|
||||
|
||||
slice : null, // todo temp
|
||||
};
|
||||
|
||||
})();
|
||||
114
js/geo-line.js
Normal file
114
js/geo-line.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
"use strict";
|
||||
|
||||
var gs_base_line = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.base) self.base = {};
|
||||
if (self.base.Line) return;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {String} [key]
|
||||
* @constructor
|
||||
*/
|
||||
function Line(p1, p2, key) {
|
||||
if (!key) key = [p1.key, p2.key].join('-');
|
||||
this.p1 = p1;
|
||||
this.p2 = p2;
|
||||
this.key = key;
|
||||
this.coplanar = false;
|
||||
this.edge = false;
|
||||
this.del = false;
|
||||
}
|
||||
|
||||
var BASE = self.base,
|
||||
LiP = Line.prototype;
|
||||
|
||||
BASE.Line = Line;
|
||||
BASE.newLine = newLine;
|
||||
BASE.newOrderedLine = newOrderedLine;
|
||||
|
||||
/** ******************************************************************
|
||||
* Line Prototype Functions
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
* @returns {number}
|
||||
*/
|
||||
LiP.length = function() {
|
||||
return Math.sqrt(this.length2());
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {number} square of length
|
||||
*/
|
||||
LiP.length2 = function() {
|
||||
return this.p1.distToSq2D(this.p2);
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Slope}
|
||||
*/
|
||||
LiP.slope = function() {
|
||||
return BASE.newSlope(this.p1.slopeTo(this.p2));
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Line}
|
||||
*/
|
||||
LiP.reverse = function() {
|
||||
var t = this.p1;
|
||||
this.p1 = this.p2;
|
||||
this.p2 = t;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Point}
|
||||
*/
|
||||
LiP.midpoint = function() {
|
||||
return this.p1.midPointTo(this.p2);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Line} line
|
||||
* @returns {boolean}
|
||||
*/
|
||||
LiP.isCollinear = function(line) {
|
||||
var p1 = this.p1,
|
||||
p2 = this.p2,
|
||||
p3 = line.p1,
|
||||
p4 = line.p2,
|
||||
d1x = (p2.x - p1.x),
|
||||
d1y = (p2.y - p1.y),
|
||||
d2x = (p4.x - p3.x),
|
||||
d2y = (p4.y - p3.y);
|
||||
|
||||
return Math.abs( (d2y * d1x) - (d2x * d1y) ) < 0.000001;
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Connect to base and Helpers
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {String} [key]
|
||||
* @returns {Line}
|
||||
*/
|
||||
function newLine(p1, p2, key) {
|
||||
return new Line(p1, p2, key);
|
||||
}
|
||||
|
||||
function newOrderedLine(p1, p2, key) {
|
||||
return p1.key < p2.key ? newLine(p1,p2,key) : newLine(p2,p1,key);
|
||||
}
|
||||
|
||||
})();
|
||||
645
js/geo-point.js
Normal file
645
js/geo-point.js
Normal file
|
|
@ -0,0 +1,645 @@
|
|||
"use strict";
|
||||
|
||||
var gs_base_point = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.base) self.base = {};
|
||||
if (self.base.Point) return;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} z
|
||||
* @param {String} [key]
|
||||
* @constructor
|
||||
*/
|
||||
function Point(x,y,z,key,CP) {
|
||||
// todo make more efficient for cloning when all 5 params are passed
|
||||
if (CP) {
|
||||
this.x = x || (CP.X / CONF.clipper) || 0;
|
||||
this.y = y || (CP.Y / CONF.clipper) || 0;
|
||||
this.z = z || 0;
|
||||
this.X = CP.X;
|
||||
this.Y = CP.Y;
|
||||
this.key = null;
|
||||
} else {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z || 0;
|
||||
this.X = (x * CONF.clipper);
|
||||
this.Y = (y * CONF.clipper);
|
||||
this.key = key || [x, y, z].toString();
|
||||
}
|
||||
this.poly = null; // parent polygon
|
||||
this.dist = 0.0; // for group intersection sorting and tests
|
||||
this.p1 = null; // used in sliceIntersect(), connectLines() and intersect()
|
||||
this.p2 = null; // used in sliceIntersect(), connectLines() and intersect()
|
||||
this.pos = 0; // position in group
|
||||
this.mod = 0; // group length (for modulus of pos)
|
||||
this.del = false; // for culling
|
||||
this.group = null; // for grouping in slice intersect, offset lines in trace
|
||||
}
|
||||
|
||||
var BASE = self.base,
|
||||
UTIL = BASE.util,
|
||||
CONF = BASE.config,
|
||||
KEYS = BASE.key,
|
||||
ROUND = UTIL.round,
|
||||
PoP = Point.prototype;
|
||||
|
||||
BASE.Point = Point;
|
||||
BASE.newPoint = newPoint;
|
||||
|
||||
/** ******************************************************************
|
||||
* Point Prototype Functions
|
||||
******************************************************************* */
|
||||
|
||||
PoP.setZ = function(z) {
|
||||
this.z = z;
|
||||
return this;
|
||||
}
|
||||
|
||||
PoP.swapXZ = function() {
|
||||
var p = this,
|
||||
t = p.x;
|
||||
p.x = p.z;
|
||||
p.z = t;
|
||||
};
|
||||
|
||||
PoP.swapYZ = function() {
|
||||
var p = this,
|
||||
t = p.y;
|
||||
p.y = p.z;
|
||||
p.z = t;
|
||||
};
|
||||
|
||||
PoP.round = function(precision) {
|
||||
return newPoint(ROUND(this.x,precision), ROUND(this.y,precision), ROUND(this.z,precision));
|
||||
};
|
||||
|
||||
PoP.addFacet = function(facet) {
|
||||
if (!this.group) this.group = [];
|
||||
this.group.push(facet);
|
||||
return this;
|
||||
};
|
||||
|
||||
PoP.rekey = function() {
|
||||
this.key = [this.x,this.y,this.z].join(',');
|
||||
};
|
||||
|
||||
PoP.toString = function() {
|
||||
return this.key;
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Point}
|
||||
*/
|
||||
PoP.clone = function() {
|
||||
return newPoint(this.x, this.y, this.z, this.key);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point} p
|
||||
* @returns {Slope}
|
||||
*/
|
||||
PoP.slopeTo = function(p) {
|
||||
return BASE.newSlope(this, p);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Point} p
|
||||
* @param {String} [k]
|
||||
* @returns {Line}
|
||||
*/
|
||||
PoP.lineTo = function(p, k) {
|
||||
return BASE.newLine(this, p, k);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point} p
|
||||
* @param {number} [dist]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.isNear = function(p, dist) {
|
||||
return UTIL.isCloseTo(this.x, p.x, dist) && UTIL.isCloseTo(this.y, p.y, dist);
|
||||
};
|
||||
|
||||
/**
|
||||
* return distance to line connecting points p1, p2
|
||||
* distance is calculated on the perpendicular (normal) to line
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @returns {number}
|
||||
*/
|
||||
PoP.distToLine = function(p1, p2) {
|
||||
return Math.sqrt(this.distToLineSq(p1, p2));
|
||||
};
|
||||
|
||||
/**
|
||||
* return square of distance to line connecting points p1, p2
|
||||
* distance is calculated on the perpendicular (normal) to line
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @returns {number}
|
||||
*/
|
||||
PoP.distToLineSq = function(p1, p2) {
|
||||
var p = this,
|
||||
d = UTIL.distSq(p1, p2);
|
||||
|
||||
var t = ((p.x - p1.x) * (p2.x - p1.x) + (p.y - p1.y) * (p2.y - p1.y)) / d;
|
||||
|
||||
if (t < 0) return UTIL.distSq(p, p1);
|
||||
if (t > 1) return UTIL.distSq(p, p2);
|
||||
|
||||
return UTIL.distSqv2(p.x, p.y, p1.x + t * (p2.x - p1.x), p1.y + t * (p2.y - p1.y));
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {number} dist2
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.withinDist2 = function(p1, p2, dist2) {
|
||||
var ll2 = p1.distToSq2D(p2),
|
||||
dp1 = this.distToSq2D(p1),
|
||||
dp2 = this.distToSq2D(p2);
|
||||
// if the line segment described is less than dist2
|
||||
// then add dist2 to ll2. if this point is not closer
|
||||
// than newll2 to either point, then it can't be closer
|
||||
// to the described segment than dist2
|
||||
if (ll2 < dist2) {
|
||||
ll2 += dist2;
|
||||
if (dp1 > ll2 && dp2 > ll2) return false;
|
||||
}
|
||||
// if point is farther from each point that the distance
|
||||
// between the points and that distance is greater than dist2
|
||||
// then it's not possible for the point to be closer than
|
||||
// dist2 to the described line segment.
|
||||
if (dp1 > ll2 && dp2 > ll2) return false;
|
||||
return this.distToLineSq(p1, p2) < dist2;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point} p2
|
||||
* @returns {Point}
|
||||
*/
|
||||
PoP.midPointTo = function(p2) {
|
||||
return newPoint((this.x + p2.x)/2, (this.y + p2.y)/2, this.z);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point} p2
|
||||
* @returns {Point}
|
||||
*/
|
||||
PoP.midPointTo3D = function(p2) {
|
||||
return newPoint(
|
||||
(this.x + p2.x)/2,
|
||||
(this.y + p2.y)/2,
|
||||
(this.z + p2.z)/2
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* non-scale corrected version of follow()
|
||||
*
|
||||
* @param slope
|
||||
* @param mult
|
||||
* @returns {Point}
|
||||
*/
|
||||
PoP.projectOnSlope = function(slope, mult) {
|
||||
return newPoint(
|
||||
this.x + slope.dx * mult,
|
||||
this.y + slope.dy * mult,
|
||||
this.z);
|
||||
};
|
||||
|
||||
PoP.followTo = function(point, mult) {
|
||||
return this.follow(this.slopeTo(point), mult);
|
||||
};
|
||||
|
||||
/**
|
||||
* return a point along the line this from point to p2
|
||||
* but offset by a distance. positive distances are
|
||||
* closer to this point.
|
||||
*
|
||||
* @param p2
|
||||
* @param dist
|
||||
*/
|
||||
PoP.offsetPointFrom = function(p2, dist) {
|
||||
var p1 = this,
|
||||
dx = p2.x - p1.x,
|
||||
dy = p2.y - p1.y,
|
||||
ls = dist / Math.sqrt(dx * dx + dy * dy),
|
||||
ox = dx * ls,
|
||||
oy = dy * ls;
|
||||
return newPoint(p2.x - ox, p2.y - oy, p2.z, KEYS.NONE);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point} p2
|
||||
* @param {number} offset
|
||||
* @returns {Line}
|
||||
*/
|
||||
PoP.offsetLineTo = function(p2, offset) {
|
||||
var p1 = this,
|
||||
dx = p2.x - p1.x,
|
||||
dy = p2.y - p1.y,
|
||||
ls = offset / Math.sqrt(dx * dx + dy * dy),
|
||||
ox = dx * ls,
|
||||
oy = dy * ls,
|
||||
np1 = newPoint(p1.x - oy, p1.y + ox, p1.z, KEYS.NONE),
|
||||
np2 = newPoint(p2.x - oy, p2.y + ox, p2.z, KEYS.NONE);
|
||||
np1.op = p1;
|
||||
np2.op = p2;
|
||||
return BASE.newLine(np1, np2, KEYS.NONE);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* checks if a point is inside of a polygon
|
||||
* does not check children/holes
|
||||
*
|
||||
* @param {Polygon} poly
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.inPolygon = function(poly) {
|
||||
if (!poly.bounds.containsXY(this.x, this.y)) return false;
|
||||
|
||||
var p = poly.points, pl = p.length, p1, p2, i, inside = false;
|
||||
|
||||
for (i=0; i<pl; i++) {
|
||||
p1 = p[i];
|
||||
p2 = p[(i+1)%pl];
|
||||
if ((p1.y >= this.y) != (p2.y >= this.y) &&
|
||||
(this.x <= (p2.x - p1.x) * (this.y - p1.y) / (p2.y - p1.y) + p1.x))
|
||||
{
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
|
||||
return inside;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns true if the point is inside of a polygon but
|
||||
* not inside any of it's children
|
||||
*
|
||||
* @param {Polygon | Polygon[]} poly
|
||||
* @return {boolean} true if inside outer but not inner
|
||||
*/
|
||||
PoP.isInPolygon = function(poly) {
|
||||
var point = this, i;
|
||||
if (Array.isArray(poly)) {
|
||||
for (i=0; i<poly.length; i++) {
|
||||
if (point.isInPolygon(poly[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var holes = poly.inner;
|
||||
if (point.inPolygon(poly) || point.nearPolygon(poly, CONF.precision_merge_sq)) {
|
||||
for (i=0; holes && i < holes.length; i++) {
|
||||
if (point.inPolygon(holes[i]) && !point.nearPolygon(holes[i], CONF.precision_merge_sq)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns true if the point is inside of a polygon but
|
||||
* not inside any of it's children
|
||||
*
|
||||
* @param {Polygon | Polygon[]} poly
|
||||
* @return {boolean} true if inside outer but not inner
|
||||
*/
|
||||
PoP.isInPolygonOnly = function(poly) {
|
||||
var point = this, i;
|
||||
if (Array.isArray(poly)) {
|
||||
for (i=0; i<poly.length; i++) {
|
||||
if (point.isInPolygonOnly(poly[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var holes = poly.inner;
|
||||
if (point.inPolygon(poly)) {
|
||||
for (i=0; holes && i < holes.length; i++) {
|
||||
if (point.inPolygon(holes[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* checks if point is near polygon edge. distance is squared.
|
||||
*
|
||||
* @param {Polygon} poly
|
||||
* @param {number} dist2
|
||||
* @param {boolean} [inner] process inner polygons
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.nearPolygon = function(poly, dist2, inner) {
|
||||
// throw new Error("nearPolygon");
|
||||
for (var i=0, p=poly.points, pl=p.length ; i<pl; i++) {
|
||||
if (this.withinDist2(p[i], p[(i+1)%pl], dist2)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (inner && poly.inner) {
|
||||
for (var i=0; i<poly.inner.length; i++) {
|
||||
if (this.nearPolygon(poly.inner[i], dist2)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns true if point will not be trimmed later
|
||||
*
|
||||
* @param {Polygon} poly
|
||||
* @param {number} offset
|
||||
* @param {number} mindist2
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.insideOffset = function(poly, offset, mindist2) {
|
||||
return this.inPolygon(poly) === (offset > 0) && !this.nearPolygon(poly, mindist2);
|
||||
};
|
||||
|
||||
/**
|
||||
* returns a new point following given slope for given distance
|
||||
* same as projectOnSlope() but scaled
|
||||
*
|
||||
* @param {Slope} slope
|
||||
* @param {number} distance
|
||||
* @returns {Point}
|
||||
*/
|
||||
PoP.follow = function(slope, distance) {
|
||||
var ls = distance / Math.sqrt(slope.dx * slope.dx + slope.dy * slope.dy);
|
||||
return newPoint(this.x + slope.dx * ls, this.y + slope.dy * ls, this.z);
|
||||
};
|
||||
|
||||
/**
|
||||
* for point, return z-plane intersecting point on line to next point
|
||||
*
|
||||
* @param {Point} p
|
||||
* @param {number} z
|
||||
* @returns {Point}
|
||||
*/
|
||||
PoP.intersectZ = function(p, z) {
|
||||
var dx = p.x - this.x,
|
||||
dy = p.y - this.y,
|
||||
dz = p.z - this.z,
|
||||
pct = 1 - ((p.z - z) / dz);
|
||||
return newPoint(this.x + dx * pct, this.y + dy * pct, this.z + dz * pct);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point} p
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.isEqual2D = function(p) {
|
||||
return this === p || (this.x === p.x && this.y === p.y);
|
||||
};
|
||||
|
||||
/**
|
||||
* returns true if points are close enough to be considered equivalent
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.isMergable2D = function(p) {
|
||||
return this.isEqual2D(p) || (this.distToSq2D(p) < CONF.precision_merge_sq);
|
||||
};
|
||||
|
||||
/**
|
||||
* compares 3D point
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.isEqual = function(p) {
|
||||
return this === p || (this.x === p.x && this.y === p.y && this.z === p.z);
|
||||
};
|
||||
|
||||
/**
|
||||
* returns true if points are close enough to be considered equivalent
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.isMergable3D = function(p) {
|
||||
return this.isEqual(p) || (this.distToSq3D(p) < CONF.precision_merge_sq);
|
||||
};
|
||||
|
||||
/**
|
||||
* return true if point is inside 2D square size dist*2 around p
|
||||
*
|
||||
* @param {Point} p
|
||||
* @param {number} dist
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.isInBox = function(p, dist) {
|
||||
return Math.abs(this.x - p.x) < dist && Math.abs(this.y - p.y) < dist;
|
||||
};
|
||||
|
||||
/**
|
||||
* return min distance from point to a polygon
|
||||
* stops searching if any point is closer than threshold
|
||||
*
|
||||
* @param {Polygon} poly
|
||||
* @param {number} [threshold] stop looking if under threshold
|
||||
*/
|
||||
PoP.distToPolySegments = function(poly, threshold) {
|
||||
var point = this,
|
||||
mindist = Infinity;
|
||||
poly.forEachSegment(function(p1, p2) {
|
||||
const nextdist = Math.min(mindist, point.distToLine(p1, p2));
|
||||
mindist = Math.min(nextdist, mindist);
|
||||
// returning true terminates forEachSegment()
|
||||
if (mindist <= threshold) return true;
|
||||
});
|
||||
return mindist;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Polygon} poly
|
||||
* @param {number} [threshold] stop looking if under threshold
|
||||
*/
|
||||
PoP.distToPolyPoints = function(poly, threshold) {
|
||||
var point = this, mindist = Infinity;
|
||||
poly.forEachPoint(function(pp) {
|
||||
mindist = Math.min(mindist, point.distTo2D(pp));
|
||||
if (mindist < threshold) return true;
|
||||
});
|
||||
return mindist;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point[]} points
|
||||
* @param {number} max
|
||||
* @returns {Point} nearest point (less than max) from array to this point
|
||||
*/
|
||||
PoP.nearestTo = function(points, max) {
|
||||
if (!max) throw "missing max";
|
||||
var mind = Infinity,
|
||||
minp = null,
|
||||
i, p, d;
|
||||
for (i=0; i<points.length; i++) {
|
||||
p = points[i];
|
||||
if (p === this || p.del) continue;
|
||||
d = this.distToSq2D(p);
|
||||
if (d < max && d < mind) {
|
||||
mind = d;
|
||||
minp = p;
|
||||
}
|
||||
}
|
||||
return minp;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point[]} points
|
||||
* @return {number} average square dist to cloud of points
|
||||
*/
|
||||
PoP.averageDistTo = function(points) {
|
||||
var sum = 0.0, count = 0, i;
|
||||
for (i = 0; i < points.length; i++) {
|
||||
if (points[i] != this) {
|
||||
sum += this.distToSq2D(points[i]);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return sum / count;
|
||||
};
|
||||
|
||||
/**
|
||||
* dist to point in 2D
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {number}
|
||||
*/
|
||||
PoP.distTo2D = function(p) {
|
||||
var dx = this.x - p.x,
|
||||
dy = this.y - p.y;
|
||||
return Math.sqrt(dx * dx + dy * dy);
|
||||
};
|
||||
|
||||
/**
|
||||
* square of distance in 2D
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {number}
|
||||
*/
|
||||
PoP.distToSq2D = function(p) {
|
||||
var dx = this.x - p.x,
|
||||
dy = this.y - p.y;
|
||||
return dx * dx + dy * dy;
|
||||
};
|
||||
|
||||
PoP.distTo3D = function(p) {
|
||||
var dx = this.x - p.x,
|
||||
dy = this.y - p.y,
|
||||
dz = this.z - p.z;
|
||||
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
};
|
||||
|
||||
/**
|
||||
* square of distance in 3D
|
||||
*
|
||||
* @param {Point} p
|
||||
* @returns {number}
|
||||
*/
|
||||
PoP.distToSq3D = function(p) {
|
||||
var dx = this.x - p.x,
|
||||
dy = this.y - p.y,
|
||||
dz = this.z - p.z;
|
||||
return dx * dx + dy * dy + dz * dz;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns true if point is inside triangle described by three points
|
||||
*
|
||||
* @param {Point} a
|
||||
* @param {Point} b
|
||||
* @param {Point} c
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.inTriangle = function(a, b, c) {
|
||||
var as_x = this.x - a.x,
|
||||
as_y = this.y - a.y,
|
||||
s_ab = (b.x - a.x) * as_y - (b.y - a.y) * as_x > 0;
|
||||
if ((c.x - a.x) * as_y - (c.y - a.y) * as_x > 0 == s_ab) return false;
|
||||
if ((c.x - b.x) * (this.y - b.y) - (c.y - b.y) * (this.x - b.x) > 0 != s_ab) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns true if point is on a line described by two points.
|
||||
* test sum of distances p1->this + this->p2 ~= p1->p2 whens
|
||||
* slopes from p1->this same as this->p2
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @returns {boolean}
|
||||
*/
|
||||
PoP.onLine = function(p1, p2) {
|
||||
return this.distToLine(p1, p2) < CONF.precision_point_on_line;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {THREE.Vector3} delta
|
||||
* @return {Point} new offset point
|
||||
*/
|
||||
PoP.add = function(delta) {
|
||||
return newPoint(this.x + delta.x, this.y + delta.y, this.z + delta.z);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {THREE.Vector3} delta
|
||||
* @return {Point} new offset point
|
||||
*/
|
||||
PoP.sub = function(delta) {
|
||||
return newPoint(this.x - delta.x, this.y - delta.y, this.z - delta.z);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {THREE.Vector3} delta
|
||||
*/
|
||||
PoP.move = function(delta) {
|
||||
this.x += delta.x;
|
||||
this.y += delta.y;
|
||||
this.z += delta.z;
|
||||
this.X += delta.x * CONF.clipper;
|
||||
this.Y += delta.y * CONF.clipper;
|
||||
return this;
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Connect to base and Helpers
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} z
|
||||
* @param {String} [key]
|
||||
* @param {Object} [CP] clipper point
|
||||
* @returns {Point}
|
||||
*/
|
||||
function newPoint(x, y, z, key, CP) {
|
||||
return new Point(x, y, z, key, CP);
|
||||
}
|
||||
|
||||
})();
|
||||
1192
js/geo-polygon.js
Normal file
1192
js/geo-polygon.js
Normal file
File diff suppressed because it is too large
Load diff
710
js/geo-polygons.js
Normal file
710
js/geo-polygons.js
Normal file
|
|
@ -0,0 +1,710 @@
|
|||
"use strict";
|
||||
|
||||
var gs_base_polygons = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.base) self.base = {};
|
||||
if (self.base.polygons) return;
|
||||
|
||||
var BASE = self.base,
|
||||
UTIL = BASE.util,
|
||||
CONF = BASE.config,
|
||||
DBUG = BASE.debug,
|
||||
DEG2RAD = Math.PI / 180,
|
||||
ABS = Math.abs,
|
||||
SQRT = Math.sqrt,
|
||||
SQR = UTIL.sqr,
|
||||
NOKEY = BASE.key.NONE,
|
||||
newPoint = BASE.newPoint;
|
||||
|
||||
BASE.polygons = {
|
||||
trace2count : trace2count,
|
||||
rayIntersect : rayIntersect,
|
||||
alignWindings : alignWindings,
|
||||
setWinding : setWinding,
|
||||
fillArea : fillArea,
|
||||
subtract : subtract,
|
||||
flatten : flatten,
|
||||
trimTo : trimTo,
|
||||
expand : expand,
|
||||
union : union,
|
||||
nest : nest,
|
||||
dump : dump,
|
||||
diff : doDiff,
|
||||
filter : filter,
|
||||
toClipper : toClipper,
|
||||
fromClipperNode : fromClipperNode,
|
||||
fromClipperTree : fromClipperTree,
|
||||
cleanClipperTree : cleanClipperTree
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Polygon array utility functions
|
||||
******************************************************************* */
|
||||
|
||||
function toClipper(polys,debug) {
|
||||
var out = [];
|
||||
polys.forEach(function(poly) { poly.toClipper(out,debug) });
|
||||
return out;
|
||||
}
|
||||
|
||||
function fromClipperNode(tnode, z) {
|
||||
var poly = BASE.newPolygon();
|
||||
tnode.m_polygon.forEach(function(p) {
|
||||
poly.push(newPoint(null, null, z, null, p));
|
||||
});
|
||||
poly.open = tnode.IsOpen;
|
||||
return poly;
|
||||
};
|
||||
|
||||
function fromClipperTree(tnode, z, tops, parent) {
|
||||
var polys = tops || [],
|
||||
poly;
|
||||
|
||||
tnode.m_Childs.forEach(function(child) {
|
||||
poly = fromClipperNode(child, z);
|
||||
// throw out all tiny polygons
|
||||
if (poly.area() < 0.1) return;
|
||||
if (parent) {
|
||||
parent.addInner(poly);
|
||||
} else {
|
||||
polys.push(poly);
|
||||
}
|
||||
if (child.m_Childs) {
|
||||
fromClipperTree(child, z, polys, parent ? null : poly);
|
||||
}
|
||||
});
|
||||
|
||||
return polys;
|
||||
};
|
||||
|
||||
function cleanClipperTree(tree) {
|
||||
var clib = self.ClipperLib,
|
||||
clip = clib.Clipper;
|
||||
|
||||
if (tree.m_Childs) tree.m_Childs.forEach(function(child) {
|
||||
child.m_polygon = clip.CleanPolygon(child.m_polygon, CONF.clipperClean);
|
||||
cleanClipperTree(child.m_Childs);
|
||||
});
|
||||
|
||||
return tree;
|
||||
};
|
||||
|
||||
function filter(array, output, fn) {
|
||||
array.forEach(function(poly) {
|
||||
poly = fn(poly);
|
||||
if (poly) {
|
||||
if (Array.isArray(poly)) {
|
||||
output.appendAll(poly);
|
||||
} else {
|
||||
output.push(poly);
|
||||
}
|
||||
}
|
||||
});
|
||||
return output;
|
||||
}
|
||||
|
||||
function dump(poly) {
|
||||
if (Array.isArray(poly)) {
|
||||
poly.forEach(function(p) { dump(p) });
|
||||
} else {
|
||||
console.group({id:poly.id, area:poly.area(), depth:poly.depth, inner:(poly.inner ? poly.inner.length : null)});
|
||||
if (poly.inner) dump(poly.inner);
|
||||
console.groupEnd();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* todo use clipper polytree?
|
||||
*
|
||||
* use bounding boxes and sliceIntersection
|
||||
* to determine parent/child nesting. returns a
|
||||
* array of trees.
|
||||
*
|
||||
* @param {Polygon[]} polygon soup
|
||||
* @param {boolean} deep allow nesting beyond 2 levels
|
||||
* @param {boolean} opentop prevent open polygons from having inners
|
||||
* @returns {Polygon[]} top level parent polygons
|
||||
*/
|
||||
function nest(polygons, deep, opentop) {
|
||||
if (!polygons) return polygons;
|
||||
// sort groups by size
|
||||
polygons.sort(function (a, b) {
|
||||
return a.area() - b.area();
|
||||
});
|
||||
var i, poly;
|
||||
// clear parent/child links if they exist
|
||||
for (i = 0; i < polygons.length; i++) {
|
||||
poly = polygons[i];
|
||||
poly.parent = null;
|
||||
poly.inner = null;
|
||||
}
|
||||
// nest groups if fully contained by a parent
|
||||
for (i = 0; i < polygons.length - 1; i++) {
|
||||
poly = polygons[i];
|
||||
// find the smallest suitable parent
|
||||
for (var j = i + 1; j < polygons.length; j++) {
|
||||
var parent = polygons[j];
|
||||
// prevent open polys from having inners
|
||||
if (opentop && parent.isOpen()) continue;
|
||||
if (poly.isNested(parent)) {
|
||||
parent.addInner(poly);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// tops have an even # depth
|
||||
var tops = [],
|
||||
p;
|
||||
// assign a depth level to each group
|
||||
for (i = 0; i < polygons.length; i++) {
|
||||
p = polygons[i];
|
||||
poly = p;
|
||||
poly.depth = 0;
|
||||
while (p.parent) {
|
||||
poly.depth++;
|
||||
p = p.parent;
|
||||
}
|
||||
if (deep) {
|
||||
if (poly.depth === 0) tops.push(poly);
|
||||
} else {
|
||||
if (poly.depth % 2 === 0) {
|
||||
tops.push(poly);
|
||||
} else {
|
||||
poly.inner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tops;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets windings for parents one way
|
||||
* and children in opposition
|
||||
*
|
||||
* @param {Polygon[]} array
|
||||
* @param {boolean} CW
|
||||
* @param {boolean} [recurse]
|
||||
*/
|
||||
function setWinding(array, CW, recurse) {
|
||||
if (!array) return;
|
||||
var poly, i = 0;
|
||||
while (i < array.length) {
|
||||
poly = array[i++];
|
||||
if (poly.isClockwise() !== CW) poly.reverse();
|
||||
if (recurse && poly.inner) setWinding(poly.inner, !CW, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ensure all polygons have the same winding direction.
|
||||
* try to use reversals that touch the fewest nodes.
|
||||
*
|
||||
* @param {Polygon[]} polys
|
||||
* @return {boolean} true if aligned clockwise
|
||||
*/
|
||||
function alignWindings(polys) {
|
||||
var len = polys.length,
|
||||
fwd = 0,
|
||||
pts = 0,
|
||||
i = 0,
|
||||
setCW,
|
||||
poly;
|
||||
while (i < len) {
|
||||
poly = polys[i++];
|
||||
pts += poly.length;
|
||||
if (poly.isClockwise()) fwd += poly.length;
|
||||
}
|
||||
i = 0;
|
||||
setCW = fwd > (pts/2);
|
||||
while (i < len) {
|
||||
poly = polys[i++];
|
||||
if (poly.isClockwise() != setCW) poly.reverse();
|
||||
}
|
||||
return setCW;
|
||||
}
|
||||
|
||||
function setContains(setA, poly) {
|
||||
for (var i=0; i<setA.length; i++) {
|
||||
if (setA[i].contains(poly)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function flatten(polys, to, crush) {
|
||||
if (!to) to = [];
|
||||
polys.forEach(function(poly) {
|
||||
poly.flattenTo(to);
|
||||
if (crush) poly.inner = null;
|
||||
});
|
||||
return to;
|
||||
}
|
||||
|
||||
/**
|
||||
* Diff two sets of polygons and return A-B, B-A.
|
||||
* no polygons in a given set can overlap ... only between sets
|
||||
*
|
||||
* @param {Polygon[]} setA
|
||||
* @param {Polygon[]} setB
|
||||
* @param {Polygon[]} outA
|
||||
* @param {Polygon[]} outB
|
||||
* @param {number} [z]
|
||||
* @param {number} [minArea]
|
||||
* @returns {Polygon[]} out
|
||||
*/
|
||||
function subtract(setA, setB, outA, outB, z, minArea) {
|
||||
var clib = self.ClipperLib,
|
||||
ctyp = clib.ClipType,
|
||||
ptyp = clib.PolyType,
|
||||
cfil = clib.PolyFillType,
|
||||
clip = new clib.Clipper(),
|
||||
ctre = new clib.PolyTree(),
|
||||
sp1 = toClipper(setA),
|
||||
sp2 = toClipper(setB),
|
||||
min = minArea || 0.1,
|
||||
out = [];
|
||||
|
||||
function filter(from, to) {
|
||||
from.forEach(function(poly) {
|
||||
if (poly.area() >= min && poly.circularity() > 0.01) {
|
||||
to.push(poly);
|
||||
out.push(poly);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// expensive but worth it?
|
||||
clip.StrictlySimple = true;
|
||||
|
||||
if (outA) {
|
||||
clip.AddPaths(sp1, ptyp.ptSubject, true);
|
||||
clip.AddPaths(sp2, ptyp.ptClip, true);
|
||||
|
||||
if (clip.Execute(ctyp.ctDifference, ctre, cfil.pftEvenOdd, cfil.pftEvenOdd)) {
|
||||
cleanClipperTree(ctre);
|
||||
filter(fromClipperTree(ctre, z), outA);
|
||||
}
|
||||
}
|
||||
|
||||
if (outB) {
|
||||
if (outA) {
|
||||
ctre.Clear();
|
||||
clip.Clear();
|
||||
}
|
||||
|
||||
clip.AddPaths(sp2, ptyp.ptSubject, true);
|
||||
clip.AddPaths(sp1, ptyp.ptClip, true);
|
||||
|
||||
if (clip.Execute(ctyp.ctDifference, ctre, cfil.pftEvenOdd, cfil.pftEvenOdd)) {
|
||||
cleanClipperTree(ctre);
|
||||
filter(fromClipperTree(ctre, z), outB);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice.doProjectedFills()
|
||||
* Print.init w/ brims
|
||||
*
|
||||
* @param {Polygon[]} polys
|
||||
* @param {number} [z]
|
||||
* @returns {Polygon[]}
|
||||
*/
|
||||
function union(polys) {
|
||||
if (polys.length < 2) return polys;
|
||||
|
||||
var out = polys.slice(), i, j, union, uset = [];
|
||||
|
||||
outer: for (i=0; i<out.length; i++) {
|
||||
if (!out[i]) continue;
|
||||
for (j=i+1; j<out.length; j++) {
|
||||
if (!out[j]) continue;
|
||||
union = out[i].union(out[j]);
|
||||
if (union) {
|
||||
out[i] = null;
|
||||
out[j] = null;
|
||||
out.push(union);
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i=0; i<out.length; i++) {
|
||||
if (out[i]) uset.push(out[i]);
|
||||
}
|
||||
|
||||
return uset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Polygon} poly clipping mask
|
||||
* @returns {?Polygon[]}
|
||||
*/
|
||||
function doDiff(setA, setB, z) {
|
||||
var clib = self.ClipperLib,
|
||||
ctyp = clib.ClipType,
|
||||
ptyp = clib.PolyType,
|
||||
cfil = clib.PolyFillType,
|
||||
clip = new clib.Clipper(),
|
||||
ctre = new clib.PolyTree(),
|
||||
sp1 = toClipper(setA),
|
||||
sp2 = toClipper(setB);
|
||||
|
||||
clip.AddPaths(sp1, ptyp.ptSubject, true);
|
||||
clip.AddPaths(sp2, ptyp.ptClip, true);
|
||||
|
||||
if (clip.Execute(ctyp.ctDifference, ctre, cfil.pftEvenOdd, cfil.pftEvenOdd)) {
|
||||
return fromClipperTree(ctre, z);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Slice.doProjectedFills()
|
||||
*
|
||||
* @param {Polygon[]} setA source set
|
||||
* @param {Polygon[]} setB mask set
|
||||
* @returns {Polygon[]}
|
||||
*/
|
||||
function trimTo(setA, setB) {
|
||||
// handle null/empty slices
|
||||
if (setA === setB || setA === null || setB === null) return null;
|
||||
|
||||
var out = [], tmp;
|
||||
UTIL.doCombinations(setA, setB, {}, function(a, b) {
|
||||
if (tmp = a.mask(b)) {
|
||||
out.appendAll(tmp);
|
||||
}
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Polygon[]} polys
|
||||
* @param {number} distance offset
|
||||
* @param {number} [z] defaults to 0
|
||||
* @param {Polygon[]} [out] optional collector
|
||||
* @param {number} [count] offset passes (0 == until no space left)
|
||||
* @param {number} [distance2] after first offset pass
|
||||
* @param {Function} [collector] receives output of each pass
|
||||
* @returns {Polygon[]} last offset
|
||||
*/
|
||||
function expand(polys, distance, z, out, count, distance2, collector) {
|
||||
// prepare alignments for clipper lib
|
||||
alignWindings(polys);
|
||||
polys.forEach(function(poly) {
|
||||
if (poly.inner) setWinding(poly.inner, !poly.isClockwise());
|
||||
});
|
||||
|
||||
var fact = CONF.clipper,
|
||||
clib = self.ClipperLib,
|
||||
clip = clib.Clipper,
|
||||
cpft = clib.PolyFillType,
|
||||
cjnt = clib.JoinType,
|
||||
cety = clib.EndType,
|
||||
coff = new clib.ClipperOffset(),
|
||||
ctre = new clib.PolyTree();
|
||||
|
||||
polys.forEach(function(poly) {
|
||||
var clean = clip.CleanPolygons(poly.toClipper(), CONF.clipperClean);
|
||||
var simple = clip.SimplifyPolygons(clean, cpft.pftNonZero);
|
||||
coff.AddPaths(simple, cjnt.jtMiter, cety.etClosedPolygon);
|
||||
});
|
||||
|
||||
coff.Execute(ctre, distance * fact);
|
||||
polys = fromClipperTree(ctre, z);
|
||||
|
||||
if (out) out.appendAll(polys);
|
||||
if (collector) collector(polys, count);
|
||||
if ((count === 0 || count > 1) && polys.length > 0) expand(polys, distance2 || distance, z, out, count > 0 ? count-1 : 0, distance2, collector);
|
||||
|
||||
return polys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Polygon} poly
|
||||
* @param {Polygon[]} traces
|
||||
* @param {number} offset
|
||||
* @param {number} count
|
||||
* @param {number} depth
|
||||
* @param {Polygon[]} [last]
|
||||
* @param {Polygon[]} [first]
|
||||
*/
|
||||
function trace2count(poly, traces, offset, count, depth, last, first) {
|
||||
if (DBUG) DBUG.set('last', count === 1);
|
||||
|
||||
if (count === 0) {
|
||||
if (last) last.append(poly);
|
||||
return;
|
||||
}
|
||||
|
||||
// offset polygon to outer traces array
|
||||
var calcoff = depth === 0 && last ? offset / 2 : offset,
|
||||
outer = poly.offset(calcoff, []),
|
||||
inner = [],
|
||||
j;
|
||||
|
||||
// outer offset failed
|
||||
if (outer.length === 0) {
|
||||
if (last) last.append(poly);
|
||||
return;
|
||||
}
|
||||
|
||||
// offset poly children to inner traces array
|
||||
if (poly.inner) {
|
||||
poly.inner.forEach(function(ic) {
|
||||
ic.offset(-calcoff, inner);
|
||||
});
|
||||
}
|
||||
|
||||
var newouter = [], newinner = [];
|
||||
subtract(outer, inner, newouter, newinner, poly.getZ());
|
||||
|
||||
if (newouter.length > 0) {
|
||||
traces.appendAll(newouter);
|
||||
if (depth === 0 && first) {
|
||||
first.appendAll(newouter);
|
||||
}
|
||||
// recurse for multiple shells
|
||||
if (count > 0) {
|
||||
for (j=0; j<newouter.length; j++) {
|
||||
trace2count(newouter[j], traces, offset, count - 1, depth + 1, last);
|
||||
}
|
||||
}
|
||||
} else if (last) {
|
||||
last.append(poly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* todo use clipper opne poly clipping?
|
||||
*
|
||||
* @param {Polygon[]} polys
|
||||
* @param {number} angle (-90 to 90)
|
||||
* @param {number} spacing
|
||||
* @param {Polygon[]} [output]
|
||||
* @param {numer} minLen
|
||||
* @returns {Point[]} supplied output or new array
|
||||
*/
|
||||
function fillArea(polys, angle, spacing, output, minLen) {
|
||||
var i = 1,
|
||||
p0 = polys[0],
|
||||
zpos = p0.getZ(),
|
||||
bounds = p0.bounds.clone(),
|
||||
raySlope;
|
||||
|
||||
// ensure angle is in the -90:90 range
|
||||
angle = angle % 180;
|
||||
while (angle > 90) angle -= 180;
|
||||
|
||||
// X,Y ray slope derived from angle
|
||||
raySlope = BASE.newSlope(0,0,
|
||||
UTIL.round(Math.cos(angle * DEG2RAD) * spacing, 7),
|
||||
UTIL.round(Math.sin(angle * DEG2RAD) * spacing, 7)
|
||||
);
|
||||
|
||||
// compute union of top boundaries
|
||||
while (i < polys.length) bounds.merge(polys[i++].bounds);
|
||||
|
||||
// ray stepping is an axis from the line perpendicular to the ray
|
||||
var rayint = output || [],
|
||||
stepX = -raySlope.dy,
|
||||
stepY = raySlope.dx,
|
||||
iterX = ABS(ABS(stepX) > 0 ? bounds.width() / stepX : 0),
|
||||
iterY = ABS(ABS(stepY) > 0 ? bounds.height() / stepY : 0),
|
||||
dist = SQRT(SQR(iterX * stepX) + SQR(iterY * stepY)),
|
||||
step = SQRT(SQR(stepX) + SQR(stepY)),
|
||||
steps = dist / step,
|
||||
start = angle < 0 ? { x:bounds.minx, y:bounds.miny, z:zpos } : { x:bounds.maxx, y:bounds.miny, z:zpos },
|
||||
clib = self.ClipperLib,
|
||||
ctyp = clib.ClipType,
|
||||
ptyp = clib.PolyType,
|
||||
cfil = clib.PolyFillType,
|
||||
clip = new clib.Clipper(),
|
||||
ctre = new clib.PolyTree(),
|
||||
minlen = BASE.config.clipper * (minLen || 0.25),
|
||||
lines = [];
|
||||
|
||||
for (i = 0; i < steps; i++) {
|
||||
var p1 = newPoint(start.x - raySlope.dx * 1000, start.y - raySlope.dy * 1000, zpos, NOKEY),
|
||||
p2 = newPoint(start.x + raySlope.dx * 1000, start.y + raySlope.dy * 1000, zpos, NOKEY);
|
||||
|
||||
lines.push([p1,p2]);
|
||||
|
||||
start.x += stepX;
|
||||
start.y += stepY;
|
||||
}
|
||||
|
||||
clip.AddPaths(lines, ptyp.ptSubject, false);
|
||||
clip.AddPaths(toClipper(polys), ptyp.ptClip, true);
|
||||
|
||||
if (clip.Execute(ctyp.ctIntersection, ctre, cfil.pftNonZero, cfil.pftEvenOdd)) {
|
||||
ctre.m_AllPolys.forEach(function(poly) {
|
||||
// filter out polygons under min length (0.5mm)
|
||||
if (clib.JS.PerimeterOfPath(poly.m_polygon, false, 1) < minlen) return;
|
||||
poly.m_polygon.forEach(function(point) {
|
||||
rayint.push(newPoint(null,null,zpos,null,point));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return rayint;
|
||||
}
|
||||
|
||||
/**
|
||||
* tracing a ray through a slice's polygons, find and return
|
||||
* a sorted list of all intersecting points.
|
||||
*
|
||||
* @param {Point} start
|
||||
* @param {Slope} slope
|
||||
* @param {Polygon[]} polygons
|
||||
* @param {boolean} [for_fill]
|
||||
* @returns {Point[]}
|
||||
*/
|
||||
function rayIntersect(start, slope, polygons, for_fill) {
|
||||
var i = 0,
|
||||
flat = [],
|
||||
points = [],
|
||||
conf = BASE.config,
|
||||
merge_dist = for_fill ? conf.precision_fill_merge : conf.precision_merge;
|
||||
// todo use new flatten() function above
|
||||
polygons.forEach(function(p) {
|
||||
p.flattenTo(flat);
|
||||
});
|
||||
polygons = flat;
|
||||
while (i < polygons.length) {
|
||||
var polygon = polygons[i++],
|
||||
pp = polygon.points,
|
||||
pl = pp.length,
|
||||
dbug = BASE.debug,
|
||||
debug = false;
|
||||
for (var j = 0; j < pl; j++) {
|
||||
var j2 = (j + 1) % pl,
|
||||
ip = UTIL.intersectRayLine(start, slope, pp[j], pp[j2]);
|
||||
if (ip) {
|
||||
// add group object to point for cull detection
|
||||
ip.group = polygon;
|
||||
// add point to point list
|
||||
points.push(ip);
|
||||
// if point is near a group endpoint, add position marker for culling
|
||||
if (ip.isNear(pp[j], merge_dist)) {
|
||||
ip.pos = j;
|
||||
ip.mod = pl;
|
||||
if (debug) dbug.points([ip], 0x0000ff, 0.5, 1.0);
|
||||
} else if (ip.isNear(pp[j2], merge_dist)) {
|
||||
ip.pos = j2;
|
||||
ip.mod = pl;
|
||||
if (debug) dbug.points([ip], 0x00ffff, 0.5, 0.85);
|
||||
} else {
|
||||
if (debug) dbug.points([ip], 0xff00ff, 0.5, 0.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (points.length > 0) {
|
||||
var del = false;
|
||||
// sort on distance from ray origin
|
||||
points.sort(function (p1, p2) {
|
||||
// handle passing through line-common end points
|
||||
if (!(p1.del || p2.del) && p1.isNear(p2, merge_dist)) {
|
||||
var line = [];
|
||||
if (!p1.isNear(p1.p1, merge_dist)) line.push(p1.p1);
|
||||
if (!p1.isNear(p1.p2, merge_dist)) line.push(p1.p2);
|
||||
if (!p2.isNear(p2.p1, merge_dist)) line.push(p2.p1);
|
||||
if (!p2.isNear(p2.p2, merge_dist)) line.push(p2.p2);
|
||||
/**
|
||||
* when true, points are coincident on collinear lines but
|
||||
* not passing through endpoints on each. kill them. this case
|
||||
* was added later. see below for what else can happen.
|
||||
*/
|
||||
if (line.length < 2) {
|
||||
dbug.log("sliceInt: line common ep fail: "+line.length);
|
||||
} else
|
||||
if (line.length > 2) {
|
||||
p1.del = true;
|
||||
p2.del = true;
|
||||
} else
|
||||
/**
|
||||
* when a ray intersects two equal points, they are either inside or outside.
|
||||
* to determine which, we create a line from the two points connected to them
|
||||
* and test intersect the ray with that line. if it intersects, the points are
|
||||
* inside and we keep one of them. otherwise, they are outside and we drop both.
|
||||
*/
|
||||
if (!UTIL.intersectRayLine(start, slope, line[0], line[1])) {
|
||||
del = true;
|
||||
p1.del = true;
|
||||
p2.del = true;
|
||||
if (debug) dbug.points([p1, p2], 0xffffff, 0.2, 1);
|
||||
} else {
|
||||
del = true;
|
||||
p1.del = true;
|
||||
if (debug) dbug.points([p1], 0xffff00, 0.2, 0.85);
|
||||
}
|
||||
}
|
||||
return p1.dist - p2.dist; // sort on 'a' dist from ray origin
|
||||
});
|
||||
/**
|
||||
* cull invalid lines between groups on same/different levels depending
|
||||
* ok = same level (even), same group
|
||||
* ok = same level (odd), diff group
|
||||
* ok = diff level (even-odd)
|
||||
*/
|
||||
if (for_fill) {
|
||||
var p1, p2;
|
||||
i = 0;
|
||||
pl = points.length;
|
||||
while (i < pl) {
|
||||
p1 = points[i++];
|
||||
while (p1 && p1.del && i < pl) p1 = points[i++];
|
||||
p2 = points[i++];
|
||||
while (p2 && p2.del && i < pl) p2 = points[i++];
|
||||
if (p1 && p2 && p1.group && p1.group) {
|
||||
var p1g = p1.group,
|
||||
p2g = p2.group,
|
||||
even = (p1g.depth % 2 === 0), // point is on an even depth group
|
||||
same = (p1g === p2g); // points intersect same group
|
||||
if (p1g.depth === p2g.depth) {
|
||||
// TODO this works sometimes and not others
|
||||
//if ((even && !same) || (same && !even)) {
|
||||
// p1.del = true;
|
||||
// p2.del = true;
|
||||
// del = true;
|
||||
// if (debug) dbug.points([p1, p2], 0xfff000, 0.2, 2);
|
||||
//}
|
||||
// check cull co-linear with group edge
|
||||
if (same && p1.mod && p2.mod) {
|
||||
var diff = ABS(p1.pos - p2.pos);
|
||||
if (diff === 1 || diff === p1.mod - 1) {
|
||||
p1.del = true;
|
||||
p2.del = true;
|
||||
del = true;
|
||||
if (debug) dbug.points([p1, p2], 0xffffff, 0.2, 1.85);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle deletions, if found
|
||||
if (del) {
|
||||
var np = [];
|
||||
for (i = 0; i < points.length; i++) {
|
||||
var p = points[i];
|
||||
if (!p.del) {
|
||||
np.push(p);
|
||||
}
|
||||
}
|
||||
points = np;
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
})();
|
||||
99
js/geo-render.js
Normal file
99
js/geo-render.js
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
"use strict";
|
||||
|
||||
var gs_base_render = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.base) self.base = {};
|
||||
if (self.base.render) return;
|
||||
|
||||
var BASE = self.base,
|
||||
line_width = 1,
|
||||
materialCache = {};
|
||||
|
||||
BASE.render = {
|
||||
wireframe : wireframe
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Render Functions
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
* render triangle array as line segments. use of {@link newOrderedLine}
|
||||
* allows lines to be cached by normalizing key order.
|
||||
*
|
||||
* @param {THREE.Group} group
|
||||
* @param {Point[]} points
|
||||
* @param {number} color
|
||||
* @returns {THREE.Line}
|
||||
*/
|
||||
function wireframe(group, points, color) {
|
||||
if (points.length % 3 != 0) throw "invalid line : "+points.length;
|
||||
var lines = new THREE.Geometry(),
|
||||
hash = {},
|
||||
added = 0;
|
||||
for (var i = 0; i < points.length; i += 3) {
|
||||
var p1 = points[i],
|
||||
p2 = points[i + 1],
|
||||
p3 = points[i + 2],
|
||||
l1 = newOrderedLine(p1, p2),
|
||||
l2 = newOrderedLine(p2, p3),
|
||||
l3 = newOrderedLine(p3, p1);
|
||||
if (!hash[l1.key]) {
|
||||
lines.vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
|
||||
lines.vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
|
||||
hash[l1.key] = ++added;
|
||||
}
|
||||
if (!hash[l2.key]) {
|
||||
lines.vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
|
||||
lines.vertices.push(new THREE.Vector3(p3.x, p3.y, p3.z));
|
||||
hash[l2.key] = ++added;
|
||||
}
|
||||
if (!hash[l3.key]) {
|
||||
lines.vertices.push(new THREE.Vector3(p3.x, p3.y, p3.z));
|
||||
lines.vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
|
||||
hash[l3.key] = ++added;
|
||||
}
|
||||
}
|
||||
lines.verticesNeedUpdate = true;
|
||||
var mesh = new THREE.LineSegments(lines, getMaterial(color));
|
||||
group.add(mesh);
|
||||
return mesh;
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Connect to base and Helpers
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
* @param {number} color
|
||||
* @returns {THREE.LineBasicMaterial}
|
||||
*/
|
||||
function getMaterial(color) {
|
||||
var material = materialCache[color];
|
||||
if (!material) {
|
||||
material = new THREE.LineBasicMaterial({
|
||||
fog:false,
|
||||
color: color,
|
||||
linewidth: line_width
|
||||
});
|
||||
materialCache[color] = material;
|
||||
}
|
||||
return material;
|
||||
}
|
||||
|
||||
/**
|
||||
* required for line caching in {@link base.render.wireframe}
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @returns {Line}
|
||||
*/
|
||||
function newOrderedLine(p1, p2) {
|
||||
return p1.key < p2.key ? BASE.newLine(p1, p2) : BASE.newLine(p2, p1);
|
||||
}
|
||||
|
||||
})();
|
||||
134
js/geo-slope.js
Normal file
134
js/geo-slope.js
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
"use strict";
|
||||
|
||||
var gs_base_slope = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.base) self.base = {};
|
||||
if (self.base.Slope) return;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param dx
|
||||
* @param dy
|
||||
* @constructor
|
||||
*/
|
||||
function Slope(p1, p2, dx, dy) {
|
||||
this.dx = p1 && p2 ? p2.x - p1.x : dx;
|
||||
this.dy = p1 && p2 ? p2.y - p1.y : dy;
|
||||
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
||||
}
|
||||
|
||||
var BASE = self.base,
|
||||
CONF = BASE.config,
|
||||
ABS = Math.abs,
|
||||
SlP = Slope.prototype,
|
||||
DEG2RAD = Math.PI / 180,
|
||||
RAD2DEG = 180 / Math.PI;
|
||||
|
||||
BASE.Slope = Slope;
|
||||
BASE.newSlope = newSlope;
|
||||
|
||||
BASE.newSlopeFromAngle = function(angle) {
|
||||
return newSlope(0,0,
|
||||
Math.cos(angle * DEG2RAD),
|
||||
Math.sin(angle * DEG2RAD)
|
||||
);
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Slope Prototype Functions
|
||||
******************************************************************* */
|
||||
|
||||
SlP.toString = function() {
|
||||
return [this.dx, this.dy, this.angle].join(',');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Slope} s
|
||||
* @returns {boolean}
|
||||
*/
|
||||
SlP.isSame = function(s) {
|
||||
// if very close to vertical or horizontal, they're the same
|
||||
if (ABS(this.dx) <= CONF.precision_merge && ABS(s.dx) <= CONF.precision_merge) return true;
|
||||
if (ABS(this.dy) <= CONF.precision_merge && ABS(s.dy) <= CONF.precision_merge) return true;
|
||||
// check angle within a range
|
||||
var prec = Math.min(1/Math.sqrt(this.dx * this.dx + this.dy * this.dy), CONF.precision_slope_merge);
|
||||
return angleWithinDelta(this.angle, s.angle, prec || CONF.precision_slope);
|
||||
};
|
||||
|
||||
/**
|
||||
* turn slope 90 degrees
|
||||
*
|
||||
* @returns {Slope}
|
||||
*/
|
||||
SlP.normal = function() {
|
||||
var t = this.dx;
|
||||
this.dx = -this.dy;
|
||||
this.dy = t;
|
||||
this.angle = Math.atan2(this.dy, this.dx) * RAD2DEG;
|
||||
return this;
|
||||
};
|
||||
|
||||
SlP.toUnit = function() {
|
||||
var max = Math.max(ABS(this.dx), ABS(this.dy));
|
||||
this.dx = this.dx / max;
|
||||
this.dy = this.dy / max;
|
||||
return this;
|
||||
};
|
||||
|
||||
SlP.factor = function(f) {
|
||||
this.dx *= f;
|
||||
this.dy *= f;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* reverse (180 degree) slope
|
||||
*
|
||||
* @returns {Slope}
|
||||
*/
|
||||
SlP.invert = function() {
|
||||
this.dx = -this.dx;
|
||||
this.dy = -this.dy;
|
||||
this.angle = 360 - this.angle;//Math.atan2(this.dy, this.dx) * RAD2DEG;
|
||||
return this;
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Connect to base and Helpers
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
* returns true if the difference between a & b is less than v
|
||||
*
|
||||
* @param {number} a
|
||||
* @param {number} b
|
||||
* @param {number} v
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function minDeltaABS(a,b,v) {
|
||||
return ABS(a-b) < v;
|
||||
}
|
||||
|
||||
function angleWithinDelta(a1, a2, delta) {
|
||||
return (ABS(a1-a2) <= delta || 360-ABS(a1-a2) <= delta);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param dx
|
||||
* @param dy
|
||||
* @returns {Slope}
|
||||
*/
|
||||
function newSlope(p1, p2, dx, dy) {
|
||||
return new Slope(p1, p2, dx, dy);
|
||||
}
|
||||
|
||||
})();
|
||||
394
js/geo.js
Normal file
394
js/geo.js
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
"use strict";
|
||||
|
||||
var gs_base = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.base) self.base = {};
|
||||
if (self.base.util) return;
|
||||
|
||||
var BASE = self.base,
|
||||
ABS = Math.abs,
|
||||
round_decimal_precision = 8;
|
||||
|
||||
/** ******************************************************************
|
||||
* Utility Functions
|
||||
******************************************************************* */
|
||||
|
||||
function time() { return new Date().getTime() }
|
||||
|
||||
/**
|
||||
* call function with all combinations of a1, a2
|
||||
* and passing in the supplied arg object.
|
||||
*
|
||||
* @param {Array} a1
|
||||
* @param {Array} a2
|
||||
* @param {Object} arg
|
||||
* @param {Function} fn
|
||||
* @returns {Object}
|
||||
*/
|
||||
function doCombinations(a1, a2, arg, fn) {
|
||||
var i, j;
|
||||
for (i = 0; i < a1.length; i++) {
|
||||
for (j = (a1 === a2 ? i + 1 : 0); j < a2.length; j++) {
|
||||
fn(a1[i], a2[j], arg);
|
||||
}
|
||||
}
|
||||
return arg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {Point} p3
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isClockwise(p1, p2, p3) {
|
||||
return area2(p1, p2, p3) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {Point} p3
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isCounterClockwise(p1, p2, p3) {
|
||||
return area2(p1, p2, p3) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {Point} p3
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isCollinear(p1, p2, p3) {
|
||||
return inCloseRange(area2(p1, p2, p3), -0.00001, 0.00001);
|
||||
}
|
||||
|
||||
function pac(p1, p2) {
|
||||
return (p2.x - p1.x) * (p2.y + p1.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns 2x area for a triangle with sign indicating handedness
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {Point} p3
|
||||
* @returns {number} negative for CCW progression, positive for CW progression
|
||||
*/
|
||||
function area2(p1, p2, p3) {
|
||||
return pac(p1,p2) + pac(p2,p3) + pac(p3,p1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param v1
|
||||
* @param v2
|
||||
* @param [dist]
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isCloseTo(v1,v2,dist) {
|
||||
return ABS(v1-v2) <= (dist || BASE.config.precision_merge);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param val
|
||||
* @param min
|
||||
* @param max
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function inCloseRange(val, min, max) {
|
||||
return (isCloseTo(val,min) || val >= min) && (isCloseTo(val,max) || val <= max);
|
||||
}
|
||||
|
||||
/**
|
||||
* return square of value
|
||||
* @param v
|
||||
* @returns {number}
|
||||
*/
|
||||
function sqr(v) { return v * v }
|
||||
|
||||
/**
|
||||
* return distance squared between two points
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @returns {number}
|
||||
*/
|
||||
function dist2(p1,p2) { return sqr(p2.x - p1.x) + sqr(p2.y - p1.y) }
|
||||
|
||||
/**
|
||||
* return distance squared between two points
|
||||
* enables faster Point.nearPolygon()
|
||||
*
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {number} x2
|
||||
* @param {number} y2
|
||||
* @returns {number}
|
||||
*/
|
||||
function dist2v2(x1,y1,x2,y2) { return sqr(x2 - x1) + sqr(y2 - y1) }
|
||||
|
||||
/**
|
||||
*
|
||||
* @param offset
|
||||
* @param precision
|
||||
* @returns {number}
|
||||
*/
|
||||
function offsetPrecision(offset, precision) {
|
||||
return ABS(offset) - precision;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value
|
||||
* @param min
|
||||
* @param max
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function inRange(value, min, max) {
|
||||
var val = parseFloat(value);
|
||||
return val >= min && val <= max;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param v
|
||||
* @param zeros
|
||||
* @returns {number}
|
||||
*/
|
||||
function round(v, zeros) {
|
||||
var pow = Math.pow(10,zeros || round_decimal_precision);
|
||||
return Math.round(v * pow) / pow;
|
||||
}
|
||||
|
||||
/**
|
||||
* used by {@link Polygon.trace} and {@link Polygon.intersect}
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {Point} p3
|
||||
* @param {Point} p4
|
||||
* @param {String} [test]
|
||||
* @param {boolean} [parallelok]
|
||||
* @returns {?Point | String}
|
||||
*/
|
||||
function intersect(p1, p2, p3, p4, test, parallelok) {
|
||||
var keys = BASE.key,
|
||||
p1x = p1.x,
|
||||
p1y = p1.y,
|
||||
p2x = p2.x,
|
||||
p2y = p2.y,
|
||||
p3x = p3.x,
|
||||
p3y = p3.y,
|
||||
p4x = p4.x,
|
||||
p4y = p4.y,
|
||||
d1x = (p2x - p1x), // ad.x
|
||||
d1y = (p2y - p1y), // ad.y
|
||||
d2x = (p4x - p3x), // bd.x
|
||||
d2y = (p4y - p3y), // bd.y
|
||||
d = (d2y * d1x) - (d2x * d1y); // det
|
||||
|
||||
//if (ABS(d) < 0.0000000001) {
|
||||
if (ABS(d) < 0.0001) {
|
||||
// lines are parallel or collinear
|
||||
return test && !parallelok ? null : keys.PARALLEL;
|
||||
}
|
||||
|
||||
var a = p1y - p3y, // origin dy
|
||||
b = p1x - p3x, // origin dx
|
||||
n1 = (d2x * a) - (d2y * b),
|
||||
n2 = (d1x * a) - (d1y * b);
|
||||
|
||||
a = n1 / d; // roughly distance from l1 origin to l2 intersection
|
||||
b = n2 / d; // roughly distance from l2 origin to l1 intersection
|
||||
|
||||
var ia = a >= -0.0001 && a <= 1.0001,
|
||||
ib = b >= -0.0001 && b <= 1.0001,
|
||||
segint = (ia && ib),
|
||||
rayint = (a >= 0 && b >= 0);
|
||||
|
||||
if (test === keys.SEGINT && !segint) return null;
|
||||
if (test === keys.RAYINT && !rayint) return null;
|
||||
|
||||
var ip = BASE.newPoint(
|
||||
p1x + (a * d1x), // x
|
||||
p1y + (a * d1y), // y
|
||||
p3.z || p4.z, // z
|
||||
segint ? keys.SEGINT : rayint ? keys.RAYINT : keys.PROJECT
|
||||
);
|
||||
|
||||
ip.dist = a;
|
||||
ip.p1 = p3;
|
||||
ip.p2 = p4;
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* used by {@link rayIntersect} and {@link Polygon.trace}
|
||||
*
|
||||
* @param {Point} ro
|
||||
* @param {Slope} s1
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {boolean} [infinite]
|
||||
* @returns {?Point}
|
||||
*/
|
||||
function intersectRayLine(ro, s1, p1, p2, infinite) {
|
||||
var keys = BASE.key,
|
||||
p1x = ro.x,
|
||||
p1y = ro.y,
|
||||
s1x = s1.dx,
|
||||
s1y = s1.dy,
|
||||
p3x = p1.x,
|
||||
p3y = p1.y,
|
||||
p4x = p2.x,
|
||||
p4y = p2.y,
|
||||
s2x = p4x - p3x,
|
||||
s2y = p4y - p3y,
|
||||
d = (s2y * s1x) - (s2x * s1y);
|
||||
|
||||
var a = p1y - p3y,
|
||||
b = p1x - p3x,
|
||||
n1 = (s2x * a) - (s2y * b),
|
||||
n2 = (s1x * a) - (s1y * b);
|
||||
|
||||
if (ABS(d) < 0.000000000001) {
|
||||
// lines are parallel or collinear
|
||||
return null;
|
||||
}
|
||||
|
||||
a = n1 / d;
|
||||
b = n2 / d;
|
||||
|
||||
if (infinite || (inCloseRange(b,0,1) && a >= 0)) {
|
||||
var ip = BASE.newPoint(
|
||||
p1x + (a * s1x),
|
||||
p1y + (a * s1y),
|
||||
p2.z || ro.z,
|
||||
keys.NONE
|
||||
);
|
||||
ip.dist = a;
|
||||
ip.p1 = p1;
|
||||
ip.p2 = p2;
|
||||
return ip;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {Point} p3
|
||||
* @param {Point} p4
|
||||
* @returns {number}
|
||||
*/
|
||||
function determinant(p1, p2, p3, p4) {
|
||||
var d1x = (p2.x - p1.x),
|
||||
d1y = (p2.y - p1.y),
|
||||
d2x = (p4.x - p3.x),
|
||||
d2y = (p4.y - p3.y);
|
||||
|
||||
return (d2y * d1x) - (d2x * d1y);
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Connect to base
|
||||
******************************************************************* */
|
||||
|
||||
BASE.key = {
|
||||
NONE : "",
|
||||
PROJECT : "project",
|
||||
SEGINT : "segint",
|
||||
RAYINT : "rayint",
|
||||
PARALLEL : "parallel"
|
||||
};
|
||||
|
||||
BASE.config = {
|
||||
// heal disjoint polygons in slicing (experimental)
|
||||
bridgeLineGapDistance : 0,
|
||||
// Bounds default margin nearTo
|
||||
// Polygon.offset mindist2 offset precision
|
||||
precision_offset : 0.05,
|
||||
// Polygon.isEquivalent area() isCloseTo
|
||||
precision_poly_area : 0.05,
|
||||
// Polygon.isEquivalent bounds() equals value
|
||||
precision_poly_bounds: 0.01,
|
||||
// Polygon.isEquivalent point distance to other poly line
|
||||
precision_poly_merge: 0.05,
|
||||
// Polygon.traceIntersects mindist2
|
||||
// Polygon.overlaps (bounds overlaps test precision)
|
||||
// Polygon.isEquivalent circularity (is circle if 1-this < merge)
|
||||
// Slope.isSame (vert/horiz w/in this value)
|
||||
// isCloseTo() default for dist
|
||||
// sliceIntersects point merge dist for non-fill
|
||||
precision_merge : 0.0001,
|
||||
precision_slice_z : 0.0001,
|
||||
// Point.isInPolygon nearPolygon value
|
||||
// Point.isInPolygonNotNear nearPolygon value
|
||||
// Point.isMergable2D distToSq2D value
|
||||
// Point.isMergable3D distToSq2D value
|
||||
// Polygon.isInside nearPolygon value
|
||||
// Polygon.isOutside nearPolygon value
|
||||
precision_merge_sq : sqr(0.0001),
|
||||
// Bound.isNested inflation value for potential parent
|
||||
precision_bounds : 0.0001,
|
||||
// Slope.isSame default precision
|
||||
precision_slope : 0.02,
|
||||
// Slope.isSame use to calculate precision
|
||||
precision_slope_merge : 0.25,
|
||||
// sliceIntersect point merge distance for fill
|
||||
precision_fill_merge : 0.001,
|
||||
// convertPoints point merge distance
|
||||
// other values break cube-s9 (wtf)
|
||||
precision_decimate : 0.05,
|
||||
// decimate test over this many points
|
||||
decimate_threshold : 100000,
|
||||
// Point.onLine precision distance (endpoints in Polygon.intersect)
|
||||
precision_point_on_line : 0.01,
|
||||
// Polygon.isEquivalent value for determining similar enough to test
|
||||
precision_circularity : 0.001,
|
||||
// polygon fill hinting (settings override)
|
||||
hint_len_min : sqr(3),
|
||||
hint_len_max : sqr(20),
|
||||
hint_min_circ : 0.15,
|
||||
// tolerances to determine if a point is near a masking polygon
|
||||
precision_mask_tolerance : 0.001,
|
||||
// Polygon isInside,isOutside tolerance (accounts for midpoint skew)
|
||||
precision_close_to_poly_sq : sqr(0.001),
|
||||
// how long a segment has to be to trigger a midpoint check (inner/outer)
|
||||
precision_midpoint_check_dist : 1,
|
||||
precision_nested_sq : sqr(0.01),
|
||||
// clipper multiplier
|
||||
clipper : 100000,
|
||||
// clipper poly clean
|
||||
clipperClean : 1000
|
||||
};
|
||||
|
||||
BASE.util = {
|
||||
sqr : sqr,
|
||||
time : time,
|
||||
round : round,
|
||||
area2: area2,
|
||||
distSq : dist2,
|
||||
distSqv2 : dist2v2,
|
||||
inRange : inRange,
|
||||
isCloseTo : isCloseTo,
|
||||
inCloseRange : inCloseRange,
|
||||
isCollinear : isCollinear,
|
||||
isClockwise : isClockwise,
|
||||
isCounterClockwise : isCounterClockwise,
|
||||
doCombinations : doCombinations,
|
||||
offsetPrecision : offsetPrecision,
|
||||
intersectRayLine : intersectRayLine,
|
||||
intersect : intersect,
|
||||
determinant : determinant
|
||||
};
|
||||
|
||||
})();
|
||||
223
js/kiri-codec.js
Normal file
223
js/kiri-codec.js
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
"use strict";
|
||||
|
||||
var gs_kiri_codec = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.kiri) self.kiri = {};
|
||||
if (self.kiri.codec) return;
|
||||
|
||||
var base = self.base,
|
||||
kiri = self.kiri,
|
||||
handlers = {};
|
||||
|
||||
kiri.codec = {
|
||||
encode: encode,
|
||||
decode: decode,
|
||||
registerDecoder: registerDecoder
|
||||
};
|
||||
|
||||
function encode(o, state) {
|
||||
state = state || {};
|
||||
if (o === null) return null;
|
||||
if (o === undefined) return undefined;
|
||||
if (Array.isArray(o)) {
|
||||
var arr = new Array(o.length), i=0;
|
||||
while (i < o.length) arr[i] = encode(o[i++], state);
|
||||
return arr;
|
||||
}
|
||||
switch (typeof(o)) {
|
||||
case 'string':
|
||||
case 'number':
|
||||
return o;
|
||||
case 'object':
|
||||
if (o.encode) return o.encode(state);
|
||||
return genOEncode(o, state);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function decode(o, state) {
|
||||
state = state || {};
|
||||
if (o === null) return null;
|
||||
if (o === undefined) return undefined;
|
||||
if (Array.isArray(o)) {
|
||||
for (var i=0; i < o.length; i++) o[i] = decode(o[i],state);
|
||||
return o;
|
||||
}
|
||||
switch (typeof(o)) {
|
||||
case 'string':
|
||||
case 'number':
|
||||
return o;
|
||||
case 'object':
|
||||
if (o.type && handlers[o.type]) return handlers[o.type](o,state);
|
||||
return genODecode(o, state);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function registerDecoder(type, handler) {
|
||||
handlers[type] = handler;
|
||||
}
|
||||
|
||||
function genOEncode(o, state) {
|
||||
if (o instanceof Float32Array) return o;
|
||||
var out = {};
|
||||
for (var k in o) {
|
||||
if (o.hasOwnProperty(k)) out[k] = encode(o[k], state);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function genODecode(o, state) {
|
||||
if (o instanceof Float32Array) return o;
|
||||
var out = {};
|
||||
for (var k in o) {
|
||||
if (o.hasOwnProperty(k)) out[k] = decode(o[k], state);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Object CODEC Functions
|
||||
******************************************************************* */
|
||||
|
||||
kiri.Slice.prototype.encode = function(state) {
|
||||
return {
|
||||
type: 'slice',
|
||||
z: this.z,
|
||||
index: this.index,
|
||||
camMode: this.camMode,
|
||||
tops: encode(this.tops, state),
|
||||
bridges: encode(this.bridges, state),
|
||||
flats: encode(this.flats, state),
|
||||
solids: encode(this.solids, state),
|
||||
supports: encode(this.supports, state)
|
||||
//groups: encode(this.groups, state)
|
||||
};
|
||||
};
|
||||
|
||||
registerDecoder('slice', function(v, state) {
|
||||
var slice = kiri.newSlice(v.z, state.mesh ? state.mesh.newGroup() : null);
|
||||
|
||||
slice.index = v.index;
|
||||
slice.camMode = v.camMode;
|
||||
slice.tops = decode(v.tops, state);
|
||||
slice.bridges = decode(v.bridges, state);
|
||||
slice.flats = decode(v.flats, state);
|
||||
slice.solids = decode(v.solids, state);
|
||||
slice.supports = decode(v.supports, state);
|
||||
//slice.groups = decode(v.groups, state);
|
||||
|
||||
return slice;
|
||||
});
|
||||
|
||||
kiri.Top.prototype.encode = function(state) {
|
||||
return {
|
||||
type: 'top',
|
||||
poly: encode(this.poly, state),
|
||||
traces: encode(this.traces, state),
|
||||
inner: encode(this.inner, state),
|
||||
solids: encode(this.solids, state),
|
||||
fill_lines: encodePointArray(this.fill_lines),
|
||||
fill_sparse: encode(this.fill_sparse, state)
|
||||
};
|
||||
};
|
||||
|
||||
registerDecoder('top', function(v, state) {
|
||||
var top = kiri.newTop(decode(v.poly, state));
|
||||
|
||||
top.traces = decode(v.traces, state);
|
||||
top.inner = decode(v.inner, state);
|
||||
top.solids = decode(v.solids, state);
|
||||
top.fill_lines = decodePointArray(v.fill_lines,state);
|
||||
top.fill_sparse = decode(v.fill_sparse, state);
|
||||
|
||||
return top;
|
||||
});
|
||||
|
||||
function encodePointArray(points) {
|
||||
if (!points) return null;
|
||||
|
||||
var array = new Float32Array(points.length * 3),
|
||||
pos = 0;
|
||||
|
||||
points.forEach(function(point) {
|
||||
array[pos++] = point.x;
|
||||
array[pos++] = point.y;
|
||||
array[pos++] = point.z;
|
||||
});
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
function decodePointArray(array) {
|
||||
if (!array) return null;
|
||||
|
||||
var vid = 0,
|
||||
pid = 0,
|
||||
points = new Array(array.length/3);
|
||||
|
||||
while (vid < array.length) {
|
||||
points[pid++] = base.newPoint(array[vid++], array[vid++], array[vid++]);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
base.Polygon.prototype.encode = function(state) {
|
||||
if (!state.poly) state.poly = {};
|
||||
|
||||
var cached = state.poly[this.id];
|
||||
|
||||
if (cached) {
|
||||
return {
|
||||
type: 'poly',
|
||||
ref: this.id
|
||||
};
|
||||
}
|
||||
|
||||
state.poly[this.id] = this;
|
||||
|
||||
return {
|
||||
type: 'poly',
|
||||
id: this.id,
|
||||
array: encodePointArray(this.points),
|
||||
open: this.isOpen(),
|
||||
inner: encode(this.inner, state),
|
||||
parent: encode(this.parent, state),
|
||||
fills: encodePointArray(this.fills),
|
||||
depth: this.depth
|
||||
};
|
||||
};
|
||||
|
||||
registerDecoder('poly', function(v, state) {
|
||||
if (!state.poly) state.poly = {};
|
||||
|
||||
if (v.ref) return state.poly[v.ref];
|
||||
|
||||
var poly = base.newPolygon(),
|
||||
vid = 0;
|
||||
|
||||
while (vid < v.array.length) {
|
||||
poly.push(base.newPoint(v.array[vid++], v.array[vid++], v.array[vid++]));
|
||||
}
|
||||
|
||||
poly.id = v.id;
|
||||
poly.open = v.open;
|
||||
// if (v.open) poly.setOpen(); else poly.setClosed();
|
||||
|
||||
state.poly[v.id] = poly;
|
||||
|
||||
poly.inner = decode(v.inner, state);
|
||||
poly.parent = decode(v.parent, state);
|
||||
poly.fills = decodePointArray(v.fills);
|
||||
poly.depth = v.depth;
|
||||
|
||||
return poly;
|
||||
});
|
||||
|
||||
|
||||
})();
|
||||
164
js/kiri-db.js
Normal file
164
js/kiri-db.js
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
"use strict";
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.kiri) self.kiri = { };
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function Catalog(motodb, decimate) {
|
||||
var store = this;
|
||||
store.db = motodb;
|
||||
store.files = {};
|
||||
store.listeners = [];
|
||||
store.autodec = decimate;
|
||||
store.deferredHandler = null;
|
||||
store.refresh();
|
||||
}
|
||||
|
||||
var kiri = self.kiri,
|
||||
DP = Catalog.prototype;
|
||||
|
||||
kiri.openCatalog = function(motodb,decimate) {
|
||||
return new Catalog(motodb,decimate);
|
||||
};
|
||||
|
||||
DP.refresh = function() {
|
||||
var store = this;
|
||||
store.db.get('files', function(files) {
|
||||
if (files) {
|
||||
store.files = files;
|
||||
notifyFileListeners(store);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
DP.wipe = function() {
|
||||
var key, files = this.files;
|
||||
for (key in files) {
|
||||
if (files.hasOwnProperty(key)) this.deleteFile(key);
|
||||
}
|
||||
};
|
||||
|
||||
DP.fileList = function() {
|
||||
return this.files;
|
||||
};
|
||||
|
||||
DP.addFileListener = function(listener) {
|
||||
if (!this.listeners.contains(listener)) {
|
||||
this.listeners.push(listener);
|
||||
listener(this.files);
|
||||
}
|
||||
};
|
||||
|
||||
DP.removeFileListener = function(listener) {
|
||||
this.listeners.remove(listener);
|
||||
};
|
||||
|
||||
function saveFileList(store) {
|
||||
store.db.put('files', store.files);
|
||||
notifyFileListeners(store);
|
||||
}
|
||||
|
||||
function notifyFileListeners(store) {
|
||||
for (var i=0; i<store.listeners.length; i++) {
|
||||
store.listeners[i](store.files);
|
||||
}
|
||||
}
|
||||
|
||||
DP.decimate = function(vertices, callback) {
|
||||
if (vertices.length < 500000) return callback(vertices);
|
||||
kiri.work.decimate(vertices, function(reply) {
|
||||
callback(reply);
|
||||
});
|
||||
};
|
||||
|
||||
DP.setDeferredHandler = function(handler) {
|
||||
this.deferredHandler = handler;
|
||||
};
|
||||
|
||||
DP.putDeferred = function(name, mark) {
|
||||
// triggers refresh callback
|
||||
this.files[name] = {
|
||||
deferred: mark
|
||||
};
|
||||
saveFileList(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @param {Float32Array} vertices
|
||||
* @param {Function} [callback]
|
||||
*/
|
||||
DP.putFile = function(name, vertices, callback) {
|
||||
var store = this;
|
||||
store.db.put('file-'+name, vertices, function(ok) {
|
||||
if (ok) {
|
||||
store.files[name] = {
|
||||
vertices: vertices.length/3,
|
||||
updated: new Date().getTime()
|
||||
};
|
||||
saveFileList(store);
|
||||
if (store.autodec) {
|
||||
store.decimate(vertices, function(decimated) {
|
||||
store.db.put('fdec-'+name, decimated);
|
||||
if (callback) callback(decimated);
|
||||
});
|
||||
} else if (callback) callback(ok);
|
||||
} else if (callback) callback(ok);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DP.getFile = function(name, callback) {
|
||||
var store = this,
|
||||
rec = store.files[name];
|
||||
if (rec && rec.deferred) {
|
||||
if (store.deferredHandler) return store.deferredHandler(rec.deferred, name, callback);
|
||||
return callback();
|
||||
}
|
||||
if (!this.autodec) {
|
||||
store.db.get('file-'+name, callback);
|
||||
return;
|
||||
}
|
||||
this.db.get('fdec-'+name, function(vertices) {
|
||||
if (vertices) {
|
||||
callback(vertices);
|
||||
} else {
|
||||
store.db.get('file-'+name, function(vertices) {
|
||||
if (vertices) {
|
||||
store.decimate(vertices, function(decimated) {
|
||||
store.db.put('fdec-'+name, decimated);
|
||||
callback(vertices);
|
||||
});
|
||||
} else {
|
||||
return callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @param {Function} callback
|
||||
*/
|
||||
DP.deleteFile = function(name, callback) {
|
||||
var store = this;
|
||||
if (store.files[name]) {
|
||||
delete store.files[name];
|
||||
store.db.remove('fdec-'+name);
|
||||
store.db.remove('file-'+name, function(ok) {
|
||||
saveFileList(store);
|
||||
if (callback) callback(ok);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (callback) callback(false);
|
||||
};
|
||||
|
||||
})();
|
||||
1573
js/kiri-driver-cam.js
Normal file
1573
js/kiri-driver-cam.js
Normal file
File diff suppressed because it is too large
Load diff
462
js/kiri-driver-fdm.js
Normal file
462
js/kiri-driver-fdm.js
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
/** Copyright 2014-2017 Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gs_kiri_fdm = exports;
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.kiri) self.kiri = { };
|
||||
if (!self.kiri.driver) self.kiri.driver = { };
|
||||
if (self.kiri.driver.FDM) return;
|
||||
|
||||
var KIRI = self.kiri,
|
||||
BASE = self.base,
|
||||
UTIL = BASE.util,
|
||||
CONF = BASE.config,
|
||||
FDM = KIRI.driver.FDM = { },
|
||||
POLY = BASE.polygons,
|
||||
SLICER = KIRI.slicer,
|
||||
newPoint = BASE.newPoint,
|
||||
time = UTIL.time;
|
||||
|
||||
/**
|
||||
* DRIVER SLICE CONTRACT
|
||||
*
|
||||
* Given a widget and settings object, call functions necessary to produce
|
||||
* slices and then the computations using those slices. This function is
|
||||
* designed to run client or server-side and provides all output via
|
||||
* callback functions.
|
||||
*
|
||||
* @param {Object} settings
|
||||
* @param {Widget} Widget
|
||||
* @param {Function} onupdate (called with % complete and optional message)
|
||||
* @param {Function} ondone (called when complete with an array of Slice objects)
|
||||
*/
|
||||
FDM.slice = function(settings, widget, onupdate, ondone) {
|
||||
var spro = settings.process,
|
||||
spri = settings.device,
|
||||
sout = settings.process,
|
||||
update_start = time(),
|
||||
minSolid = spro.sliceSolidMinArea,
|
||||
solidLayers = spro.sliceSolidLayers,
|
||||
doSolidLayers = solidLayers && !spro.sliceVase,
|
||||
firstOffset = spri.nozzleSize / 2,
|
||||
shellOffset = spri.nozzleSize * spro.sliceShellSpacing,
|
||||
fillOffset = shellOffset * settings.synth.fillOffsetMult,
|
||||
sliceFillAngle = spro.sliceFillAngle,
|
||||
view = widget.mesh && widget.mesh.newGroup ? widget.mesh.newGroup() : null;
|
||||
|
||||
if (spro.sliceHeight <= 0.01) {
|
||||
DBUG.log("invalid slice height");
|
||||
return ondone(null);
|
||||
}
|
||||
|
||||
SLICER.sliceWidget(widget, {
|
||||
height: spro.sliceHeight,
|
||||
view:view,
|
||||
firstHeight: spro.sliceHeight * sout.firstLayerHeight
|
||||
}, onSliceDone, onSliceUpdate);
|
||||
|
||||
function onSliceUpdate(update) {
|
||||
onupdate(0.0 + update * 0.5);
|
||||
}
|
||||
|
||||
function onSliceDone(slices) {
|
||||
widget.slices = slices;
|
||||
|
||||
if (!slices) return;
|
||||
|
||||
// calculate % complete and call onupdate()
|
||||
function doupdate(index, from, to, msg) {
|
||||
onupdate(0.5 + (from + ((index/slices.length) * (to-from))) * 0.5, msg);
|
||||
}
|
||||
|
||||
// for each slice, performe a function and call doupdate()
|
||||
function forSlices(from, to, fn, msg) {
|
||||
slices.forEach(function(slice) {
|
||||
fn(slice);
|
||||
doupdate(slice.index, from, to, msg)
|
||||
});
|
||||
}
|
||||
|
||||
// do not hint polygin fill longer than a max span length
|
||||
CONF.hint_len_max = UTIL.sqr(spro.sliceBridgeMax);
|
||||
|
||||
// reset (if necessary) for solids and support projections
|
||||
slices.forEach(function(slice) {
|
||||
slice.invalidateFill();
|
||||
slice.invalidateSolids();
|
||||
slice.invalidateSupports();
|
||||
});
|
||||
|
||||
var supportEnabled = spro.sliceSupportEnable && spro.sliceSupportDensity > 0.0,
|
||||
supportMinArea = spro.sliceSupportArea;
|
||||
|
||||
// create shells and diff inner fillable areas
|
||||
forSlices(0.0, 0.2, function(slice) {
|
||||
var solid = (
|
||||
slice.index < spro.sliceBottomLayers ||
|
||||
slice.index > slices.length - spro.sliceTopLayers-1 ||
|
||||
spro.sliceFillSparse > 0.95
|
||||
) && !spro.sliceVase;
|
||||
slice.doShells(spro.sliceShells, firstOffset, shellOffset, fillOffset, spro.sliceVase);
|
||||
if (solid) slice.doSolidLayerFill(spri.nozzleSize, sliceFillAngle, spro.sliceFillDensity);
|
||||
sliceFillAngle += 90.0;
|
||||
}, "offsets");
|
||||
|
||||
// calculations only relevant when solid layers are used
|
||||
if (doSolidLayers) {
|
||||
forSlices(0.2, 0.34, function(slice) {
|
||||
slice.doDiff(minSolid);
|
||||
}, "diff");
|
||||
forSlices(0.34, 0.35, function(slice) {
|
||||
slice.projectFlats(solidLayers);
|
||||
slice.projectBridges(solidLayers);
|
||||
}, "solids");
|
||||
forSlices(0.35, 0.5, function(slice) {
|
||||
slice.doSolidsFill(spri.nozzleSize, sliceFillAngle, spro.sliceFillDensity, minSolid);
|
||||
sliceFillAngle += 90.0;
|
||||
}, "solids");
|
||||
}
|
||||
|
||||
// calculations only relevant when supports are enabled
|
||||
if (supportEnabled) {
|
||||
forSlices(0.5, 0.7, function(slice) {
|
||||
slice.doSupport(spro.sliceSupportOffset, spro.sliceSupportSpan, spro.sliceSupportExtra, supportMinArea, spro.sliceSupportSize, spro.sliceSupportOffset);
|
||||
}, "support");
|
||||
forSlices(0.7, 0.8, function(slice) {
|
||||
slice.doSupportFill(spri.nozzleSize, spro.sliceSupportDensity, supportMinArea);
|
||||
}, "support");
|
||||
}
|
||||
|
||||
// sparse layers only present when non-vase mose and sparse % > 0
|
||||
if (!spro.sliceVase && spro.sliceFillSparse > 0.0) {
|
||||
forSlices(0.8, 1.0, function(slice) {
|
||||
slice.doSparseLayerFill(spri.nozzleSize, spro.sliceFillDensity, spro.sliceFillSparse, widget.getBoundingBox());
|
||||
}, "infill");
|
||||
}
|
||||
|
||||
// report slicing complete
|
||||
ondone();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* DRIVER PRINT CONTRACT
|
||||
*
|
||||
* @param {Object} print state object
|
||||
* @param {Function} update incremental callback
|
||||
*/
|
||||
FDM.printSetup = function(print, update) {
|
||||
var widgets = print.widgets,
|
||||
settings = print.settings,
|
||||
device = settings.device,
|
||||
process = settings.process,
|
||||
mode = settings.mode,
|
||||
output = print.output,
|
||||
printPoint = newPoint(0,0,0),
|
||||
maxLayers = 0,
|
||||
layer = 0,
|
||||
mesh,
|
||||
meshIndex,
|
||||
layerout,
|
||||
closest,
|
||||
mindist,
|
||||
minidx,
|
||||
find,
|
||||
mslices,
|
||||
slices,
|
||||
sliceEntry;
|
||||
|
||||
// find max layers (for updates)
|
||||
widgets.forEach(function(widget) {
|
||||
maxLayers = Math.max(maxLayers, widget.slices.length);
|
||||
});
|
||||
|
||||
// for each layer until no layers are found
|
||||
for (;;) {
|
||||
slices = [];
|
||||
layerout = [];
|
||||
|
||||
// create list of mesh slice arrays with their platform offsets
|
||||
for (meshIndex = 0; meshIndex < widgets.length; meshIndex++) {
|
||||
mesh = widgets[meshIndex].mesh;
|
||||
if (!mesh.widget) continue;
|
||||
mslices = mesh.widget.slices;
|
||||
if (mslices && mslices[layer]) {
|
||||
slices.push({slice:mslices[layer], offset:mesh.position});
|
||||
}
|
||||
}
|
||||
|
||||
if (slices.length === 0) break;
|
||||
|
||||
// create brim, if specificed in FDM mode (code shared by laser)
|
||||
if (layer === 0 && process.outputBrimCount) {
|
||||
var brims = [],
|
||||
polys = [],
|
||||
preout = [],
|
||||
startPoint = printPoint;
|
||||
|
||||
widgets.forEach(function(widget) {
|
||||
var tops = [];
|
||||
widget.slices[0].tops.forEach(function(top) {
|
||||
tops.push(top.poly.clone());
|
||||
});
|
||||
POLY.nest(tops).forEach(function(poly) {
|
||||
poly.offset(-process.outputBrimOffset).forEach(function(brim) {
|
||||
brim.move(widget.mesh.position);
|
||||
brims.push(brim);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
POLY.union(brims).forEach(function(brim) {
|
||||
POLY.trace2count(brim, polys, -device.nozzleSize, process.outputBrimCount, 0);
|
||||
});
|
||||
|
||||
printPoint = print.poly2polyEmit(polys, printPoint, function(poly, index, count, startPoint) {
|
||||
return print.polyPrintPath(poly, startPoint, preout);
|
||||
});
|
||||
|
||||
print.addPrintPoints(preout, layerout, startPoint);
|
||||
}
|
||||
|
||||
// iterate over layer slices, find closest widget, print, eliminate
|
||||
for (;;) {
|
||||
closest = null;
|
||||
mindist = Infinity;
|
||||
for (meshIndex = 0; meshIndex < slices.length; meshIndex++) {
|
||||
sliceEntry = slices[meshIndex];
|
||||
if (!sliceEntry) continue;
|
||||
find = sliceEntry.slice.findClosestPointTo(printPoint.sub(sliceEntry.offset));
|
||||
if (find && (!closest || find.distance < mindist)) {
|
||||
closest = sliceEntry;
|
||||
mindist = find.distance;
|
||||
minidx = meshIndex;
|
||||
}
|
||||
}
|
||||
if (!closest) break;
|
||||
slices[minidx] = null;
|
||||
// output seek to start point between mesh slices if previous data
|
||||
printPoint = print.slicePrintPath(closest.slice, printPoint.sub(closest.offset), closest.offset, layerout, true);
|
||||
}
|
||||
|
||||
if (layerout.length) output.append(layerout);
|
||||
layer++;
|
||||
update(layer / maxLayers);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {Array} gcode lines
|
||||
*/
|
||||
FDM.printExport = function(print, online) {
|
||||
var layers = print.output,
|
||||
settings = print.settings,
|
||||
device = settings.device,
|
||||
process = settings.process,
|
||||
time = 0,
|
||||
layer = 0,
|
||||
fan_power = device.gcodeFan,
|
||||
trackProgress = device.gcodeTrack,
|
||||
layer1speed = process.firstLayerSpeed,
|
||||
decimals = 4,
|
||||
output = [],
|
||||
outputLength = 0,
|
||||
lastProgress = 0,
|
||||
progress = 0,
|
||||
distance = 0,
|
||||
emitted = 0,
|
||||
pos = {x:0, y:0, z:0, f:0},
|
||||
zinc = process.sliceHeight,
|
||||
zpos = zinc * process.firstLayerHeight,
|
||||
offset = process.outputOriginCenter ? null : {
|
||||
x: device.bedWidth/2,
|
||||
y: device.bedDepth/2
|
||||
},
|
||||
consts = {
|
||||
temp: process.outputTemp,
|
||||
temp_bed: process.outputBedTemp,
|
||||
bed_temp: process.outputBedTemp,
|
||||
fan_speed: process.outputFanMax,
|
||||
speed: process.outputFanMax,
|
||||
top: offset ? device.bedDepth : device.bedDepth/2,
|
||||
left: offset ? 0 : -device.bedWidth/2,
|
||||
right: offset ? device.bedWidth : device.bedWidth/2,
|
||||
bottom: offset ? 0 : -device.bedDepth/2,
|
||||
z_max: device.maxHeight
|
||||
},
|
||||
shortDist = process.outputShortDistance,
|
||||
shortFact = process.outputShortFactor,
|
||||
maxPrintMMM = process.outputFeedrate * 60,
|
||||
seekMMM = process.outputSeekrate * 60,
|
||||
retOver = process.outputRetractOver,
|
||||
retDist = process.outputRetractDist,
|
||||
retSpeed = process.outputRetractSpeed * 60,
|
||||
// ratio of nozzle area to filament area times
|
||||
// ratio of slice height to filament max noodle height
|
||||
emitPerMM = print.extrudePerMM(device.nozzleSize, device.filamentSize, process.sliceHeight),
|
||||
emitPerMMLayer1 = print.extrudePerMM(device.nozzleSize, device.filamentSize, process.sliceHeight * process.firstLayerHeight),
|
||||
constReplace = print.constReplace,
|
||||
pidx, path, out, lastp, dist, printMMM, shortMMM,
|
||||
appendAll = function(arr) {
|
||||
arr.forEach(function(line) { append(line) });
|
||||
},
|
||||
append,
|
||||
lines = 0,
|
||||
bytes = 0;
|
||||
|
||||
if (online) {
|
||||
append = function(line) {
|
||||
if (line) {
|
||||
lines++;
|
||||
bytes += line.length;
|
||||
output.append(line);
|
||||
}
|
||||
if (!line || output.length > 1000) {
|
||||
online(output.join("\n"));
|
||||
output = [];
|
||||
}
|
||||
};
|
||||
} else {
|
||||
append = function(line) {
|
||||
if (!line) return;
|
||||
output.append(line);
|
||||
lines++;
|
||||
bytes += line.length;
|
||||
}
|
||||
}
|
||||
|
||||
append("; Generated by KIRI:MOTO");
|
||||
append("; "+new Date().toString());
|
||||
append(constReplace("; Bed left:{left} right:{right} top:{top} bottom:{bottom}", consts));
|
||||
append("; --- startup ---");
|
||||
for (var i=0; i<device.gcodePre.length; i++) {
|
||||
append(constReplace(device.gcodePre[i], consts));
|
||||
}
|
||||
|
||||
function moveTo(newpos, rate, comment) {
|
||||
var o = ['G1'];
|
||||
if (typeof newpos.x === 'number') {
|
||||
pos.x = UTIL.round(newpos.x,decimals);
|
||||
o.append(" X").append(pos.x);
|
||||
}
|
||||
if (typeof newpos.y === 'number') {
|
||||
pos.y = UTIL.round(newpos.y,decimals);
|
||||
o.append(" Y").append(pos.y);
|
||||
}
|
||||
if (typeof newpos.z === 'number') {
|
||||
pos.z = UTIL.round(newpos.z,decimals);
|
||||
o.append(" Z").append(pos.z);
|
||||
}
|
||||
if (typeof newpos.e === 'number') {
|
||||
outputLength += newpos.e;
|
||||
o.append(" E").append(UTIL.round(newpos.e,decimals));
|
||||
}
|
||||
if (rate && rate != pos.f) {
|
||||
o.append(" F").append(Math.round(rate));
|
||||
pos.f = rate
|
||||
}
|
||||
if (comment) {
|
||||
o.append(" ; ").append(comment);
|
||||
}
|
||||
append(o.join(''));
|
||||
}
|
||||
|
||||
// find total distance traveled by head as approx for progress
|
||||
var allout = [],
|
||||
totaldistance = 0;
|
||||
layers.forEach(function(outs) { allout.appendAll(outs) });
|
||||
allout.forEachPair(function (o1, o2) {
|
||||
totaldistance += o1.point.distTo2D(o2.point);
|
||||
},1);
|
||||
|
||||
while (layer < layers.length) {
|
||||
append("; --- layer "+layer+" ---");
|
||||
// second layer fan on
|
||||
if (layer === 1 && fan_power) append(constReplace(fan_power,consts));
|
||||
// first layer 50% underspeed
|
||||
shortMMM = shortFact * maxPrintMMM;
|
||||
printMMM = maxPrintMMM;
|
||||
if (layer === 0) {
|
||||
printMMM *= layer1speed;
|
||||
shortMMM *= layer1speed;
|
||||
}
|
||||
path = layers[layer];
|
||||
moveTo({z:zpos}, seekMMM);
|
||||
zpos += zinc;
|
||||
for (pidx=0; pidx<path.length; pidx++) {
|
||||
out = path[pidx];
|
||||
// if no point in output, it's a dwell command
|
||||
if (!out.point) {
|
||||
append("G4 P" + out.speed);
|
||||
continue;
|
||||
}
|
||||
var x = out.point.x,
|
||||
y = out.point.y;
|
||||
|
||||
if (process.outputInvertX) x = -x;
|
||||
if (process.outputInvertY) y = -y;
|
||||
if (offset) {
|
||||
x += offset.x;
|
||||
y += offset.y;
|
||||
}
|
||||
|
||||
dist = lastp ? lastp.distTo2D(out.point) : 0;
|
||||
distance += dist;
|
||||
progress = Math.round((distance / totaldistance) * 100);
|
||||
if (lastp && out.emit) {
|
||||
var outMMM = printMMM,
|
||||
emitMM = emitPerMM * out.emit * dist;
|
||||
if (layer === 0) {
|
||||
emitMM = emitPerMMLayer1 * out.emit * dist;
|
||||
}
|
||||
if (dist < shortDist) {
|
||||
outMMM = shortMMM + ((outMMM - shortMMM) * (dist / shortDist));
|
||||
} else {
|
||||
// approximate compensation for acceleration & deceleration
|
||||
time += (shortDist * 2) / outMMM / 10 * 60;
|
||||
}
|
||||
// print time
|
||||
time += (dist / outMMM) * 60;
|
||||
moveTo({x:x, y:y, e:emitMM}, outMMM);
|
||||
emitted += emitMM;
|
||||
} else {
|
||||
if (retOver > 0.0 && dist > retOver) moveTo({e:-retDist}, retSpeed, "ooze retract");
|
||||
moveTo({x:x, y:y}, seekMMM);
|
||||
if (retOver > 0.0 && dist > retOver) moveTo({e:retDist}, retSpeed, "re-engage");
|
||||
time += (dist / seekMMM) * 60; // seek distance
|
||||
time += (retDist / retSpeed) * 60 * 2; // retraction time
|
||||
// approximate compensation for acceleration & deceleration
|
||||
time += (shortDist * 2) / seekMMM / 10 * 60;
|
||||
}
|
||||
lastp = out.point;
|
||||
// emit tracked progress
|
||||
if (trackProgress && progress != lastProgress) {
|
||||
append(constReplace(trackProgress, {progress:progress}));
|
||||
lastProgress = progress;
|
||||
}
|
||||
}
|
||||
layer++;
|
||||
}
|
||||
|
||||
append("; --- shutdown ---");
|
||||
for (var i=0; i<device.gcodePost.length; i++) {
|
||||
append(constReplace(device.gcodePost[i], consts));
|
||||
}
|
||||
append("; --- filament used: "+UTIL.round(emitted,decimals)+"mm ---");
|
||||
|
||||
// force emit of buffer
|
||||
append();
|
||||
|
||||
print.distance = emitted;
|
||||
print.lines = lines;
|
||||
print.bytes = bytes + lines - 1;
|
||||
print.time = time;
|
||||
|
||||
return online ? null : output.join("\n");
|
||||
};
|
||||
|
||||
})();
|
||||
354
js/kiri-driver-laser.js
Normal file
354
js/kiri-driver-laser.js
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/** Copyright 2014-2017 Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gs_kiri_laser = exports;
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.kiri) self.kiri = { };
|
||||
if (!self.kiri.driver) self.kiri.driver = { };
|
||||
if (self.kiri.driver.LASER) return;
|
||||
|
||||
var KIRI = self.kiri,
|
||||
BASE = self.base,
|
||||
UTIL = BASE.util,
|
||||
DBUG = BASE.debug,
|
||||
LASER = KIRI.driver.LASER = { },
|
||||
SLICER = KIRI.slicer,
|
||||
newPoint = BASE.newPoint;
|
||||
|
||||
/**
|
||||
* DRIVER SLICE CONTRACT
|
||||
*
|
||||
* @param {Object} settings
|
||||
* @param {Widget} Widget
|
||||
* @param {Function} onupdate (called with % complete and optional message)
|
||||
* @param {Function} ondone (called when complete with an array of Slice objects)
|
||||
*/
|
||||
LASER.slice = function(settings, widget, onupdate, ondone) {
|
||||
var proc = settings.process;
|
||||
|
||||
if (proc.laserSliceHeight < 0) {
|
||||
DBUG.log("invalid slice height");
|
||||
return ondone(false);
|
||||
}
|
||||
|
||||
SLICER.sliceWidget(widget, {height: proc.laserSliceHeight}, function(slices) {
|
||||
widget.slices = slices;
|
||||
slices.forEach(function(slice, index) {
|
||||
slice.doShells(1, -proc.laserOffset);
|
||||
onupdate(0.80 + (index/slices.length) * 0.20);
|
||||
});
|
||||
ondone(true);
|
||||
}, function(update) {
|
||||
onupdate(0.0 + update * 0.80)
|
||||
});
|
||||
};
|
||||
|
||||
function sliceEmitObjects(print, slice, objects) {
|
||||
var start = newPoint(0,0,0);
|
||||
|
||||
function laserOut(poly, object) {
|
||||
if (!poly) return;
|
||||
if (Array.isArray(poly)) {
|
||||
poly.forEach(function(pi) {
|
||||
laserOut(pi, object);
|
||||
});
|
||||
} else {
|
||||
print.polyPrintPath(poly, start, object, 1);
|
||||
}
|
||||
}
|
||||
|
||||
slice.tops.forEach(function(top) {
|
||||
var object = [];
|
||||
laserOut(top.traces, object);
|
||||
laserOut(top.innerTraces(), object);
|
||||
objects.push(object);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* DRIVER PRINT CONTRACT
|
||||
*
|
||||
* @param {Object} print state object
|
||||
* @param {Function} update incremental callback
|
||||
*/
|
||||
LASER.printSetup = function(print, update) {
|
||||
var widgets = print.widgets,
|
||||
settings = print.settings,
|
||||
device = settings.device,
|
||||
process = settings.process,
|
||||
mode = settings.mode,
|
||||
output = print.output,
|
||||
totalSlices = 0,
|
||||
slices = 0;
|
||||
|
||||
// find max layers (for updates)
|
||||
widgets.forEach(function(widget) {
|
||||
totalSlices += widget.slices.length;
|
||||
});
|
||||
|
||||
// emit objects from each slice into output array
|
||||
widgets.forEach(function(widget) {
|
||||
widget.slices.forEach(function(slice) {
|
||||
sliceEmitObjects(print, slice, output);
|
||||
update(slices++ / totalSlices);
|
||||
});
|
||||
});
|
||||
|
||||
// compute tile width / height
|
||||
output.forEach(function(layerout) {
|
||||
var min = {w:Infinity, h:Infinity}, max = {w:-Infinity, h:-Infinity}, p;
|
||||
layerout.forEach(function(out) {
|
||||
p = out.point;
|
||||
min.w = Math.min(min.w, p.x);
|
||||
max.w = Math.max(max.w, p.x);
|
||||
min.h = Math.min(min.h, p.y);
|
||||
max.h = Math.max(max.h, p.y);
|
||||
});
|
||||
layerout.w = max.w - min.w;
|
||||
layerout.h = max.h - min.h;
|
||||
});
|
||||
|
||||
// do object layout packing
|
||||
var i, m, e,
|
||||
MOTO = self.moto,
|
||||
device = settings.device,
|
||||
process = settings.process,
|
||||
mp = [device.bedWidth, device.bedDepth],
|
||||
ms = [mp[0] / 2, mp[1] / 2],
|
||||
mi = mp[0] > mp[1] ? [(mp[0] / mp[1]) * 10, 10] : [10, (mp[1] / mp[1]) * 10],
|
||||
// sort objects by size
|
||||
c = output.sort(function (a, b) { return (b.w * b.h) - (a.w * a.h) }),
|
||||
p = new MOTO.Pack(ms[0], ms[1], process.outputTileSpacing).fit(c);
|
||||
|
||||
while (!p.packed) {
|
||||
ms[0] += mi[0];
|
||||
ms[1] += mi[1];
|
||||
p = new MOTO.Pack(ms[0], ms[1], process.outputTileSpacing).fit(c);
|
||||
}
|
||||
|
||||
for (i = 0; i < c.length; i++) {
|
||||
m = c[i];
|
||||
m.fit.x += m.w / 2 + p.pad;
|
||||
m.fit.y += m.h / 2 + p.pad;
|
||||
m.forEach(function(o, i) {
|
||||
// because first point emitted twice (begin/end)
|
||||
e = o.point = o.point.clone();
|
||||
e.x += p.max.w / 2 - m.fit.x;
|
||||
e.y += p.max.h / 2 - m.fit.y;
|
||||
e.z = 0;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function exportElements(print, onpre, onpoly, onpost, onpoint) {
|
||||
|
||||
var process = print.settings.process,
|
||||
output = print.output,
|
||||
last,
|
||||
point,
|
||||
poly = [],
|
||||
min = {x:0, y:0},
|
||||
max = {x:0, y:0};
|
||||
|
||||
output.forEach(function(layer) {
|
||||
layer.forEach(function(out) {
|
||||
point = out.point;
|
||||
if (process.outputInvertX) point.x = -point.x;
|
||||
if (process.outputInvertY) point.y = -point.y;
|
||||
point.x *= process.outputTileScaling;
|
||||
point.y *= process.outputTileScaling;
|
||||
min.x = Math.min(min.x, point.x);
|
||||
max.x = Math.max(max.x, point.x);
|
||||
min.y = Math.min(min.y, point.y);
|
||||
max.y = Math.max(max.y, point.y);
|
||||
});
|
||||
});
|
||||
|
||||
// normalize against origin lower left
|
||||
if (!process.outputOriginCenter) {
|
||||
output.forEach(function(layer) {
|
||||
layer.forEach(function(out) {
|
||||
point = out.point;
|
||||
point.x -= min.x;
|
||||
point.y -= min.y;
|
||||
});
|
||||
});
|
||||
max.x = max.x - min.x;
|
||||
max.y = max.y - min.y;
|
||||
min.x = 0;
|
||||
min.y = 0;
|
||||
}
|
||||
|
||||
onpre(min, max, process.outputLaserPower, process.outputLaserSpeed);
|
||||
|
||||
output.forEach(function(layer) {
|
||||
layer.forEach(function(out) {
|
||||
point = out.point;
|
||||
if (out.emit) {
|
||||
if (last && poly.length === 0) poly.push(onpoint(last));
|
||||
poly.push(onpoint(point));
|
||||
} else if (poly.length > 0) {
|
||||
onpoly(poly);
|
||||
poly = [];
|
||||
}
|
||||
last = point;
|
||||
});
|
||||
if (poly.length > 0) {
|
||||
onpoly(poly);
|
||||
poly = [];
|
||||
}
|
||||
});
|
||||
|
||||
onpost();
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
LASER.exportGCode = function(print) {
|
||||
var lines = [], dx = 0, dy = 0, feedrate, laser_on;
|
||||
|
||||
exportElements(
|
||||
print,
|
||||
function(min, max, power, speed) {
|
||||
var width = (max.x - min.x),
|
||||
height = (max.y - min.y);
|
||||
dx = min.x;
|
||||
dy = min.y;
|
||||
feedrate = " F" + speed,
|
||||
laser_on = "M106 S" + UTIL.round(256 * (power / 100), 3);
|
||||
// pre
|
||||
},
|
||||
function(poly) {
|
||||
poly.forEach(function(point, index) {
|
||||
if (index === 0) {
|
||||
lines.push("G0 " + point);
|
||||
} else if (index === 1) {
|
||||
lines.push(laser_on);
|
||||
lines.push("G1 " + point + feedrate);
|
||||
} else {
|
||||
lines.push("G1 " + point);
|
||||
}
|
||||
});
|
||||
lines.push("M107");
|
||||
},
|
||||
function() {
|
||||
// post
|
||||
},
|
||||
function(point) {
|
||||
return "X" + UTIL.round(point.x - dx, 3) + " Y" + UTIL.round(point.y - dy, 3);
|
||||
}
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
LASER.exportSVG = function(print) {
|
||||
var lines = [], dx = 0, dy = 0, my;
|
||||
|
||||
exportElements(
|
||||
print,
|
||||
function(min, max) {
|
||||
var width = (max.x - min.x),
|
||||
height = (max.y - min.y);
|
||||
dx = min.x;
|
||||
dy = min.y;
|
||||
my = max.y;
|
||||
lines.push('<?xml version="1.0" standalone="no"?>');
|
||||
lines.push('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">');
|
||||
lines.push('<svg width="'+width+'mm" height="'+height+'mm" viewBox="0 0 '+width+' '+height+'" xmlns="http://www.w3.org/2000/svg" version="1.1">');
|
||||
},
|
||||
function(poly) {
|
||||
lines.push('<polyline points="'+poly.join(' ')+'" fill="none" stroke="blue" stroke-width="0.01mm" />');
|
||||
},
|
||||
function() {
|
||||
lines.push("</svg>");
|
||||
},
|
||||
function(point) {
|
||||
return UTIL.round(point.x - dx, 3) + "," + UTIL.round(my - point.y - dy, 3);
|
||||
}
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
LASER.exportDXF = function(print) {
|
||||
var lines = [];
|
||||
|
||||
exportElements(
|
||||
print,
|
||||
function(min, max) {
|
||||
lines.appendAll([
|
||||
' 0',
|
||||
'SECTION',
|
||||
' 2',
|
||||
'HEADER',
|
||||
' 9',
|
||||
'$ACADVER',
|
||||
'1',
|
||||
'AC1014',
|
||||
' 0',
|
||||
'ENDSEC',
|
||||
' 0',
|
||||
'SECTION',
|
||||
' 2',
|
||||
'ENTITIES',
|
||||
]);
|
||||
},
|
||||
function(poly) {
|
||||
lines.appendAll([
|
||||
' 0',
|
||||
'LWPOLYLINE',
|
||||
'100', // subgroup required
|
||||
'AcDbPolyline',
|
||||
' 90', // poly vertices
|
||||
poly.length,
|
||||
' 70', // open
|
||||
'0',
|
||||
' 43', // constant width line
|
||||
'0.0'
|
||||
]);
|
||||
|
||||
poly.forEach(function(point) {
|
||||
lines.appendAll([
|
||||
' 10',
|
||||
point.x,
|
||||
' 20',
|
||||
point.y
|
||||
]);
|
||||
});
|
||||
|
||||
lines.appendAll([
|
||||
' 0',
|
||||
'SEQEND',
|
||||
]);
|
||||
},
|
||||
function() {
|
||||
lines.appendAll([
|
||||
' 0',
|
||||
'ENDSEC',
|
||||
' 0',
|
||||
'EOF',
|
||||
]);
|
||||
},
|
||||
function(point) {
|
||||
return {x:point.x,y:point.y};
|
||||
}
|
||||
);
|
||||
|
||||
return lines.join('\n');
|
||||
};
|
||||
|
||||
})();
|
||||
165
js/kiri-layer.js
Normal file
165
js/kiri-layer.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
"use strict";
|
||||
|
||||
var gs_kiri_layer = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.kiri) self.kiri = {};
|
||||
if (self.kiri.Layer) return;
|
||||
|
||||
var KIRI = self.kiri,
|
||||
LP = Layer.prototype,
|
||||
mcache = {};
|
||||
|
||||
KIRI.Layer = Layer;
|
||||
KIRI.newLayer = function(view) { return new Layer(view) };
|
||||
|
||||
function materialFor(color) {
|
||||
var m = mcache[color];
|
||||
if (!m) {
|
||||
m = mcache[color] = new THREE.LineBasicMaterial({
|
||||
fog: false,
|
||||
color: color,
|
||||
linewidth: 1
|
||||
});
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
function toVector(p) {
|
||||
return new THREE.Vector3(p.x, p.y, p.z);
|
||||
}
|
||||
|
||||
function addPoly(arr, poly, deep, open) {
|
||||
var points = poly.points,
|
||||
len = points.length;
|
||||
|
||||
if (len < 2) return;
|
||||
|
||||
var doOpen = (open === undefined || open === null) ? poly.isOpen() : open,
|
||||
end = doOpen ? len - 1 : len,
|
||||
last = toVector(points[0]),
|
||||
i = 0;
|
||||
|
||||
while (i < end) {
|
||||
arr.push(last);
|
||||
arr.push(last = toVector(points[(++i) % len]));
|
||||
}
|
||||
|
||||
if (deep && poly.inner) {
|
||||
poly.inner.forEach(function(p) {
|
||||
addPoly(arr, p, false, open);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {THREE.Group} view
|
||||
*/
|
||||
function Layer(view) {
|
||||
this.changed = false;
|
||||
this.group = null;
|
||||
this.view = view;
|
||||
this.bycolor = {};
|
||||
};
|
||||
|
||||
LP.setVisible = function(vis) {
|
||||
if (this.group) this.group.visible = vis;
|
||||
};
|
||||
|
||||
LP.clear = function() {
|
||||
this.changed = true;
|
||||
this.bycolor = {};
|
||||
};
|
||||
|
||||
LP.add = function(color, obj) {
|
||||
var ca = this.bycolor[color];
|
||||
if (!ca) ca = this.bycolor[color] = [];
|
||||
ca.push(obj);
|
||||
};
|
||||
|
||||
LP.poly = function(poly, color, deep, open) {
|
||||
var layer = this;
|
||||
if (Array.isArray(poly)) {
|
||||
poly.forEach(function(p) { layer.poly(p, color, deep, open) });
|
||||
} else {
|
||||
this.add(color, {poly:poly, deep:deep, open:open});
|
||||
this.changed = true;
|
||||
}
|
||||
};
|
||||
|
||||
LP.points = function(points, color, size, opacity) {
|
||||
var layer = this,
|
||||
sz = size/2;
|
||||
points.forEach(function(p) {
|
||||
layer.lines([
|
||||
toVector({x:p.x+sz, y:p.y+sz, z:p.z-sz}),
|
||||
toVector({x:p.x+sz, y:p.y+sz, z:p.z+sz}),
|
||||
toVector({x:p.x-sz, y:p.y+sz, z:p.z-sz}),
|
||||
toVector({x:p.x-sz, y:p.y+sz, z:p.z+sz}),
|
||||
toVector({x:p.x+sz, y:p.y-sz, z:p.z-sz}),
|
||||
toVector({x:p.x+sz, y:p.y-sz, z:p.z+sz}),
|
||||
toVector({x:p.x-sz, y:p.y-sz, z:p.z-sz}),
|
||||
toVector({x:p.x-sz, y:p.y-sz, z:p.z+sz}),
|
||||
|
||||
toVector({x:p.x+sz, y:p.y+sz, z:p.z+sz}),
|
||||
toVector({x:p.x-sz, y:p.y+sz, z:p.z+sz}),
|
||||
toVector({x:p.x+sz, y:p.y+sz, z:p.z-sz}),
|
||||
toVector({x:p.x-sz, y:p.y+sz, z:p.z-sz}),
|
||||
toVector({x:p.x+sz, y:p.y-sz, z:p.z+sz}),
|
||||
toVector({x:p.x-sz, y:p.y-sz, z:p.z+sz}),
|
||||
toVector({x:p.x+sz, y:p.y-sz, z:p.z-sz}),
|
||||
toVector({x:p.x-sz, y:p.y-sz, z:p.z-sz}),
|
||||
|
||||
toVector({x:p.x+sz, y:p.y+sz, z:p.z+sz}),
|
||||
toVector({x:p.x+sz, y:p.y-sz, z:p.z+sz}),
|
||||
toVector({x:p.x+sz, y:p.y+sz, z:p.z-sz}),
|
||||
toVector({x:p.x+sz, y:p.y-sz, z:p.z-sz}),
|
||||
toVector({x:p.x-sz, y:p.y+sz, z:p.z+sz}),
|
||||
toVector({x:p.x-sz, y:p.y-sz, z:p.z+sz}),
|
||||
toVector({x:p.x-sz, y:p.y+sz, z:p.z-sz}),
|
||||
toVector({x:p.x-sz, y:p.y-sz, z:p.z-sz}),
|
||||
], color);
|
||||
});
|
||||
};
|
||||
|
||||
LP.lines = function(points, color) {
|
||||
this.add(color, {lines:points});
|
||||
this.changed = true;
|
||||
};
|
||||
|
||||
LP.render = function() {
|
||||
if (!(this.view && this.changed)) return;
|
||||
if (this.group) this.view.remove(this.group);
|
||||
this.group = this.view.newGroup();
|
||||
|
||||
var bycolor = this.bycolor,
|
||||
key, added;
|
||||
|
||||
for (key in bycolor) {
|
||||
if (!bycolor.hasOwnProperty(key)) continue;
|
||||
|
||||
var geo = new THREE.Geometry(),
|
||||
arr = geo.vertices,
|
||||
mat = materialFor(parseInt(key));
|
||||
added = bycolor[key];
|
||||
|
||||
added.forEach(function(obj) {
|
||||
if (obj.poly) {
|
||||
addPoly(arr, obj.poly, obj.deep, obj.open);
|
||||
} else if (obj.lines) {
|
||||
obj.lines.forEach(function(p) {
|
||||
arr.push(toVector(p));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (arr.length > 0) this.group.add(new THREE.LineSegments(geo, mat));
|
||||
}
|
||||
|
||||
this.changed = false;
|
||||
}
|
||||
|
||||
})();
|
||||
64
js/kiri-pack.js
Normal file
64
js/kiri-pack.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"use strict";
|
||||
|
||||
var gs_kiri_pack = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
/**
|
||||
* adapted from
|
||||
* http://codeincomplete.com/posts/2011/5/7/bin_packing/
|
||||
*/
|
||||
|
||||
(function (){
|
||||
|
||||
function Packer (w, h, spacing) {
|
||||
this.root = { x: 0, y: 0, w: w, h: h };
|
||||
this.max = { w: 0, h: 0 };
|
||||
this.packed = false;
|
||||
this.spacing = typeof(spacing) === 'number' ? spacing : 1;
|
||||
this.pad = this.spacing / 2;
|
||||
}
|
||||
|
||||
Packer.prototype = {
|
||||
|
||||
fit: function (blocks) {
|
||||
var n = 0, node, block, w, h;
|
||||
while (n < blocks.length) {
|
||||
block = blocks[n++];
|
||||
w = block.w + this.spacing;
|
||||
h = block.h + this.spacing;
|
||||
if (node = this.findNode(this.root, w, h)) {
|
||||
block.fit = this.splitNode(node, w, h);
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
this.packed = true;
|
||||
return this;
|
||||
},
|
||||
|
||||
findNode: function (root, w, h) {
|
||||
if (root.used) {
|
||||
return this.findNode(root.right, w, h) || this.findNode(root.down, w, h);
|
||||
} else if (w <= root.w && h <= root.h) {
|
||||
return root;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
splitNode: function (node, w, h) {
|
||||
node.used = true;
|
||||
node.down = { x: node.x, y: node.y + h, w: node.w, h: node.h - h };
|
||||
node.right = { x: node.x + w, y: node.y, w: node.w - w, h: h };
|
||||
this.max.w = Math.max(this.max.w, node.x + w);
|
||||
this.max.h = Math.max(this.max.h, node.y + h);
|
||||
return node;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
if (!self.moto) self.moto = {};
|
||||
self.moto.Pack = Packer;
|
||||
|
||||
})();
|
||||
765
js/kiri-print.js
Normal file
765
js/kiri-print.js
Normal file
|
|
@ -0,0 +1,765 @@
|
|||
/** Copyright 2014-2017 Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gs_kiri_print = exports;
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.kiri) self.kiri = {};
|
||||
|
||||
var KIRI = self.kiri,
|
||||
DRIVERS = KIRI.driver,
|
||||
CAM = DRIVERS.CAM,
|
||||
FDM = DRIVERS.FDM,
|
||||
LASER = DRIVERS.LASER,
|
||||
BASE = self.base,
|
||||
UTIL = BASE.util,
|
||||
DBUG = BASE.debug,
|
||||
POLY = BASE.polygons,
|
||||
SQRT = Math.sqrt,
|
||||
PI = Math.PI,
|
||||
PROTO = Print.prototype,
|
||||
Polygon = BASE.Polygon,
|
||||
newPoint = BASE.newPoint;
|
||||
|
||||
KIRI.newPrint = function(settings, widgets, id) { return new Print(settings, widgets, id) };
|
||||
|
||||
/**
|
||||
* @param {Object} settings
|
||||
* @param {Widget[]} widgets
|
||||
* @constructor
|
||||
*/
|
||||
function Print(settings, widgets, id) {
|
||||
this.id = id || new Date().getTime().toString(36);
|
||||
this.settings = settings;
|
||||
this.widgets = widgets;
|
||||
|
||||
this.group = new THREE.Group();
|
||||
this.layerView = [];
|
||||
|
||||
this.time = 0;
|
||||
this.lines = 0;
|
||||
this.bytes = 0;
|
||||
this.output = [];
|
||||
this.distance = 0;
|
||||
this.bounds = null;
|
||||
}
|
||||
|
||||
PROTO.newOut = newOut;
|
||||
PROTO.tip2tipEmit = tip2tipEmit;
|
||||
PROTO.extrudePerMM = extrudePerMM;
|
||||
PROTO.constReplace = constReplace;
|
||||
PROTO.poly2polyEmit = poly2polyEmit;
|
||||
PROTO.addPrintPoints = addPrintPoints;
|
||||
PROTO.poly2polyDepthFirstEmit = poly2polyDepthFirstEmit;
|
||||
|
||||
PROTO.parseGCode = function(gcode, offset) {
|
||||
var lines = gcode
|
||||
.toUpperCase()
|
||||
.replace("X", " X")
|
||||
.replace("Y", " Y")
|
||||
.replace("Z", " Z")
|
||||
.replace("E", " E")
|
||||
.replace("F", " F")
|
||||
.replace(" ", " ")
|
||||
.split("\n");
|
||||
|
||||
var scope = this,
|
||||
output = scope.output = [],
|
||||
bounds = scope.bounds = {
|
||||
max: { x:-Infinity, y:-Infinity, z:-Infinity},
|
||||
min: { x:Infinity, y:Infinity, z:Infinity}
|
||||
},
|
||||
seq = [],
|
||||
move = false,
|
||||
E0G0 = false,
|
||||
G0 = function() {
|
||||
move = true;
|
||||
if (seq.length > 0) {
|
||||
output.push(seq);
|
||||
seq = [];
|
||||
}
|
||||
},
|
||||
LZ = 0.0,
|
||||
pos = {
|
||||
X: 0.0,
|
||||
Y: 0.0,
|
||||
Z: 0.0,
|
||||
F: 0.0,
|
||||
E: 0.0
|
||||
},
|
||||
off = {
|
||||
x: offset ? offset.x || 0 : 0,
|
||||
y: offset ? offset.y || 0 : 0,
|
||||
z: offset ? offset.z || 0 : 0
|
||||
};
|
||||
|
||||
lines.forEach(function(line) {
|
||||
line = line.split(" ");
|
||||
if (line.length < 2) return;
|
||||
switch (line.shift()) {
|
||||
case 'G0':
|
||||
G0();
|
||||
case 'G1':
|
||||
line.forEach(function(tok) {
|
||||
pos[tok.charAt(0)] = parseFloat(tok.substring(1));
|
||||
});
|
||||
if (pos.X) bounds.min.x = Math.min(bounds.min.x, pos.X);
|
||||
if (pos.X) bounds.max.x = Math.max(bounds.max.x, pos.X);
|
||||
if (pos.Y) bounds.min.y = Math.min(bounds.min.y, pos.Y);
|
||||
if (pos.Y) bounds.max.y = Math.max(bounds.max.y, pos.Y);
|
||||
if (pos.Z) bounds.min.z = Math.min(bounds.min.z, pos.Z);
|
||||
if (pos.Z) bounds.max.z = Math.max(bounds.max.z, pos.Z);
|
||||
if (pos.E) E0G0 = true;
|
||||
if (E0G0 && pos.E === 0.0) {
|
||||
if (LZ != pos.Z) G0();
|
||||
else move = true;
|
||||
}
|
||||
seq.push(newOut(
|
||||
{x:pos.X + off.x, y:pos.Y + off.y, z:pos.Z + off.z},
|
||||
!move,
|
||||
pos.F
|
||||
));
|
||||
break;
|
||||
case 'M6':
|
||||
break;
|
||||
}
|
||||
move = false;
|
||||
pos.E = 0.0;
|
||||
LZ = pos.Z;
|
||||
});
|
||||
|
||||
G0();
|
||||
|
||||
scope.lines = lines.length;
|
||||
scope.bytes = gcode.length;
|
||||
};
|
||||
|
||||
PROTO.setup = function(remote, onupdate, ondone) {
|
||||
var scope = this,
|
||||
settings = scope.settings,
|
||||
mode = settings.mode;
|
||||
|
||||
if (remote) {
|
||||
KIRI.work.printSetup(settings, function(reply) {
|
||||
if (reply.done) {
|
||||
scope.output = reply.output;
|
||||
ondone();
|
||||
} else {
|
||||
onupdate(reply.update, reply.updateStatus)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
var driver = KIRI.driver[mode];
|
||||
if (driver) driver.printSetup(scope, onupdate);
|
||||
else console.log({missing_print_driver: mode});
|
||||
ondone();
|
||||
}
|
||||
};
|
||||
|
||||
PROTO.exportGCode = function(remote, ondone, online) {
|
||||
var scope = this,
|
||||
settings = scope.settings,
|
||||
mode = settings.mode;
|
||||
|
||||
if (remote) {
|
||||
KIRI.work.printGCode(function(reply) {
|
||||
scope.lines = reply.lines;
|
||||
scope.bytes = reply.bytes;
|
||||
scope.bounds = reply.bounds;
|
||||
scope.distance = reply.distance;
|
||||
scope.time = reply.time;
|
||||
ondone(reply.gcode);
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
var driver = KIRI.driver[mode];
|
||||
if (driver && driver.printExport) {
|
||||
ondone(driver.printExport(scope, online));
|
||||
} else {
|
||||
console.log({missing_export_driver: mode});
|
||||
ondone(null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
PROTO.exportLaserGCode = function() {
|
||||
return KIRI.driver.LASER.exportGCode(this);
|
||||
};
|
||||
|
||||
PROTO.exportSVG = function() {
|
||||
return KIRI.driver.LASER.exportSVG(this);
|
||||
};
|
||||
|
||||
PROTO.exportDXF = function() {
|
||||
return KIRI.driver.LASER.exportDXF(this);
|
||||
};
|
||||
|
||||
PROTO.encodeOutput = function() {
|
||||
var newout = [], newlayer;
|
||||
|
||||
this.output.forEach(function(layerout) {
|
||||
newlayer = [];
|
||||
newout.push(newlayer);
|
||||
layerout.forEach(function(out) {
|
||||
if (out.point) newlayer.push({emit:out.emit, point:{x:out.point.x, y:out.point.y, z:out.point.z}});
|
||||
});
|
||||
});
|
||||
return newout;
|
||||
};
|
||||
|
||||
PROTO.render = function() {
|
||||
var scope = this,
|
||||
mode = scope.settings.mode;
|
||||
|
||||
switch (mode) {
|
||||
case 'CAM':
|
||||
case 'FDM':
|
||||
scope.renderMoves(true, 0x0088aa);
|
||||
break;
|
||||
case 'LASER':
|
||||
scope.renderMoves(false, 0x0088aa);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
PROTO.renderMoves = function(showMoves, moveColor) {
|
||||
var scope = this, last, view;
|
||||
// render layered output
|
||||
scope.lines = 0;
|
||||
scope.output.forEach(function(layerout) {
|
||||
var move = [], print = [], z;
|
||||
layerout.forEach(function(out) {
|
||||
if (last) {
|
||||
if (UTIL.distSq(last, out.point) < 0.001 && out.point.z === last.z) {
|
||||
return;
|
||||
}
|
||||
if (out.emit > 0) {
|
||||
print.push(last);
|
||||
print.push(out.point);
|
||||
} else {
|
||||
move.push(last);
|
||||
move.push(out.point);
|
||||
}
|
||||
} else {
|
||||
if (out.emit) DBUG.log("first point is emit");
|
||||
z = out.point.z;
|
||||
}
|
||||
last = out.point;
|
||||
});
|
||||
view = KIRI.newLayer(scope.group);
|
||||
scope.layerView.push(view);
|
||||
if (showMoves) view.lines(move, moveColor);
|
||||
view.lines(print, 0x5566aa);
|
||||
view.render();
|
||||
scope.lines += print.length;
|
||||
});
|
||||
}
|
||||
|
||||
PROTO.getLayerCount = function() {
|
||||
return this.output.length;
|
||||
}
|
||||
|
||||
PROTO.hide = function() {
|
||||
this.layerView.forEach(function(layer) {
|
||||
layer.setVisible(false);
|
||||
})
|
||||
};
|
||||
|
||||
PROTO.showLayer = function(index, show) {
|
||||
if (this.layerView[index]) this.layerView[index].setVisible(show);
|
||||
};
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function Output(point, emit, speed, tool) {
|
||||
this.point = point; // point to emit
|
||||
this.emit = emit; // emit (feed for printers, power for lasers, cut for cam)
|
||||
this.speed = speed;
|
||||
this.tool = tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Point} point
|
||||
* @param {number} emit (0=move, !0=filament emit/laser on/cut mode)
|
||||
* @param {number} [speed] speed
|
||||
* @param {number} [tool] tool
|
||||
*/
|
||||
function newOut(point, emit, speed, tool) {
|
||||
return new Output(point, emit, speed, tool);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Polygon} poly
|
||||
* @param {Point} startPoint
|
||||
* @param {Array} output
|
||||
* @param {number} [extrude] multiplier
|
||||
* @param {number} [extrude] wipe distance
|
||||
* @return {Point} last output point
|
||||
*/
|
||||
PROTO.polyPrintPath = function(poly, startPoint, output, extrude, wipe) {
|
||||
// poly.setClosed();
|
||||
poly.setClockwise();
|
||||
|
||||
var mindist = Infinity,
|
||||
closest = poly.findClosestPointTo(startPoint),
|
||||
dist,
|
||||
first = true,
|
||||
settings = this.settings,
|
||||
shellMult = extrude || settings.process.outputShellMult;
|
||||
|
||||
poly.forEachPoint(function(point) {
|
||||
if (first) {
|
||||
// move from startPoint to point
|
||||
output.push(newOut(point, 0));
|
||||
first = false;
|
||||
} else {
|
||||
output.push(newOut(point, shellMult));
|
||||
}
|
||||
}, true, closest.index);
|
||||
|
||||
if (wipe) {
|
||||
wipe = Math.min(wipe, poly.perimeter());
|
||||
poly.forEachSegment(function(point, next) {
|
||||
// exit loop when no more wipe
|
||||
if (!(wipe && wipe > 0.1)) return true;
|
||||
dist = point.distTo2D(next);
|
||||
if (dist <= wipe) {
|
||||
output.push(newOut(next, 0));
|
||||
wipe -= dist;
|
||||
} else {
|
||||
output.push(newOut(point.followTo(next, wipe), 0));
|
||||
return true;
|
||||
}
|
||||
}, false, closest.index);
|
||||
}
|
||||
|
||||
return output[output.length - 1].point;
|
||||
};
|
||||
|
||||
/**
|
||||
* create 3d print output path for this slice
|
||||
*
|
||||
* @parma {Slice} slice
|
||||
* @param {Point} startPoint start as close as possible to startPoint
|
||||
* @param {THREE.Vector3} offset
|
||||
* @param {Point[]} output points
|
||||
* @param {boolean} isFDM controls whether we emit wipe or not
|
||||
* @return {Point} last output point
|
||||
*/
|
||||
PROTO.slicePrintPath = function(slice, startPoint, offset, output, isFDM) {
|
||||
var i,
|
||||
preout = [],
|
||||
scope = this,
|
||||
settings = this.settings,
|
||||
process = settings.process,
|
||||
minSeek = settings.device.nozzleSize * 1.5,
|
||||
wipeDist = process.outputWipeDistance, // todo disable for laser mode
|
||||
fillMult = process.outputFillMult,
|
||||
seekMult = fillMult * 0.5,
|
||||
origin = startPoint.add(offset),
|
||||
extrude = process.outputShellMult || (process.laserSliceHeight >= 0 ? 1 : 0),
|
||||
z = slice.z;
|
||||
|
||||
function outputTraces(poly, extrude, last) {
|
||||
if (!poly) return;
|
||||
if (Array.isArray(poly)) {
|
||||
outputOrderClosest(poly, function(next) {
|
||||
outputTraces(next, extrude, last);
|
||||
});
|
||||
} else {
|
||||
startPoint = scope.polyPrintPath(poly, startPoint, preout, extrude, isFDM && last ? wipeDist : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Polygon[]} polys
|
||||
*/
|
||||
function outputSparse(polys) {
|
||||
if (!polys) return;
|
||||
var lines = [], p1, p2, iter;
|
||||
polys.forEach(function(poly) {
|
||||
poly.forEachSegment(function(p1,p2) {
|
||||
lines.push(p1.clone());
|
||||
lines.push(p2.clone());
|
||||
}, true);
|
||||
});
|
||||
outputFills(lines);
|
||||
}
|
||||
|
||||
function outputFills(lines, extrude) {
|
||||
var mindist, p1, p2, dist, point, find,
|
||||
printMult = extrude || fillMult;
|
||||
while (lines) {
|
||||
find = null;
|
||||
mindist = Infinity;
|
||||
for (i=0; i<lines.length; i++) {
|
||||
point = lines[i];
|
||||
if (point.del) continue;
|
||||
dist = SQRT(startPoint.distToSq2D(point));
|
||||
if (dist < mindist) {
|
||||
find = {i:i, p:point};
|
||||
mindist = dist;
|
||||
}
|
||||
}
|
||||
if (find) {
|
||||
if (find.i % 2 === 0) {
|
||||
p1 = find.p;
|
||||
p2 = lines[find.i + 1];
|
||||
} else {
|
||||
p1 = find.p;
|
||||
p2 = lines[find.i - 1];
|
||||
}
|
||||
p1.del = true;
|
||||
p2.del = true;
|
||||
preout.push(newOut(p1, SQRT(startPoint.distToSq2D(p1)) < minSeek ? seekMult : 0));
|
||||
preout.push(newOut(p2, printMult));
|
||||
startPoint = p2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// clear delete marks so we can re-print later
|
||||
if (lines) lines.forEach(function(p) { p.del = false });
|
||||
}
|
||||
|
||||
/**
|
||||
* given array of polygons, emit them in next closest order
|
||||
* @param {Array} array of Polygon or Polygon wrappers
|
||||
* @param {Function} fn
|
||||
* @param {Function} fnp convert 'next' object into a Polygon
|
||||
*/
|
||||
function outputOrderClosest(array, fn, fnp) {
|
||||
array = array.slice();
|
||||
var closest, find, next, poly;
|
||||
for (;;) {
|
||||
closest = null;
|
||||
for (i=0; i<array.length; i++) {
|
||||
next = array[i];
|
||||
if (!next) continue;
|
||||
poly = fnp ? fnp(next) : next;
|
||||
find = poly.findClosestPointTo(startPoint);
|
||||
if (!closest || find.distance < closest.distance) {
|
||||
closest = find;
|
||||
closest.i = i;
|
||||
closest.next = next;
|
||||
}
|
||||
}
|
||||
if (!closest) return;
|
||||
array[closest.i] = null;
|
||||
fn(closest.next);
|
||||
}
|
||||
}
|
||||
|
||||
var all = [].appendAll(slice.supports || []).appendAll(slice.tops || []);
|
||||
outputOrderClosest(all || [], function(next) {
|
||||
if (next instanceof Polygon) {
|
||||
// support polygon
|
||||
next.setZ(z);
|
||||
outputTraces(next, extrude);
|
||||
outputTraces(next.inner, extrude);
|
||||
if (next.fills) {
|
||||
next.fills.forEach(function(p) { p.z = z });
|
||||
outputFills(next.fills, extrude);
|
||||
}
|
||||
} else {
|
||||
// top object
|
||||
outputTraces(next.traces, extrude);
|
||||
outputTraces(next.innerTraces(), extrude);
|
||||
outputFills(next.fill_lines);
|
||||
outputSparse(next.fill_sparse);
|
||||
}
|
||||
}, function(obj) {
|
||||
return obj instanceof Polygon ? obj : obj.poly;
|
||||
});
|
||||
|
||||
// offset print points
|
||||
for (i=0; i<preout.length; i++) {
|
||||
preout[i].point = preout[i].point.add(offset);
|
||||
}
|
||||
|
||||
// add offset points to total print
|
||||
addPrintPoints(preout, output, origin);
|
||||
|
||||
return startPoint.add(offset);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Output[]} input
|
||||
* @param {Point[]} output
|
||||
* @param {Point} [startPoint]
|
||||
*/
|
||||
function addPrintPoints(input, output, startPoint) {
|
||||
if (startPoint && input.length > 0) {
|
||||
output.push(newOut(startPoint, 0));
|
||||
}
|
||||
output.appendAll(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* emit each element in an array based on
|
||||
* the next closest endpoint.
|
||||
* todo replace outputFills() with this
|
||||
*/
|
||||
function tip2tipEmit(array, startPoint, emitter) {
|
||||
var mindist, dist, found, count = 0;
|
||||
|
||||
for (;;) {
|
||||
found = null;
|
||||
mindist = Infinity;
|
||||
array.forEach(function(el) {
|
||||
if (el.delete) return;
|
||||
dist = startPoint.distTo3D(el.first);
|
||||
if (dist < mindist) {
|
||||
found = {el:el, first:el.first, last:el.last};
|
||||
mindist = dist;
|
||||
}
|
||||
dist = startPoint.distTo3D(el.last);
|
||||
if (dist < mindist) {
|
||||
found = {el:el, first:el.last, last:el.first};
|
||||
mindist = dist;
|
||||
}
|
||||
});
|
||||
if (found) {
|
||||
found.el.delete = true;
|
||||
startPoint = found.last;
|
||||
emitter(found.el, found.first, ++count);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return startPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* like tip2tipEmit but accepts an array of
|
||||
* polygons and the next closest point can
|
||||
* be anywhere in the adjacent polygon
|
||||
*/
|
||||
function poly2polyEmit(array, startPoint, emitter) {
|
||||
var mindist, dist, found, count = 0;
|
||||
for (;;) {
|
||||
found = null;
|
||||
mindist = Infinity;
|
||||
array.forEach(function(poly) {
|
||||
if (poly.delete) return;
|
||||
if (poly.isOpen()) {
|
||||
const d2f = startPoint.distTo2D(poly.first());
|
||||
const d2l = startPoint.distTo2D(poly.first());
|
||||
if (d2f > mindist && d2l > mindist) return;
|
||||
if (d2l < mindist && d2l < d2f) {
|
||||
poly.reverse();
|
||||
found = {poly:poly, index:0, point:poly.first()};
|
||||
} else if (d2f < mindist) {
|
||||
found = {poly:poly, index:0, point:poly.first()};
|
||||
}
|
||||
return;
|
||||
}
|
||||
poly.forEachPoint(function(point, index) {
|
||||
dist = startPoint.distTo3D(point);
|
||||
if (dist < mindist) {
|
||||
found = {poly:poly, index:index, point:point};
|
||||
mindist = dist;
|
||||
}
|
||||
});
|
||||
});
|
||||
if (found) {
|
||||
found.poly.delete = true;
|
||||
startPoint = emitter(found.poly, found.index, ++count, startPoint) || found.point;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// undo delete marks
|
||||
array.forEach(function(poly) { poly.delete = false });
|
||||
|
||||
return startPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Polygon[][]} array of array of polygons representing each layer (top down)
|
||||
* @param {Point} startPoint entry point for algorithm
|
||||
* @param {Function} emitter called to emit each polygon
|
||||
* @param {number} offset tool diameter used for this depth-first cut
|
||||
*
|
||||
* used for CAM depth first layer output
|
||||
*/
|
||||
function poly2polyDepthFirstEmit(array, startPoint, emitter, offset) {
|
||||
var layers = [],
|
||||
pools;
|
||||
|
||||
array.forEach(function(layerPolys, layerIndex) {
|
||||
pools = [];
|
||||
layers.push(pools);
|
||||
|
||||
// flattening but preserving inner relationships
|
||||
// allows iterating over all layer polys to determine
|
||||
// if they deserve their own pool
|
||||
flattenPolygons(POLY.nest(layerPolys, true, true)).sort(function(p1,p2) {
|
||||
// sort by area descending
|
||||
return p2.area() - p1.area();
|
||||
}).forEach(function (poly) {
|
||||
// a polygon should be made into a pool if:
|
||||
// - it is open
|
||||
// - it has more than one sibling
|
||||
// - it has no parent (top/outer most)
|
||||
// - it is offset from its parent by more than diameter
|
||||
if (poly.isOpen() || !poly.parent || poly.parent.innerCount() > 1 || !polygonWithinOffset(poly, poly.parent, offset)) {
|
||||
pools.push(poly);
|
||||
poly.pool = [];
|
||||
poly.poolsDown = [];
|
||||
} else {
|
||||
// otherwise walk up the parent tree to find a pool to join
|
||||
var search = poly.parent;
|
||||
// walk up until pool found
|
||||
while (search && !search.pool) {
|
||||
search = search.parent;
|
||||
}
|
||||
// open polygons can be unparented and without a pool
|
||||
if (!search) {
|
||||
console.log({orphan:poly});
|
||||
return;
|
||||
}
|
||||
// add to pool
|
||||
search.pool.push(poly);
|
||||
}
|
||||
});
|
||||
|
||||
// sort pools increasing in size to aid fitting from below
|
||||
pools.sort(function (p1, p2) {
|
||||
return p1.area() - p2.area();
|
||||
});
|
||||
|
||||
// add add pools to smallest enclosing pool in layer above
|
||||
const poolsAbove = layers[layerIndex - 1];
|
||||
|
||||
if (layerIndex > 0)
|
||||
pools.forEach(function(pool) {
|
||||
for (var i=0; i<poolsAbove.length; i++) {
|
||||
const above = poolsAbove[i];
|
||||
// can only add open polys to open polys
|
||||
if (above.isOpen() && pool.isClosed()) {
|
||||
// console.log({skip_open_above:above});
|
||||
continue;
|
||||
}
|
||||
// if pool fits into smallest above pool, add it and break
|
||||
if (polygonFitsIn(pool, above, 0.1)) {
|
||||
above.poolsDown.push(pool);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const emitPool = function(poolPoly) {
|
||||
if (poolPoly.mark) return;
|
||||
poolPoly.mark = true;
|
||||
const polys = poolPoly.pool.slice().append(poolPoly);
|
||||
startPoint = poly2polyEmit(polys, startPoint, emitter);
|
||||
poolPoly.poolsDown.forEach(function(downPool) {
|
||||
emitPool(downPool);
|
||||
});
|
||||
};
|
||||
|
||||
// from the top layer, iterate and descend through all connected pools
|
||||
// pools are sorted smallest to largest. pools are polygons with an
|
||||
// attached 'pool' array of polygons
|
||||
layers.forEach(function(pools) {
|
||||
pools.forEach(function(poolPoly) {
|
||||
emitPool(poolPoly);
|
||||
});
|
||||
})
|
||||
return startPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* flatten deeply nested polygons preserving inner arrays
|
||||
*
|
||||
* @param {Polygon | Polygon[]} poly or array to flatten
|
||||
* @param {Polygon[]} to
|
||||
* @returns {Polygon[]}
|
||||
*/
|
||||
function flattenPolygons(poly, to) {
|
||||
if (!poly) return;
|
||||
if (!to) to = [];
|
||||
if (Array.isArray(poly)) {
|
||||
poly.forEach(function(p) {
|
||||
flattenPolygons(p, to);
|
||||
})
|
||||
} else {
|
||||
to.push(poly);
|
||||
flattenPolygons(poly.inner, to);
|
||||
}
|
||||
return to;
|
||||
}
|
||||
|
||||
function polygonFitsIn(inside, outside, tolerance) {
|
||||
return inside.isInside(outside, tolerance);
|
||||
// return inside.area() <= outside.area() + tolerance &&
|
||||
// (polygonWithinOffset(inside, outside, tolerance) || inside.isInside(outside, tolerance));
|
||||
}
|
||||
|
||||
function polygonWithinOffset(poly1, poly2, offset) {
|
||||
return polygonMinOffset(poly1, poly2, offset) <= offset;
|
||||
}
|
||||
|
||||
function polygonMinOffset(poly1, poly2, offset) {
|
||||
var mindist = Infinity;
|
||||
poly1.forEachPoint(function(p) {
|
||||
const nextdist = p.distToPolySegments(poly2, offset);
|
||||
mindist = Math.min(mindist, nextdist);
|
||||
// returning true terminates forEachPoint()
|
||||
if (mindist <= offset) return true;
|
||||
});
|
||||
return mindist;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param noz nozzle diameter
|
||||
* @param fil filament diameter
|
||||
* @param slice height in mm
|
||||
* @returns filament extruded per mm
|
||||
*/
|
||||
function extrudePerMM(noz, fil, slice) {
|
||||
return ((PI * UTIL.sqr(noz/2)) /
|
||||
(PI * UTIL.sqr(fil/2))) *
|
||||
(slice / noz);
|
||||
}
|
||||
|
||||
function constOp(tok, consts, opch, op) {
|
||||
var pos, v1, v2;
|
||||
if ((pos = tok.indexOf(opch)) > 0) {
|
||||
v1 = consts[tok.substring(0,pos)] || 0;
|
||||
v2 = parseInt(tok.substring(pos+1)) || 0;
|
||||
return op(v1,v2);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function constReplace(str, consts, start) {
|
||||
var cs = str.indexOf("{", start || 0),
|
||||
ce = str.indexOf("}", cs),
|
||||
tok, nutok, nustr;
|
||||
if (cs >=0 && ce > cs) {
|
||||
tok = str.substring(cs+1,ce);
|
||||
nutok =
|
||||
constOp(tok, consts, "-", function(v1,v2) { return v1-v2 }) ||
|
||||
constOp(tok, consts, "+", function(v1,v2) { return v1+v2 }) ||
|
||||
constOp(tok, consts, "/", function(v1,v2) { return v1/v2 }) ||
|
||||
constOp(tok, consts, "*", function(v1,v2) { return v1*v2 }) ||
|
||||
consts[tok] || 0;
|
||||
nustr = str.replace("{"+tok+"}",nutok);
|
||||
return constReplace(nustr, consts, ce+1+(nustr.length-str.length));
|
||||
} else {
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
608
js/kiri-serial.js
Normal file
608
js/kiri-serial.js
Normal file
|
|
@ -0,0 +1,608 @@
|
|||
(function() {
|
||||
if (!self.kiri) return;
|
||||
if (self.kiri.serial) return;
|
||||
|
||||
// tinyg motor (en/dis)able $me / $md
|
||||
// tinyg '%' to flush queue
|
||||
// grbl motor (en/dis)able $1=<ms> (free after ms) $1=255 (always energized)
|
||||
|
||||
var SELF = self,
|
||||
KIRI = SELF.kiri,
|
||||
SPACE = KIRI.space,
|
||||
LOC = SELF.location,
|
||||
SDB = moto.KV,
|
||||
API = kiri.api,
|
||||
initDone = false,
|
||||
nextID = new Date().getTime(),
|
||||
localQueue = [], // buffered command queue
|
||||
localQueueMax = 1500, // max local buffer
|
||||
bounds = null, // bounding box of gcode
|
||||
gcode = null, // gcode buffer
|
||||
gcodeIndex = 0, // gcode buffer send index
|
||||
gcodeAbort = false, // send abort
|
||||
logBuffer = [], // on screen log buffer
|
||||
sendBatchMax = 1000, // max to queue to waiting
|
||||
gcLinesSent, // status of local lines sent
|
||||
gcRemoteQueue, // status of remote queue
|
||||
gcPaused = false, // true if user paused
|
||||
status = null, // machine status
|
||||
socket, // connection to serial sender
|
||||
ports, // list of available ports
|
||||
senderTimeout, // repeating sender call
|
||||
waitingForAck = [], // ids sent to spjs awaiting ack
|
||||
spjsQueueCount = 0, // spjs mem queue size
|
||||
spjsQueueCountMax = 1500, // do not send to spjs over this queue size
|
||||
selectedPort,
|
||||
selectedMode,
|
||||
commandInput,
|
||||
logDiv,
|
||||
jogInput,
|
||||
hostInput,
|
||||
queueInput,
|
||||
connect, // button
|
||||
senderStatus, // machine status
|
||||
senderDialog,
|
||||
senderGcPause, // pause button
|
||||
senderPortClose, // button
|
||||
senderSelectMode, // select dropdown
|
||||
senderSelectPort; // select dropdown
|
||||
|
||||
var init_tinyg = [
|
||||
'{"ej":""}', // enable json
|
||||
'{"js":1}', // json syntax strict
|
||||
'{"sr":n}', // status request
|
||||
'{"sv":1}', // status verbosity filtered
|
||||
'{"si":250}', // status interval 250ms
|
||||
'{"qr":n}', // request queue report
|
||||
'{"qv":1}', // set queue report verbosity
|
||||
'{"ec":0}', // disable LF (from CRLF)
|
||||
'{"jv":4}', // json verbosity 4
|
||||
'{"hp":n}', // get hardware platform
|
||||
'{"fb":n}', // get firmware version
|
||||
'{"mt":n}', // get motor timeout
|
||||
'{"sr":n}',
|
||||
'{"pos":n}' // request position info
|
||||
],
|
||||
init_grbl = [
|
||||
'*init*', // init port
|
||||
'*status*' // request position info
|
||||
];
|
||||
|
||||
KIRI.serial = {
|
||||
setGCode: setGCode,
|
||||
show: show,
|
||||
hide: hide,
|
||||
toggle: toggle,
|
||||
init: init
|
||||
};
|
||||
|
||||
function init() {
|
||||
if (initDone) return;
|
||||
initDone = true;
|
||||
|
||||
var pre = LOC.protocol === 'https:' ? 'wss://' : 'ws://',
|
||||
rpx = $('srxi'),
|
||||
rpy = $('sryi'),
|
||||
rpz = $('srzi'),
|
||||
wpx = $('saxi'),
|
||||
wpy = $('sayi'),
|
||||
wpz = $('sazi');
|
||||
|
||||
senderDialog = $('sender');
|
||||
senderStatus = $('sender-status');
|
||||
commandInput = $('sender-command');
|
||||
logDiv = $('sender-log');
|
||||
jogInput = $('sender-jog');
|
||||
hostInput = $('sender-host');
|
||||
connect = $('sender-connect');
|
||||
gcLinesSent = $('sender-gc-sent');
|
||||
gcRemoteQueue = $('sender-gc-queue');
|
||||
senderSelectMode = $('sender-mode');
|
||||
senderSelectPort = $('sender-port');
|
||||
senderPortClose = $('sender-port-close');
|
||||
senderGcPause = $('sender-gc-pause');
|
||||
|
||||
senderSelectMode.onchange = selectMode;
|
||||
senderSelectPort.onchange = selectPort;
|
||||
senderPortClose.onclick = closePort;
|
||||
senderPortClose.disabled = true;
|
||||
|
||||
$('sender-close').onclick = hide;
|
||||
$('sender-spjs').onclick = function() { window.open("https://wiki.grid.space/wiki/GCode-Sender-in-CAM-Mode", "_help")};
|
||||
$('sjx-').onclick = function() { jog('X',-1) };
|
||||
$('sjx+').onclick = function() { jog('X',1) };
|
||||
$('sjy-').onclick = function() { jog('Y',-1) };
|
||||
$('sjy+').onclick = function() { jog('Y',1) };
|
||||
$('sjz-').onclick = function() { jog('Z',-1) };
|
||||
$('sjz+').onclick = function() { jog('Z',1) };
|
||||
$('sender-set-zero').onclick = function() { sendNow("G92 X0Y0Z0") };
|
||||
$('sender-goto-zero').onclick = function() { sendNow("G90 G0X0Y0Z0") };
|
||||
$('sender-ctrlx').onclick = softReset;
|
||||
$('sender-hold').onclick = feedHold;
|
||||
$('sender-resume').onclick = feedResume;
|
||||
|
||||
$('sender-pad').onmouseover = function() {
|
||||
jogInput.focus();
|
||||
};
|
||||
$('sender-pad').addEventListener('keydown', function(ev) {
|
||||
var dist = (ev.cmdKey ? 0.1 : 1);
|
||||
switch (ev.keyCode) {
|
||||
case 37: // left arrow
|
||||
jog('X', -dist);
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case 39: // right arrow
|
||||
jog('X', dist);
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case 38: // up arrow
|
||||
jog(ev.shiftKey ? 'Z' : 'Y', dist);
|
||||
ev.preventDefault();
|
||||
break;
|
||||
case 40: // down arrow
|
||||
jog(ev.shiftKey ? 'Z' : 'Y', -dist);
|
||||
ev.preventDefault();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
$('sender-gc-runbox').onclick = runbox;
|
||||
|
||||
$('sender-gc-send').onclick = programStart;
|
||||
senderGcPause.onclick = programPause;
|
||||
$('sender-gc-abort').onclick = programAbort;
|
||||
|
||||
hostInput.value = SDB['kiri-serial'] || '';
|
||||
|
||||
SPACE.onEnterKey([
|
||||
commandInput, function() {
|
||||
if (selectedPort) {
|
||||
sendNow(commandInput.value);
|
||||
emit("» "+commandInput.value);
|
||||
}
|
||||
commandInput.value = '';
|
||||
}
|
||||
]);
|
||||
|
||||
var handleMessageData = function(dm) {
|
||||
dm = dm.trim();
|
||||
if (dm.charAt(0) === '<') {
|
||||
// grbl status update
|
||||
dm = dm.substring(1,dm.length-2);
|
||||
dm = dm.split(',');
|
||||
// console.log(dm);
|
||||
status = dm[0];
|
||||
senderStatus.value = status;
|
||||
switch (status) {
|
||||
case 'Idle':
|
||||
case 'Run':
|
||||
case 'Home':
|
||||
case 'Check':
|
||||
senderStatus.style.color = '#080';
|
||||
break;
|
||||
case 'Hold':
|
||||
case 'Door':
|
||||
senderStatus.style.color = '#800';
|
||||
break;
|
||||
case 'Alarm':
|
||||
senderStatus.style.color = '#880';
|
||||
break;
|
||||
}
|
||||
rpx.value = dm[1].split(':')[1];
|
||||
rpy.value = dm[2];
|
||||
rpz.value = dm[3];
|
||||
wpx.value = dm[4].split(':')[1];
|
||||
wpy.value = dm[5];
|
||||
wpz.value = dm[6];
|
||||
} else
|
||||
if (dm.charAt(0) === '{') {
|
||||
// tinyg status update
|
||||
dm = js2o(dm);
|
||||
var ro = dm.sr || (dm.r && dm.r.sr ? dm.r.sr : null);
|
||||
if (ro) {
|
||||
if (ro.posx !== undefined) rpx.value = ro.posx;
|
||||
if (ro.posy !== undefined) rpy.value = ro.posy;
|
||||
if (ro.posz !== undefined) rpz.value = ro.posz;
|
||||
}
|
||||
var stat = dm.stat || (dm.r && dm.r.stat ? dm.r.stat : null) || 1;
|
||||
status = ['Unknown','Ready','Alarm','Stop','End','Run','Hold','Probe','Homing'][stat];
|
||||
senderStatus.style.color = ['#000','#080','#880','#000','#000','#000','#800','#000','#000'][stat];
|
||||
senderStatus.value = status;
|
||||
// console.log(dm);
|
||||
} else
|
||||
if (dm && dm !== 'ok') emit("« "+dm);
|
||||
}
|
||||
|
||||
var handleSocketData = function(msg) {
|
||||
var data = msg.data.trim();
|
||||
if (data.charAt(0) === '{') {
|
||||
data = js2o(data);
|
||||
// if (data.Cmd) console.log(data);
|
||||
if (data.Cmd && data.Cmd === 'Queued' && data.Data) queueAck(data.Data);
|
||||
if (data.QCnt >= 0) {
|
||||
spjsQueueCount = data.QCnt;
|
||||
gcRemoteQueue.value = spjsQueueCount;
|
||||
}
|
||||
if (data.SerialPorts) updatePortList(data.SerialPorts);
|
||||
if (selectedPort && data.P === selectedPort.Name) {
|
||||
// console.log(data);
|
||||
if (data.D) handleMessageData(data.D);
|
||||
}
|
||||
} else if (data.length > 0) {
|
||||
console.log("** "+data.trim().replace('\n',' ... '));
|
||||
}
|
||||
}
|
||||
|
||||
var onSocketClose = function() {
|
||||
socket = null;
|
||||
connect.innerHTML = 'connect';
|
||||
senderSelectMode.disabled = true;
|
||||
senderSelectPort.disabled = true;
|
||||
};
|
||||
|
||||
connect.onclick = function() {
|
||||
if (socket) {
|
||||
// connect.innerHTML = 'connect';
|
||||
socket.close();
|
||||
socket = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hostInput.value) {
|
||||
return alert('please enter the host:port of your SPJS server');
|
||||
}
|
||||
|
||||
SDB['kiri-serial'] = hostInput.value;
|
||||
|
||||
try {
|
||||
socket = new WebSocket(pre + hostInput.value + "/ws");
|
||||
} catch (e) {
|
||||
emit("** unable to connect to "+hostInput.value);
|
||||
return;
|
||||
}
|
||||
socket.onopen = function() {
|
||||
emit("** websocket open to "+hostInput.value);
|
||||
socket.send('list');
|
||||
};
|
||||
socket.onerror = function(e) {
|
||||
emit("** socket error with SPJS server @ "+hostInput.value);
|
||||
connect.disabled = false;
|
||||
socket.close();
|
||||
// console.log(e);
|
||||
};
|
||||
socket.onclose = onSocketClose;
|
||||
socket.onmessage = handleSocketData;
|
||||
connect.innerHTML = 'disconnect';
|
||||
connect.disabled = true;
|
||||
};
|
||||
|
||||
if (socket && ports) {
|
||||
updatePortList(ports);
|
||||
emit();
|
||||
}
|
||||
|
||||
// start sender
|
||||
sender();
|
||||
}
|
||||
|
||||
function sendOpenSequence() {
|
||||
var seq = [];
|
||||
switch (selectedMode) {
|
||||
case 'grbl': seq = init_grbl; break;
|
||||
case 'tinyg': seq = init_tinyg; break;
|
||||
}
|
||||
seq.forEach(function(line) {
|
||||
sendNow(line);
|
||||
});
|
||||
}
|
||||
|
||||
function sender() {
|
||||
// prevent multiple senders
|
||||
if (senderTimeout) return;
|
||||
drainQueue();
|
||||
// setup next call to sender()
|
||||
senderTimeout = setTimeout(function() {
|
||||
senderTimeout = null;
|
||||
sender();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function o2js(o) {
|
||||
return JSON.stringify(o);
|
||||
}
|
||||
|
||||
function js2o(s) {
|
||||
try {
|
||||
return JSON.parse(s);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
console.log(s);
|
||||
}
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
console.log("serial | "+msg);
|
||||
}
|
||||
|
||||
function sendGcode() {
|
||||
if (gcode) {
|
||||
// timeout and loop at waiting threshold
|
||||
if (waitingForAck.length || localQueue.length > localQueueMax) {
|
||||
setTimeout(sendGcode, 100);
|
||||
return;
|
||||
}
|
||||
console.log({
|
||||
gcode: (gcode !== null ? gcode.length : 0),
|
||||
idx: gcodeIndex,
|
||||
waitack: waitingForAck.length,
|
||||
abort: gcodeAbort
|
||||
});
|
||||
// queue up 500 more
|
||||
while (gcodeIndex < gcode.length && !gcodeAbort) {
|
||||
sendToQueue(gcode[gcodeIndex++]);
|
||||
// drain queue every 500 lines
|
||||
if (gcodeIndex % 500 === 0) {
|
||||
// wait 100ms before trying to send again
|
||||
setTimeout(sendGcode, 100);
|
||||
return;
|
||||
}
|
||||
}
|
||||
drainQueue();
|
||||
}
|
||||
}
|
||||
|
||||
function setGCode(gc, runbox) {
|
||||
try {
|
||||
gcode = gc.split('\n');
|
||||
bounds = runbox;
|
||||
$('sender-gc-lines').value = gcode ? gcode.length : '';
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
function closePort() {
|
||||
if (!selectedPort) return;
|
||||
emit("** closing port: "+selectedPort.Name);
|
||||
socket.send('close '+selectedPort.Name);
|
||||
senderSelectPort.selectedIndex = 0;
|
||||
senderSelectMode.selectedIndex = 0;
|
||||
senderPortClose.disabled = true;
|
||||
selectedPort = null;
|
||||
selectedMode = null;
|
||||
}
|
||||
|
||||
function updatePortSettings() {
|
||||
// console.log({ups:1, selectedPort:selectedPort, selectedMode:selectedMode});
|
||||
if (selectedPort) {
|
||||
if (selectedPort.IsOpen) {
|
||||
if (selectedMode) {
|
||||
senderPortClose.disabled = false;
|
||||
if (selectedMode != selectedPort.BufferAlgorithm) {
|
||||
alert("to change a port's mode, first close it");
|
||||
selectPort();
|
||||
} else {
|
||||
setTimeout(sendOpenSequence, 500);
|
||||
}
|
||||
} else {
|
||||
closePort();
|
||||
}
|
||||
} else {
|
||||
if (selectedMode) {
|
||||
emit("** opening port: "+selectedPort.Name);
|
||||
socket.send('open '+selectedPort.Name+' 115200 '+selectedMode);
|
||||
senderPortClose.disabled = false;
|
||||
setTimeout(sendOpenSequence, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// UI select trigger
|
||||
function selectPort() {
|
||||
var value = senderSelectPort[senderSelectPort.selectedIndex].value,
|
||||
newport = ports[value],
|
||||
algo;
|
||||
if (value >= 0) {
|
||||
selectedPort = newport;
|
||||
// console.log([selectedPort,value]);
|
||||
switch (newport.BufferAlgorithm) {
|
||||
case 'grbl': senderSelectMode.selectedIndex = 1; break;
|
||||
case 'tinyg': senderSelectMode.selectedIndex = 2; break;
|
||||
default: senderSelectMode.selectedIndex = 0; break;
|
||||
}
|
||||
senderPortClose.disabled = !newport.IsOpen;
|
||||
if (newport.BufferAlgorithm) selectMode();
|
||||
}
|
||||
}
|
||||
|
||||
// UI select trigger
|
||||
function selectMode() {
|
||||
if (senderSelectMode.selectedIndex) {
|
||||
selectedMode = senderSelectMode[senderSelectMode.selectedIndex].value;
|
||||
} else {
|
||||
selectedMode = '';
|
||||
}
|
||||
// console.log([senderSelectMode,selectedMode]);
|
||||
updatePortSettings();
|
||||
}
|
||||
|
||||
function updatePortList(portList) {
|
||||
emit("** received port list");
|
||||
|
||||
var portsHTML = '<option>select port</option>',
|
||||
portModes = [];
|
||||
|
||||
ports = portList;
|
||||
ports.forEach(function(port, index) {
|
||||
portsHTML += '<option value='+index+'>' + port.Friendly + '</option>';
|
||||
});
|
||||
|
||||
connect.disabled = false;
|
||||
senderSelectMode.selecteIndex = 0;
|
||||
senderSelectPort.selecteIndex = 0;
|
||||
senderSelectMode.disabled = false;
|
||||
senderSelectPort.disabled = false;
|
||||
senderSelectPort.innerHTML = portsHTML;
|
||||
}
|
||||
|
||||
// send immediately bypassing queue
|
||||
function sendNow(cmd) {
|
||||
if (!(socket && selectedPort)) return;
|
||||
socket.send('send '+selectedPort.Name+' '+cmd);
|
||||
}
|
||||
|
||||
// buffer to mem queue which drains to spjs
|
||||
function sendToQueue(cmd) {
|
||||
localQueue.push(cmd);
|
||||
}
|
||||
|
||||
// spjs ack (wrote) queue entry
|
||||
function queueAck(data) {
|
||||
data.forEach(function(el) {
|
||||
waitingForAck.remove(el.Id);
|
||||
gcLinesSent.value = gcodeIndex - waitingForAck.length;
|
||||
});
|
||||
}
|
||||
|
||||
// drain from mem queue to spjs
|
||||
function drainQueue() {
|
||||
// return if no live socket
|
||||
if (!(socket && selectedPort)) return;
|
||||
|
||||
// return if paused or empty outbound buffer
|
||||
if (gcPaused || localQueue.length === 0) return;
|
||||
|
||||
// return if waiting or queued to queue too large
|
||||
if (waitingForAck.length > 0 || spjsQueueCount > spjsQueueCountMax) {
|
||||
return;
|
||||
}
|
||||
|
||||
// blast out next batch of queued to queue
|
||||
var toolChange = false,
|
||||
count = 0,
|
||||
data = [],
|
||||
line,
|
||||
next;
|
||||
|
||||
while (count++ < sendBatchMax && localQueue.length > 0) {
|
||||
next = (nextID++).toString();
|
||||
waitingForAck.push(next);
|
||||
line = localQueue.shift();
|
||||
if (line.indexOf("M6") === 0) {
|
||||
toolChange = true;
|
||||
// grbl doesn't support M6 so
|
||||
// just stop sending until unpaused
|
||||
if (selectedMode === 'grbl') break;
|
||||
}
|
||||
data.push({D:line, Id:next});
|
||||
// for tinyg pause after M6 sent
|
||||
if (toolChange) break;
|
||||
}
|
||||
|
||||
socket.send('sendjson '+o2js({
|
||||
P: selectedPort.Name,
|
||||
Data: data
|
||||
}));
|
||||
|
||||
if (toolChange) {
|
||||
emit("** tool change");
|
||||
setPause(true);
|
||||
var alertToolChange = SDB['sender-alert-toolchange'];
|
||||
if (!alertToolChange && !confirm("tool change. unpause to continue.\nshow this dialog in the future?")) {
|
||||
SDB['sender-alert-toolchange'] = 'no';
|
||||
}
|
||||
// alert("tool change. click unpause to continue");
|
||||
// setPause(false);
|
||||
}
|
||||
}
|
||||
|
||||
function setPause(paused) {
|
||||
if (paused === gcPaused) return;
|
||||
gcPaused = paused;
|
||||
senderGcPause.innerHTML = paused ? 'unpause' : 'pause';
|
||||
senderGcPause.style.color = paused ? '#800' : '#000';
|
||||
if (paused) emit("** program paused. unpause to continue");
|
||||
}
|
||||
|
||||
function emit(msg) {
|
||||
if (!logDiv) return console.log({unable_to_emit: msg});
|
||||
if (msg) {
|
||||
msg = msg.replace('<','<').replace('>','>');
|
||||
logBuffer.push(msg);
|
||||
if (logBuffer.length > 100) logBuffer = logBuffer.slice(1);
|
||||
}
|
||||
logDiv.innerHTML = logBuffer.join('<br>');
|
||||
logDiv.scrollTop = logDiv.scrollHeight;
|
||||
}
|
||||
|
||||
function jog(axis, delta) {
|
||||
delta = (delta || 1) * (parseFloat(jogInput.value) || 1);
|
||||
sendNow("G91 G0 " + axis + delta.toString());
|
||||
sendNow("G90");
|
||||
}
|
||||
|
||||
function softReset() {
|
||||
sendNow("\x18");
|
||||
// for tinyg, we need to flush the buffer and resume
|
||||
// so that the commands will be processed
|
||||
if (selectedMode === 'tinyg') sendNow('%~');
|
||||
// for grbl, also reset an alarms
|
||||
if (selectedMode === 'grbl') sendNow('$X');
|
||||
}
|
||||
|
||||
function feedHold() {
|
||||
sendNow("!");
|
||||
}
|
||||
|
||||
function feedResume() {
|
||||
sendNow("~");
|
||||
// send unlock if alarm is set
|
||||
if (selectedMode === 'grbl' && status === 'Alarm') sendNow('$X');
|
||||
}
|
||||
|
||||
function runbox() {
|
||||
if (!bounds) return;
|
||||
sendNow(["G0X",bounds.min.x,"Y",bounds.min.y].join(''));
|
||||
sendNow(["G0X",bounds.max.x,"Y",bounds.min.y].join(''));
|
||||
sendNow(["G0X",bounds.max.x,"Y",bounds.max.y].join(''));
|
||||
sendNow(["G0X",bounds.min.x,"Y",bounds.max.y].join(''));
|
||||
sendNow(["G0X0Y0"]);
|
||||
}
|
||||
|
||||
function hide() {
|
||||
senderDialog.style.display = 'none';
|
||||
}
|
||||
|
||||
function show() {
|
||||
senderDialog.style.display = 'block';
|
||||
senderDialog.style.right = (kiri.api.ui.ctrlRight.offsetWidth + 5) + 'px';
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
if (senderDialog.style.display === 'block') hide(); else show();
|
||||
}
|
||||
|
||||
function programStart() {
|
||||
gcodeIndex = 0;
|
||||
gcodeAbort = false;
|
||||
gcLinesSent.value = 0;
|
||||
setPause(false);
|
||||
feedResume();
|
||||
sendGcode();
|
||||
}
|
||||
|
||||
function programPause() {
|
||||
setPause(!gcPaused);
|
||||
gcodeAbort = false;
|
||||
}
|
||||
|
||||
function programAbort() {
|
||||
gcodeIndex = 0;
|
||||
gcodeAbort = true;
|
||||
gcLinesSent.value = 0;
|
||||
localQueue = [];
|
||||
setPause(false);
|
||||
feedHold();
|
||||
}
|
||||
})();
|
||||
1202
js/kiri-slice.js
Normal file
1202
js/kiri-slice.js
Normal file
File diff suppressed because it is too large
Load diff
781
js/kiri-slicer.js
Normal file
781
js/kiri-slicer.js
Normal file
|
|
@ -0,0 +1,781 @@
|
|||
/** Copyright 2014-2017 Stewart Allen -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
var gs_kiri_slicer = exports;
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.kiri) self.kiri = {};
|
||||
if (self.kiri.slicer) return;
|
||||
|
||||
var slicer = self.kiri.slicer = {
|
||||
slice: slice,
|
||||
sliceWidget: sliceWidget
|
||||
};
|
||||
|
||||
var KIRI = self.kiri,
|
||||
BASE = self.base,
|
||||
CONF = BASE.config,
|
||||
UTIL = BASE.util,
|
||||
POLY = BASE.polygons,
|
||||
time = UTIL.time,
|
||||
newSlice = KIRI.newSlice,
|
||||
newOrderedLine = BASE.newOrderedLine;
|
||||
|
||||
/**
|
||||
* Convenience method. Gets a Widget's points and calls slice()
|
||||
*
|
||||
* @param {Widget} widget
|
||||
* @param {Object} options
|
||||
* @param {Function} ondone callback when slicing complete
|
||||
* @param {Function} onupdate callback on incremental updates
|
||||
*/
|
||||
function sliceWidget(widget, options, ondone, onupdate) {
|
||||
slice(widget.getPoints(), widget.getBoundingBox(), options, ondone, onupdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of points as triples, a bounding box and a set of
|
||||
* slicing controls, emit an array of Slice objects to the ondone()
|
||||
* function. onupdate() will be called with two parameters (% completion
|
||||
* and an optional message) so that the UI can report progress to the user.
|
||||
*
|
||||
* @param {Array} points vertex array
|
||||
* @param {Bounds} bounds bounding box for points
|
||||
* @param {Object} options slicing parameters
|
||||
* @param {Function} ondone callback when slicing done
|
||||
* @param {Function} onupdate callback to report slicing progress
|
||||
*/
|
||||
function slice(points, bounds, options, ondone, onupdate) {
|
||||
var topoMode = options.topo,
|
||||
ox = 0,
|
||||
oy = 0;
|
||||
|
||||
// handle rotating meshes for CAM finishing.
|
||||
// slicer expects things just so, so we alter
|
||||
// geometry to satisfy
|
||||
if (options.swapX || options.swapY) {
|
||||
points = points.slice();
|
||||
|
||||
var btmp = new THREE.Box3(),
|
||||
pref = {},
|
||||
cached;
|
||||
|
||||
btmp.setFromPoints(points);
|
||||
if (options.swapX) ox = -btmp.max.x;
|
||||
if (options.swapY) oy = -btmp.max.y;
|
||||
|
||||
// array re-uses points so we need
|
||||
// to be careful not to alter a point
|
||||
// more than once
|
||||
for (var p, index=0; index<points.length; index++) {
|
||||
p = points[index];
|
||||
cached = pref[p.key];
|
||||
// skip points already altered
|
||||
if (cached) {
|
||||
points[index] = cached;
|
||||
continue;
|
||||
}
|
||||
cached = p.clone();
|
||||
if (options.swapX) cached.swapXZ();
|
||||
else if (options.swapY) cached.swapYZ();
|
||||
cached.rekey();
|
||||
pref[p.key] = cached;
|
||||
points[index] = cached;
|
||||
}
|
||||
|
||||
// update temp bounds from new points
|
||||
btmp.setFromPoints(points);
|
||||
for (var p, index=0; index<points.length; index++) {
|
||||
p = points[index];
|
||||
if (p.mod === 1) continue;
|
||||
p.mod = 1;
|
||||
p.z -= btmp.min.z;
|
||||
}
|
||||
|
||||
// update temp bounds from points with altered Z
|
||||
btmp.setFromPoints(points);
|
||||
bounds = btmp;
|
||||
}
|
||||
|
||||
var zMin = options.zmin || Math.floor(bounds.min.z),
|
||||
zMax = options.zmax || Math.ceil(bounds.max.z),
|
||||
zInc = options.height,
|
||||
zOff = true ? zInc / 2 : 0,
|
||||
zIndexes = [],
|
||||
zList = {},
|
||||
zFlat = {},
|
||||
zScale,
|
||||
zPos,
|
||||
timeStart = time(),
|
||||
slices = [],
|
||||
zSum = 0.0,
|
||||
buckets = [],
|
||||
i, j = 0, k, p1, p2, p3, px,
|
||||
CPRO = KIRI.driver.CAM.process;
|
||||
|
||||
// gather z-index stats
|
||||
// these are used for auto-slicing in laser
|
||||
// and to flats detection in CAM mode
|
||||
for (i = 0; i < points.length;) {
|
||||
p1 = points[i++];
|
||||
p2 = points[i++];
|
||||
p3 = points[i++];
|
||||
zSum += (Math.abs(p1.z - p2.z) + Math.abs(p2.z - p3.z) + Math.abs(p3.z - p1.z));
|
||||
// laser auto-detect z slice points
|
||||
if (zInc === 0) {
|
||||
zList[UTIL.round(p1.z,5)] = 1;
|
||||
zList[UTIL.round(p2.z,5)] = 1;
|
||||
zList[UTIL.round(p3.z,5)] = 1;
|
||||
}
|
||||
// cam auto-detect flats
|
||||
if (options.cam) {
|
||||
if (p1.z === p2.z && p2.z === p3.z && p1.z > bounds.min.z) {
|
||||
var zkey = p1.z < bounds.max.z ? p1.z + 0.001 : p1.z,
|
||||
area = Math.abs(UTIL.area2(p1,p2,p3))/2;
|
||||
if (!zFlat[zkey]) {
|
||||
zFlat[zkey] = area;
|
||||
} else {
|
||||
zFlat[zkey] += area;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** bucket polygons into z-bounded groups */
|
||||
var bucketCount = Math.max(1, Math.ceil(zMax / (zSum / points.length)) - 1);
|
||||
|
||||
zScale = 1 / (zMax / bucketCount);
|
||||
|
||||
if (bucketCount > 1) {
|
||||
// create empty buckets
|
||||
for (i = 0; i < bucketCount + 1; i++) buckets.push([]);
|
||||
|
||||
// copy triples into all matching z-buckets
|
||||
for (i = 0; i < points.length;) {
|
||||
p1 = points[i++];
|
||||
p2 = points[i++];
|
||||
p3 = points[i++];
|
||||
var zm = Math.min(p1.z, p2.z, p3.z),
|
||||
zM = Math.max(p1.z, p2.z, p3.z),
|
||||
bm = Math.floor(zm * zScale),
|
||||
bM = Math.ceil(zM * zScale);
|
||||
for (j = bm; j < bM; j++) {
|
||||
buckets[j].push(p1);
|
||||
buckets[j].push(p2);
|
||||
buckets[j].push(p3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create zIndexes array (0 = auto-detect)
|
||||
if (zInc === 0) {
|
||||
// find unique z-index offsets for slicing
|
||||
var key, zl = [];
|
||||
for (key in zList) {
|
||||
if (!zList.hasOwnProperty(key)) continue;
|
||||
zl.push(parseFloat(key));
|
||||
}
|
||||
zl.sort(function(a,b) { return a - b});
|
||||
for (i = 0; i < zl.length-1; i++) {
|
||||
zIndexes.push((zl[i] + zl[i+1]) / 2);
|
||||
}
|
||||
zIndexes.sort(function(a,b) { return a - b});
|
||||
} else if (options.cam) {
|
||||
// re-divide slice height so that top and
|
||||
// bottom slices fall exactly on those faces
|
||||
zInc = (zMax - zMin) / (Math.floor(zMax / zInc) + 1);
|
||||
for (i = zMin; i < zMax; i += zInc) {
|
||||
zIndexes.push(i);
|
||||
}
|
||||
for (key in zFlat) {
|
||||
// todo make threshold for flat detection configurable
|
||||
if (!zFlat.hasOwnProperty(key) || zFlat[key] < 100) continue;
|
||||
key = parseFloat(key);
|
||||
if (!zIndexes.contains(key) && key >= zMin) zIndexes.push(key);
|
||||
}
|
||||
// sort top down
|
||||
zIndexes.sort(function(a,b) {
|
||||
return b-a;
|
||||
});
|
||||
} else {
|
||||
// FDM is the simplest case. Just offset and slice at
|
||||
// predicable offsets. First offset is half a slice height.
|
||||
// this was once configurable, but that turned out not to be
|
||||
// terribly useful. In future, first layer height may be
|
||||
// adjusted separately, but that would also require other
|
||||
// settings changes (flow rate, etc).
|
||||
// ... only this one special case for first layer :)
|
||||
if (options.firstHeight) {
|
||||
zIndexes.push(options.firstHeight / 2);
|
||||
zMin = options.firstHeight;
|
||||
}
|
||||
for (i = zMin + zOff; i < zMax; i += zInc) {
|
||||
zIndexes.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
// create a Slice for each z offset in the zIndexes array
|
||||
for (var i = 0; i < zIndexes.length; i++) {
|
||||
zPos = zIndexes[i];
|
||||
sliceZ(zPos);
|
||||
// kill slicing if onupdate() returns 42
|
||||
if (onupdate(i / zIndexes.length) === 42) {
|
||||
return ondone(null);
|
||||
}
|
||||
}
|
||||
|
||||
// for cam, mark top and bottom as mandatory (hasFlats)
|
||||
if (options.cam && slices.length > 0) {
|
||||
slices[0].hasFlats = true;
|
||||
slices[slices.length-1].hasFlats = true;
|
||||
}
|
||||
|
||||
// connect slices into linked list for island/bridge projections
|
||||
for (i=1; i<slices.length; i++) {
|
||||
slices[i-1].up = slices[i];
|
||||
slices[i].down = slices[i-1];
|
||||
}
|
||||
|
||||
slices.slice_time = time() - timeStart;
|
||||
|
||||
// pass Slices array back to ondone function
|
||||
ondone(slices);
|
||||
|
||||
/** ***** SLICING FUNCTIONS ***** */
|
||||
|
||||
/**
|
||||
* given a point, append to the correct
|
||||
* 'where' objec tarray (on, over or under)
|
||||
*
|
||||
* @param {Point} p
|
||||
* @param {number} z offset
|
||||
* @param {Obejct} where
|
||||
*/
|
||||
function checkUnderOverOn(p, z, where) {
|
||||
var delta = p.z - z;
|
||||
if (Math.abs(delta) < CONF.precision_slice_z) { // on
|
||||
where.on.push(p);
|
||||
} else if (delta < 0) { // under
|
||||
where.under.push(p);
|
||||
} else { // over
|
||||
where.over.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a point over and under a z offset, calculate
|
||||
* and return the intersection point on that z plane
|
||||
*
|
||||
* @param {Point} over
|
||||
* @param {Point} under
|
||||
* @param {number} z offset
|
||||
* @returns {Point} intersection point
|
||||
*/
|
||||
function intersectPoints(over, under, z) {
|
||||
var ip = [];
|
||||
for (var i = 0; i < over.length; i++) {
|
||||
for (var j = 0; j < under.length; j++) {
|
||||
ip.push(over[i].intersectZ(under[j], z));
|
||||
}
|
||||
}
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure points are unique with a cache/key algorithm
|
||||
*/
|
||||
function getCachedPoint(phash, p) {
|
||||
var cached = phash[p.key];
|
||||
if (!cached) {
|
||||
phash[p.key] = p;
|
||||
return p;
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
/**
|
||||
* Given two points and hints about their edges,
|
||||
* return a new Line object with points sorted
|
||||
* lexicographically by key. This allows for future
|
||||
* line de-duplication and joins.
|
||||
*
|
||||
* @param {Object} phash
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
* @param {boolean} [coplanar]
|
||||
* @param {boolean} [edge]
|
||||
* @returns {Line}
|
||||
*/
|
||||
function makeZLine(phash, p1, p2, coplanar, edge) {
|
||||
p1 = getCachedPoint(phash, p1);
|
||||
p2 = getCachedPoint(phash, p2);
|
||||
var line = newOrderedLine(p1,p2);
|
||||
line.coplanar = coplanar || false;
|
||||
line.edge = edge || false;
|
||||
return line;
|
||||
}
|
||||
|
||||
/**
|
||||
* process a single z-slice on a single mesh and
|
||||
* add to slices array
|
||||
*
|
||||
* @param {number} z
|
||||
*/
|
||||
function sliceZ(z) {
|
||||
var phash = {},
|
||||
lines = [],
|
||||
slice = newSlice(z, options.view ? options.view.newGroup() : null),
|
||||
bucket = bucketCount == 1 ? points : buckets[Math.floor(z * zScale)];
|
||||
|
||||
if (!bucket) return;
|
||||
|
||||
// iterate over matching buckets for this z offset
|
||||
for (var i = 0; i < bucket.length;) {
|
||||
p1 = bucket[i++];
|
||||
p2 = bucket[i++];
|
||||
p3 = bucket[i++];
|
||||
var where = {under: [], over: [], on: []};
|
||||
checkUnderOverOn(p1, z, where);
|
||||
checkUnderOverOn(p2, z, where);
|
||||
checkUnderOverOn(p3, z, where);
|
||||
if (where.under.length === 3 || where.over.length === 3) {
|
||||
// does not intersect
|
||||
} else if (where.on.length === 2) {
|
||||
// one side of triangle is on the Z plane
|
||||
lines.push(makeZLine(phash, where.on[0], where.on[1], false, true));
|
||||
} else if (where.on.length === 3) {
|
||||
// triangle is coplanar with Z
|
||||
//lines.push(makeZLine(phash, where.on[0], where.on[1], true));
|
||||
//lines.push(makeZLine(phash, where.on[1], where.on[2], true));
|
||||
//lines.push(makeZLine(phash, where.on[2], where.on[0], true));
|
||||
} else if (where.under.length === 0 || where.over.length === 0) {
|
||||
// does not intersect (but one point is on the plane)
|
||||
} else {
|
||||
// compute two point intersections and construct line
|
||||
var line = intersectPoints(where.over, where.under, z);
|
||||
if (line.length < 2 && where.on.length === 1) {
|
||||
line.push(where.on[0]);
|
||||
}
|
||||
if (line.length === 2) {
|
||||
lines.push(makeZLine(phash, line[0], line[1]));
|
||||
} else {
|
||||
console.log({msg: "invalid ips", line: line, where: where});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allow empty slices in CAM swap mode (for topos w/ gaps)
|
||||
if (lines.length == 0 && !(options.swapX || options.swapY)) return;
|
||||
|
||||
slice.index = slices.length;
|
||||
slice.lines = removeDuplicateLines(lines);
|
||||
|
||||
// annotate slices with cam flats for finishing waterlines
|
||||
if (options.cam) slice.hasFlats = zFlat[z] > 0;
|
||||
|
||||
// for topo slices, we just need the raw lines
|
||||
if (!topoMode) {
|
||||
slice.groups = connectLines(slice.lines, slices.length);
|
||||
POLY.nest(slice.groups).forEach(function(top) { slice.addTop(top) });
|
||||
}
|
||||
|
||||
// fixup un-rotates polygons for CAM
|
||||
if (options.swapX || options.swapY) {
|
||||
var move = {x:ox, y:oy, z:0};
|
||||
slice.camMode = options.swapX ? CPRO.FINISH_X : CPRO.FINISH_Y;
|
||||
if (topoMode) {
|
||||
var lines = slice.lines, llen = lines.length, idx, line;
|
||||
// shared points causing problems
|
||||
for (idx=0; idx<llen; idx++) { line = lines[idx];
|
||||
line.p1 = line.p1.clone();
|
||||
line.p2 = line.p2.clone();
|
||||
}
|
||||
for (idx=0; idx<llen; idx++) { line = lines[idx];
|
||||
if (options.swapX) {
|
||||
line.p1.swapXZ();
|
||||
line.p2.swapXZ();
|
||||
} else {
|
||||
line.p1.swapYZ();
|
||||
line.p2.swapYZ();
|
||||
}
|
||||
line.p1.move(move);
|
||||
line.p2.move(move);
|
||||
}
|
||||
} else {
|
||||
slice.tops.forEach(function(top) {
|
||||
top.poly.swap(options.swapX, options.swapY);
|
||||
top.poly.move(move);
|
||||
top.poly.inner = null;
|
||||
});
|
||||
drape(slice, options.swapX, options.swapY);
|
||||
}
|
||||
}
|
||||
|
||||
slices.push(slice);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of input lines (line soup), find the path through
|
||||
* joining line ends that encompasses the greatest area without self
|
||||
* interesection. Eliminate used points and repeat. Unjoined lines
|
||||
* are permitted and handled after all other cases are handled.
|
||||
*
|
||||
* @param {Line[]} input
|
||||
* @param {number} [index]
|
||||
* @returns {Array}
|
||||
*/
|
||||
function connectLines(input, index) {
|
||||
// map points to all other points they're connected to
|
||||
var DBUG = BASE.debug,
|
||||
CONF = BASE.config,
|
||||
pmap = {},
|
||||
points = [],
|
||||
output = [],
|
||||
connect = [],
|
||||
search = 1,
|
||||
nextMod = 1,
|
||||
debug = (BASE.debug.get('z-index') === index) && BASE.debug.get('connect'),
|
||||
bridge = CONF.bridgeLineGapDistance,
|
||||
p1, p2;
|
||||
|
||||
function cachedPoint(p) {
|
||||
var cp = pmap[p.key];
|
||||
if (cp) return cp;
|
||||
points.push(p);
|
||||
pmap[p.key] = p;
|
||||
p.mod = nextMod++; // unique seq ID for points
|
||||
p.toString = function() { return this.mod }; // point array concat
|
||||
return p;
|
||||
}
|
||||
|
||||
function addConnected(p1, p2) {
|
||||
if (!p1.group) p1.group = [ p2 ];
|
||||
else p1.group.push(p2);
|
||||
}
|
||||
|
||||
function sliceAtTerm(path, term) {
|
||||
var idx, len = path.length;
|
||||
for (idx = 0; idx < len-1; idx++) {
|
||||
if (path[idx] === term) {
|
||||
return path.slice(idx);
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* using minimal recursion, follow points through connected lines
|
||||
* to form candidate output paths.
|
||||
*/
|
||||
function findPathsMinRecurse(point, path, paths, from) {
|
||||
var stack = [ ];
|
||||
if (paths.length > 10000) {
|
||||
DBUG.log("excessive path options @ "+paths.length+" #"+input.length);
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
stack.push(point);
|
||||
|
||||
var last = point,
|
||||
links = point.group;
|
||||
|
||||
path.push(point);
|
||||
// use del to mark traversed path
|
||||
point.del = true;
|
||||
// set so point isn't used in another polygon search
|
||||
point.pos = search++;
|
||||
// seed path with two points to prevent redundant opposing seeks
|
||||
if (path.length === 1) {
|
||||
from = point;
|
||||
point = links[0];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (links.length > 2) {
|
||||
// TODO optimize when > 2 and limit to left-most and right-most branches
|
||||
// for now, pursue all possible branches
|
||||
links.forEach(function(nextp) {
|
||||
// do not backtrack
|
||||
if (nextp === from) {
|
||||
return;
|
||||
}
|
||||
if (nextp.del) {
|
||||
paths.push(sliceAtTerm(path,nextp));
|
||||
} else {
|
||||
findPathsMinRecurse(nextp, path.slice(), paths, point);
|
||||
}
|
||||
});
|
||||
break;
|
||||
} else {
|
||||
point = links[0] === from ? links[1] : links[0];
|
||||
from = last;
|
||||
// hit an open end
|
||||
if (!point) {
|
||||
path.open = true;
|
||||
paths.push(path);
|
||||
break;
|
||||
}
|
||||
// hit a point previously in the path (or start)
|
||||
if (point.del) {
|
||||
paths.push(sliceAtTerm(path,point));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var i=0; i<stack.length; i++) stack[i].del = false;
|
||||
// stack.forEach(function(p) { p.del = false });
|
||||
}
|
||||
|
||||
// emit a polygon if it can be cleaned and still have 2 or more points
|
||||
function emit(poly) {
|
||||
poly = poly.clean();
|
||||
if (poly.length > 2) output.push(poly.clean());
|
||||
// if (poly.length > 2) output.push(poly);
|
||||
}
|
||||
|
||||
// given an array of paths, emit longest to shortest
|
||||
// eliminating points from the paths as they are emitted
|
||||
// shorter paths any point eliminated are eliminated as candidates.
|
||||
function emitLongestAsPolygon(paths) {
|
||||
var longest = null,
|
||||
emitted = 0,
|
||||
closed = 0,
|
||||
open = 0;
|
||||
|
||||
paths.forEach(function(path, index) {
|
||||
// use longest perimeter vs longest path?
|
||||
if (!longest || path.length > longest.length) longest = path;
|
||||
if (!path.open) closed++; else open++;
|
||||
});
|
||||
|
||||
if (debug) DBUG.log({closed:closed, open:open});
|
||||
// it gets more complicated with multiple possible output paths
|
||||
if (closed > 1 && open === 0) {
|
||||
// add polygon to path (for area sorting)
|
||||
paths.forEach(function(path) { path.poly = BASE.newPolygon().addPoints(path) });
|
||||
|
||||
// sort descending by area VS (length below -- better in most cases)
|
||||
// paths.sort(function(a,b) { return b.poly.area() - a.poly.area() });
|
||||
|
||||
// sort descending by length
|
||||
paths.sort(function(a,b) { return b.poly.length - a.poly.length });
|
||||
|
||||
if (debug) DBUG.log({paths:paths});
|
||||
|
||||
// emit polygons largest to smallest
|
||||
// omit polygon if it intersects previously emitted (has del points)
|
||||
paths.forEach(function(path) {
|
||||
if (path.length < 3) return;
|
||||
var len = path.length, i;
|
||||
for (i = 0; i < len; i++) if (path[i].del) return;
|
||||
for (i = 0; i < len; i++) path[i].del = true;
|
||||
emit(path.poly);
|
||||
emitted++;
|
||||
});
|
||||
|
||||
if (debug) DBUG.log({longest:longest.length, paths:paths.length, closed:closed, emitted:emitted});
|
||||
} else {
|
||||
if (longest.open) {
|
||||
connect.push(longest);
|
||||
} else {
|
||||
emit(BASE.newPolygon().addPoints(longest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create point map, unique point list and point group arrays
|
||||
input.forEach(function(line) {
|
||||
p1 = cachedPoint(line.p1.round(7));
|
||||
p2 = cachedPoint(line.p2.round(7));
|
||||
addConnected(p1,p2);
|
||||
addConnected(p2,p1);
|
||||
});
|
||||
|
||||
// first trace paths starting at dangling endpoinds (bad polygon soup)
|
||||
points.forEach(function(point) {
|
||||
// must not have been used and be a dangling end
|
||||
if (point.pos === 0 && point.group.length === 1) {
|
||||
var path = [],
|
||||
paths = [];
|
||||
findPathsMinRecurse(point, path, paths);
|
||||
if (paths.length > 0) emitLongestAsPolygon(paths);
|
||||
}
|
||||
});
|
||||
|
||||
// for each point, find longest path back to self
|
||||
points.forEach(function(point) {
|
||||
// must not have been used or be at a split
|
||||
if (point.pos === 0 && point.group.length === 2) {
|
||||
var path = [],
|
||||
paths = [];
|
||||
findPathsMinRecurse(point, path, paths);
|
||||
if (paths.length > 0) emitLongestAsPolygon(paths);
|
||||
}
|
||||
});
|
||||
|
||||
// return true if points are deemed "close enough" close a polygon
|
||||
function close(p1,p2) {
|
||||
return p1.distToSq2D(p2) <= 0.01;
|
||||
}
|
||||
|
||||
// reconnect dangling/open polygons to closest endpoint
|
||||
for (var i=0; i<connect.length; i++) {
|
||||
|
||||
var array = connect[i],
|
||||
last = array[array.length-1],
|
||||
tmp, dist, j;
|
||||
|
||||
if (!bridge) {
|
||||
emit(BASE.newPolygon().addPoints(array).setOpen());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (array.delete) continue;
|
||||
|
||||
loop: for (var merged=0;;) {
|
||||
var closest = { dist:Infinity };
|
||||
for (j=i+1; j<connect.length; j++) {
|
||||
tmp = connect[j];
|
||||
if (tmp.delete) continue;
|
||||
dist = last.distToSq2D(tmp[0]);
|
||||
if (dist < closest.dist && dist <= bridge) {
|
||||
closest = {
|
||||
dist: dist,
|
||||
array: tmp
|
||||
}
|
||||
}
|
||||
dist = last.distToSq2D(tmp[tmp.length-1]);
|
||||
if (dist < closest.dist && dist <= bridge) {
|
||||
closest = {
|
||||
dist: dist,
|
||||
array: tmp,
|
||||
reverse: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tmp = closest.array) {
|
||||
if (closest.reverse) tmp.reverse();
|
||||
tmp.delete = true;
|
||||
array.appendAll(tmp);
|
||||
last = array[array.length-1];
|
||||
merged++;
|
||||
// tail meets head (closed)
|
||||
if (close(array[0], last)) {
|
||||
emit(BASE.newPolygon().addPoints(array));
|
||||
break loop;
|
||||
}
|
||||
} else {
|
||||
// no more closest polys (open set)
|
||||
emit(BASE.newPolygon().addPoints(array));
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* eliminate duplicate lines and interior-only lines (coplanar)
|
||||
*
|
||||
* lines are sorted using lexicographic point keys such that
|
||||
* they are comparable even if their points are reversed. hinting
|
||||
* for deletion, co-planar and suspect shared edge is detectable at
|
||||
* this time.
|
||||
*
|
||||
* @param {Line[]} lines
|
||||
* @returns {Line[]}
|
||||
*/
|
||||
function removeDuplicateLines(lines) {
|
||||
var output = [],
|
||||
tmplines = [],
|
||||
points = [],
|
||||
pmap = {};
|
||||
|
||||
function cachePoint(p) {
|
||||
var cp = pmap[p.key];
|
||||
if (cp) return cp;
|
||||
points.push(p);
|
||||
pmap[p.key] = p;
|
||||
return p;
|
||||
}
|
||||
|
||||
function addLinesToPoint(point, line) {
|
||||
cachePoint(point);
|
||||
if (!point.group) point.group = [ line ];
|
||||
else point.group.push(line);
|
||||
}
|
||||
|
||||
// mark duplicates for deletion preserving edges
|
||||
lines.sort(function (l1, l2) {
|
||||
if (l1.key === l2.key) {
|
||||
l1.del = !l1.edge;
|
||||
l2.del = !l2.edge;
|
||||
return 0;
|
||||
}
|
||||
return l1.key < l2.key ? -1 : 1;
|
||||
});
|
||||
|
||||
// associate points with their lines, cull deleted
|
||||
lines.forEach(function(line) {
|
||||
if (!line.del) {
|
||||
tmplines.push(line);
|
||||
addLinesToPoint(line.p1, line);
|
||||
addLinesToPoint(line.p2, line);
|
||||
}
|
||||
});
|
||||
|
||||
// merge collinear lines
|
||||
points.forEach(function(point) {
|
||||
if (point.group.length != 2) return;
|
||||
var l1 = point.group[0],
|
||||
l2 = point.group[1];
|
||||
if (l1.isCollinear(l2)) {
|
||||
l1.del = true;
|
||||
l2.del = true;
|
||||
// find new endpoints that are not shared point
|
||||
var p1 = l1.p1 != point ? l1.p1 : l1.p2,
|
||||
p2 = l2.p1 != point ? l2.p1 : l2.p2,
|
||||
newline = base.newOrderedLine(p1,p2);
|
||||
// remove deleted lines from associated points
|
||||
p1.group.remove(l1);
|
||||
p1.group.remove(l2);
|
||||
p2.group.remove(l1);
|
||||
p2.group.remove(l2);
|
||||
// associate new line with points
|
||||
p1.group.push(newline);
|
||||
p2.group.push(newline);
|
||||
// add new line to lines array
|
||||
newline.edge = l1.edge || l2.edge;
|
||||
tmplines.push(newline);
|
||||
}
|
||||
});
|
||||
|
||||
// mark duplicates for deletion
|
||||
// but preserve one if it's an edge
|
||||
tmplines.sort(function (l1, l2) {
|
||||
if (l1.key === l2.key) {
|
||||
l1.del = true;
|
||||
l2.del = !l2.edge;
|
||||
return 0;
|
||||
}
|
||||
return l1.key < l2.key ? -1 : 1;
|
||||
});
|
||||
|
||||
// create new line array culling deleted
|
||||
tmplines.forEach(function(line) {
|
||||
if (!line.del) {
|
||||
output.push(line);
|
||||
line.p1.group = null;
|
||||
line.p2.group = null;
|
||||
}
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
})();
|
||||
647
js/kiri-widget.js
Normal file
647
js/kiri-widget.js
Normal file
|
|
@ -0,0 +1,647 @@
|
|||
"use strict";
|
||||
|
||||
var gs_kiri_widget = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.kiri) self.kiri = {};
|
||||
if (self.kiri.Widget) return;
|
||||
|
||||
var KIRI = self.kiri,
|
||||
DRIVERS = KIRI.driver,
|
||||
CAM = DRIVERS.CAM,
|
||||
FDM = DRIVERS.FDM,
|
||||
LASER = DRIVERS.LASER,
|
||||
CPRO = CAM.process,
|
||||
BASE = self.base,
|
||||
CONF = BASE.config,
|
||||
DBUG = BASE.debug,
|
||||
UTIL = BASE.util,
|
||||
POLY = BASE.polygons,
|
||||
MATH = Math,
|
||||
ABS = MATH.abs,
|
||||
MIN = MATH.min,
|
||||
MAX = MATH.max,
|
||||
SQRT = MATH.sqrt,
|
||||
CEIL = MATH.ceil,
|
||||
FLOOR = MATH.floor,
|
||||
ROUND = MATH.round,
|
||||
SLICER = KIRI.slicer,
|
||||
newLine = BASE.newLine,
|
||||
newPoint = BASE.newPoint,
|
||||
newSlice = KIRI.newSlice,
|
||||
newPolygon = BASE.newPolygon,
|
||||
newOrderedLine = BASE.newOrderedLine,
|
||||
time = UTIL.time,
|
||||
WP = Widget.prototype,
|
||||
solid_opacity = 1.0,
|
||||
nextId = 0;
|
||||
|
||||
KIRI.Widget = Widget;
|
||||
KIRI.newWidget = newWidget;
|
||||
|
||||
function newWidget(id) { return new Widget(id) }
|
||||
|
||||
/** ******************************************************************
|
||||
* Constructor
|
||||
******************************************************************* */
|
||||
|
||||
/**
|
||||
* @params {String} [id]
|
||||
* @constructor
|
||||
*/
|
||||
function Widget(id) {
|
||||
this.id = id || new Date().getTime().toString(36)+(nextId++);
|
||||
this.mesh = null;
|
||||
this.points = null;
|
||||
// todo resolve use of this vs. mesh.bounds
|
||||
this.bounds = null;
|
||||
this.wire = null;
|
||||
this.topo = null;
|
||||
this.slices = null;
|
||||
this.settings = null;
|
||||
this.modified = true;
|
||||
this.orient = {
|
||||
scale: {
|
||||
x: 1.0,
|
||||
y: 1.0,
|
||||
z: 1.0
|
||||
},
|
||||
rot: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
pos: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
mirror: false
|
||||
},
|
||||
this.stats = {
|
||||
slice_time: 0,
|
||||
load_time: 0,
|
||||
progress: 0
|
||||
};
|
||||
this.saved = false;
|
||||
// cancel slice/print ops
|
||||
this.cancel = false;
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Widget Class Functions
|
||||
******************************************************************* */
|
||||
|
||||
Widget.loadFromCatalog = function(filename, ondone) {
|
||||
KIRI.catalog.getFile(filename, function(data) {
|
||||
ondone(newWidget().loadVertices(data));
|
||||
});
|
||||
};
|
||||
|
||||
Widget.loadFromState = function(id, ondone, move) {
|
||||
var widget = newWidget();
|
||||
widget.id = id;
|
||||
widget.saved = time();
|
||||
KIRI.odb.get('ws-save-'+id, function(data) {
|
||||
if (data) {
|
||||
var vertices = data.geo || data,
|
||||
orient = data.orient || null;
|
||||
ondone(widget.loadVertices(vertices));
|
||||
// restore widget position if specified
|
||||
if (move && orient && orient.pos) {
|
||||
widget.orient = orient;
|
||||
widget.move(orient.pos.x, orient.pos.y, orient.pos.z, true);
|
||||
}
|
||||
} else {
|
||||
ondone(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Widget.deleteFromState = function(id,ondone) {
|
||||
KIRI.odb.remove('ws-save-'+id, ondone);
|
||||
};
|
||||
|
||||
/**
|
||||
* converts a geometry point array into a kiri point array
|
||||
* with auto-decimation
|
||||
*
|
||||
* @param {Float32Array} array
|
||||
* @param {boolean} [decimate]
|
||||
* @returns {Array}
|
||||
*/
|
||||
Widget.verticesToPoints = function(array,decimate) {
|
||||
var parr = new Array(array.length / 3),
|
||||
i = 0,
|
||||
j = 0,
|
||||
t = time(),
|
||||
hash = {},
|
||||
unique = 0,
|
||||
passes = 0,
|
||||
points,
|
||||
oldpoints = parr.length,
|
||||
newpoints;
|
||||
// replace point objects with their equivalents
|
||||
while (i < array.length) {
|
||||
var p = newPoint(array[i++], array[i++], array[i++]),
|
||||
k = p.key,
|
||||
m = hash[k];
|
||||
if (!m) {
|
||||
m = p;
|
||||
hash[k] = p;
|
||||
unique++;
|
||||
}
|
||||
parr[j++] = m;
|
||||
}
|
||||
// decimate until all point spacing > precision_decimate
|
||||
while (parr.length > BASE.config.decimate_threshold && decimate && BASE.config.precision_decimate > 0.0) {
|
||||
var lines = [], line, dec = 0;
|
||||
for (i=0; i<oldpoints; ) {
|
||||
var p1 = parr[i++],
|
||||
p2 = parr[i++],
|
||||
p3 = parr[i++];
|
||||
lines.push( {p1:p1, p2:p2, d:SQRT(p1.distToSq3D(p2))} );
|
||||
lines.push( {p1:p1, p2:p3, d:SQRT(p1.distToSq3D(p3))} );
|
||||
lines.push( {p1:p2, p2:p3, d:SQRT(p2.distToSq3D(p3))} );
|
||||
}
|
||||
// sort by ascending line length
|
||||
lines.sort(function(a,b) {
|
||||
return a.d - b.d
|
||||
});
|
||||
// create offset mid-points
|
||||
for (i=0; i<lines.length; i++) {
|
||||
line = lines[i];
|
||||
if (line.d >= BASE.config.precision_decimate) break;
|
||||
if (line.p1.op || line.p2.op) continue;
|
||||
// todo skip dropping lines where either point is a "sharp" on 3 vectors
|
||||
line.p1.op = line.p2.op = line.p1.midPointTo3D(line.p2);
|
||||
dec++;
|
||||
}
|
||||
// exit if nothing to decimate
|
||||
if (dec === 0) break;
|
||||
passes++;
|
||||
// create new facets
|
||||
points = new Array(oldpoints);
|
||||
newpoints = 0;
|
||||
for (i=0; i<oldpoints; ) {
|
||||
var p1 = parr[i++],
|
||||
p2 = parr[i++],
|
||||
p3 = parr[i++];
|
||||
// drop facets with two offset points
|
||||
if (p1.op && p1.op === p2.op) continue;
|
||||
if (p1.op && p1.op === p3.op) continue;
|
||||
if (p2.op && p2.op === p3.op) continue;
|
||||
// otherwise emit altered facet
|
||||
points[newpoints++] = p1.op || p1;
|
||||
points[newpoints++] = p2.op || p2;
|
||||
points[newpoints++] = p3.op || p3;
|
||||
}
|
||||
parr = points.slice(0,newpoints);
|
||||
oldpoints = newpoints;
|
||||
}
|
||||
if (passes) DBUG.log({
|
||||
before: array.length / 3,
|
||||
after: parr.length,
|
||||
unique: unique,
|
||||
decimations: passes,
|
||||
time: (time() - t)
|
||||
});
|
||||
return parr;
|
||||
};
|
||||
|
||||
Widget.pointsToVertices = function(points) {
|
||||
var vertices = new Float32Array(points.length * 3),
|
||||
i = 0, vi = 0;
|
||||
while (i < points.length) {
|
||||
vertices[vi++] = points[i].x;
|
||||
vertices[vi++] = points[i].y;
|
||||
vertices[vi++] = points[i++].z;
|
||||
}
|
||||
return vertices;
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Widget Prototype Functions
|
||||
******************************************************************* */
|
||||
|
||||
WP.saveToCatalog = function(filename) {
|
||||
var widget = this;
|
||||
var time = UTIL.time();
|
||||
KIRI.catalog.putFile(filename, this.getGeoVertices(), function(vertices) {
|
||||
if (vertices && vertices.length) {
|
||||
console.log("saving decimated mesh ["+vertices.length+"] time ["+(UTIL.time()-time)+"]");
|
||||
widget.loadVertices(vertices);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
WP.saveState = function(ondone) {
|
||||
var widget = this;
|
||||
KIRI.odb.put('ws-save-'+this.id, {geo:widget.getGeoVertices(), orient:widget.orient}, function(result) {
|
||||
widget.saved = time();
|
||||
if (ondone) ondone();
|
||||
});
|
||||
};
|
||||
|
||||
WP.encodeSlices = function() {
|
||||
var encoded = [];
|
||||
if (this.slices) this.slices.forEach(function(slice) {
|
||||
encoded.push(slice.encode());
|
||||
});
|
||||
return encoded;
|
||||
};
|
||||
|
||||
WP.decodeSlices = function(encoded) {
|
||||
this.slices = KIRI.codec.decode(encoded, { mesh:this.mesh });
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Float32Array} vertices
|
||||
* @returns {Widget}
|
||||
*/
|
||||
WP.loadVertices = function(vertices) {
|
||||
if (this.mesh) {
|
||||
this.mesh.geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
||||
this.mesh.geometry.computeFaceNormals();
|
||||
this.mesh.geometry.computeVertexNormals();
|
||||
this.points = null;
|
||||
return this;
|
||||
} else {
|
||||
var geometry = new THREE.BufferGeometry();
|
||||
geometry.addAttribute('position', new THREE.BufferAttribute(vertices, 3));
|
||||
return this.loadGeometry(geometry);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {THREE.Geometry} geometry
|
||||
* @returns {Widget}
|
||||
*/
|
||||
WP.loadGeometry = function(geometry) {
|
||||
var mesh = new THREE.Mesh(
|
||||
geometry,
|
||||
new THREE.MeshPhongMaterial({
|
||||
color: 0xffff00,
|
||||
specular: 0x181818,
|
||||
shininess: 100,
|
||||
transparent: true,
|
||||
opacity: solid_opacity
|
||||
})
|
||||
);
|
||||
|
||||
// fix invalid normals
|
||||
geometry.computeFaceNormals();
|
||||
geometry.computeVertexNormals();
|
||||
// to fix mirroring of normals not working as expected
|
||||
mesh.material.side = THREE.DoubleSide;
|
||||
mesh.castShadow = true;
|
||||
mesh.receiveShadow = true;
|
||||
mesh.widget = this;
|
||||
this.mesh = mesh;
|
||||
// invalidates points cache (like any scale/rotation)
|
||||
this.center();
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Point[]} points
|
||||
* @returns {Widget}
|
||||
*/
|
||||
WP.setPoints = function(points) {
|
||||
this.points = points || null;
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* remove slice data and their views
|
||||
*/
|
||||
WP.clearSlices = function() {
|
||||
var slices = this.slices,
|
||||
mesh = this.mesh;
|
||||
if (slices) {
|
||||
slices.forEach(function(slice) {
|
||||
mesh.remove(slice.view);
|
||||
});
|
||||
this.slices = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} color
|
||||
*/
|
||||
WP.setColor = function(color) {
|
||||
var material = this.mesh.material;
|
||||
material.color.set(color);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
WP.setOpacity = function(value) {
|
||||
var mesh = this.mesh;
|
||||
if (value <= 0.0) {
|
||||
mesh.material.transparent = solid_opacity < 1.0;
|
||||
mesh.material.opacity = solid_opacity;
|
||||
mesh.material.visible = false;
|
||||
} else if (UTIL.inRange(value, 0.0, solid_opacity)) {
|
||||
mesh.material.transparent = value < 1.0;
|
||||
mesh.material.opacity = value;
|
||||
mesh.material.visible = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* center geometry bottom (on platform) at 0,0,0
|
||||
*/
|
||||
WP.center = function() {
|
||||
var i = 0,
|
||||
mesh = this.mesh,
|
||||
geo = mesh.geometry,
|
||||
bb = mesh.getBoundingBox(true),
|
||||
bm = bb.min.clone(),
|
||||
bM = bb.max.clone(),
|
||||
bd = bM.sub(bm).multiplyScalar(0.5),
|
||||
gap = geo.attributes.position,
|
||||
pa = gap.array;
|
||||
// center point array on 0,0,0
|
||||
for ( ; i < pa.length; i += 3) {
|
||||
pa[i ] -= bm.x + bd.x;
|
||||
pa[i + 1] -= bm.y + bd.y;
|
||||
pa[i + 2] -= bm.z;
|
||||
}
|
||||
gap.needsUpdate = true;
|
||||
bb = mesh.getBoundingBox(true);
|
||||
// for use with the packer
|
||||
mesh.w = (bb.max.x - bb.min.x);
|
||||
mesh.h = (bb.max.y - bb.min.y);
|
||||
mesh.d = (bb.max.z - bb.min.z);
|
||||
// invalidate cached points
|
||||
this.points = null;
|
||||
this.modified = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* moves top of widget to given Z
|
||||
* used in CAM mode
|
||||
*
|
||||
* @param {number} z position
|
||||
*/
|
||||
WP.setTopZ = function(z) {
|
||||
var mesh = this.mesh,
|
||||
pos = this.orient.pos;
|
||||
if (z) {
|
||||
pos.z = mesh.getBoundingBox().max.z - z;
|
||||
mesh.position.z = -pos.z - 0.01;
|
||||
} else {
|
||||
pos.z = 0;
|
||||
mesh.position.z = 0;
|
||||
}
|
||||
this.modified = true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} z
|
||||
* @param {boolean} abs
|
||||
*/
|
||||
WP.move = function(x, y, z, abs) {
|
||||
var mesh = this.mesh,
|
||||
pos = this.orient.pos;
|
||||
// do not allow moves in pure slice view
|
||||
if (!mesh.material.visible) return;
|
||||
if (abs) {
|
||||
mesh.position.set(x,y,z);
|
||||
pos.x = (x || 0);
|
||||
pos.y = (y || 0);
|
||||
pos.z = (z || 0);
|
||||
} else {
|
||||
mesh.position.x += ( x || 0);
|
||||
mesh.position.y += ( y || 0);
|
||||
mesh.position.z += (-z || 0);
|
||||
pos.x += (x || 0);
|
||||
pos.y += (y || 0);
|
||||
pos.z += (z || 0);
|
||||
}
|
||||
if (x || y || z) this.modified = true;
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} z
|
||||
*/
|
||||
WP.scale = function(x, y, z) {
|
||||
var mesh = this.mesh,
|
||||
scale = this.orient.scale;
|
||||
this.setWireframe(false);
|
||||
this.clearSlices();
|
||||
mesh.geometry.applyMatrix(new THREE.Matrix4().makeScale(x, y, z));
|
||||
this.center();
|
||||
scale.x *= (x || 1.0);
|
||||
scale.y *= (y || 1.0);
|
||||
scale.z *= (z || 1.0);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} z
|
||||
*/
|
||||
WP.rotate = function(x, y, z) {
|
||||
this.setWireframe(false);
|
||||
this.clearSlices();
|
||||
this.mesh.geometry.applyMatrix(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(x || 0, y || 0, z || 0)));
|
||||
this.center();
|
||||
var rot = this.orient.rot;
|
||||
rot.x += (x || 0);
|
||||
rot.y += (y || 0);
|
||||
rot.z += (z || 0);
|
||||
};
|
||||
|
||||
WP.mirror = function() {
|
||||
this.setWireframe(false);
|
||||
this.clearSlices();
|
||||
var i,
|
||||
o = this.orient,
|
||||
geo = this.mesh.geometry,
|
||||
at = geo.attributes,
|
||||
pa = at.position.array,
|
||||
nm = at.normal.array;
|
||||
for (i = 0 ; i < pa.length; i += 3) {
|
||||
pa[i] = -pa[i];
|
||||
nm[i] = -nm[i];
|
||||
}
|
||||
geo.computeFaceNormals();
|
||||
geo.computeVertexNormals();
|
||||
this.center();
|
||||
o.mirror = !o.mirror;
|
||||
};
|
||||
|
||||
WP.getGeoVertices = function() {
|
||||
return this.mesh.geometry.getAttribute('position').array;
|
||||
};
|
||||
|
||||
WP.getPoints = function() {
|
||||
if (!this.points) {
|
||||
// convert and cache points from geometry vertices
|
||||
this.points = Widget.verticesToPoints(this.getGeoVertices());
|
||||
}
|
||||
return this.points;
|
||||
};
|
||||
|
||||
WP.getBoundingBox = function(refresh) {
|
||||
// if (this.mesh) return this.mesh.getBoundingBox();
|
||||
if (!this.bounds || refresh) {
|
||||
this.bounds = new THREE.Box3();
|
||||
this.bounds.setFromPoints(this.getPoints());
|
||||
}
|
||||
return this.bounds;
|
||||
};
|
||||
|
||||
WP.isModified = function() {
|
||||
return this.modified;
|
||||
};
|
||||
|
||||
/**
|
||||
* processes points into facets, then into slices
|
||||
*
|
||||
* once upon a time there were multiple slicers. this was the fastest in most cases.
|
||||
* lines are added to all the buckets they cross. then buckets are processed in order.
|
||||
* buckets are contiguous ranges of z slicers. the advantage of this method is that
|
||||
* as long as a large percentage of lines do not cross large z distances, this reduces
|
||||
* the number of lines each slice has to consider thus improving speed.
|
||||
*
|
||||
* @params {Object} settings
|
||||
* @params {Function} [ondone]
|
||||
* @params {Function} [onupdate]
|
||||
* @params {boolean} [remote]
|
||||
*/
|
||||
WP.slice = function(settings, ondone, onupdate, remote) {
|
||||
var widget = this,
|
||||
startTime = UTIL.time();
|
||||
|
||||
widget.settings = settings;
|
||||
widget.cancel = false;
|
||||
|
||||
onupdate(0.0001, "slicing");
|
||||
|
||||
if (remote) {
|
||||
|
||||
KIRI.work.slice(settings, this, function (reply) {
|
||||
if (reply.update) {
|
||||
onupdate(reply.update, reply.updateStatus);
|
||||
}
|
||||
if (reply.send_start) widget.xfer = {start: reply.send_start};
|
||||
if (reply.topo) widget.topo = reply.topo;
|
||||
if (reply.stats) widget.stats = reply.stats;
|
||||
if (reply.send_end) widget.stats.load_time = widget.xfer.start - reply.send_end;
|
||||
if (reply.slices) { widget.clearSlices(); widget.slices = [] };
|
||||
if (reply.slice) widget.slices.push(KIRI.codec.decode(reply.slice, {mesh:widget.mesh}));
|
||||
if (reply.done) {
|
||||
ondone(true);
|
||||
widget.modified = false;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
widget.clearSlices();
|
||||
|
||||
var catchdone = function() {
|
||||
onupdate(1.0, "transferring");
|
||||
widget.stats.slice_time = UTIL.time() - startTime;
|
||||
widget.modified = false;
|
||||
|
||||
ondone(true);
|
||||
};
|
||||
|
||||
var catchupdate = function(progress, message) {
|
||||
onupdate(progress, message);
|
||||
return widget.cancel ? 42 : 0;
|
||||
};
|
||||
|
||||
var driver = null;
|
||||
|
||||
switch (settings.mode) {
|
||||
case 'LASER': driver = LASER; break;
|
||||
case 'FDM': driver = FDM; break;
|
||||
case 'CAM': driver = CAM; break;
|
||||
}
|
||||
|
||||
if (driver) {
|
||||
driver.slice(settings, widget, catchupdate, catchdone);
|
||||
} else {
|
||||
DBUG.log('invalid mode: '+settings.mode);
|
||||
ondone(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WP.getCamBounds = function(settings) {
|
||||
var bounds = this.getBoundingBox().clone();
|
||||
bounds.max.z += settings.process.camZTopOffset;
|
||||
return bounds;
|
||||
};
|
||||
|
||||
/**
|
||||
* render all slice and processed data
|
||||
* @param {number} renderMode
|
||||
* @param {boolean} cam mode
|
||||
*/
|
||||
WP.render = function(renderMode, cam) {
|
||||
var slices = this.slices;
|
||||
if (!slices) return;
|
||||
// render outline
|
||||
slices.forEach(function(s) { s.renderOutline(renderMode) });
|
||||
// render shells
|
||||
slices.forEach(function(s) { s.renderShells(renderMode) });
|
||||
// render diff
|
||||
if (!cam) slices.forEach(function(s) { s.renderDiff() });
|
||||
// render solid fill (include solid flats/bridges)
|
||||
slices.forEach(function(s) { s.renderSolidFill() });
|
||||
// render solid fill outlines
|
||||
if (!cam) slices.forEach(function(s) { s.renderSolidOutlines() });
|
||||
// render sparse fill
|
||||
if (!cam) slices.forEach(function(s) { s.renderSparseFill() });
|
||||
// render supports
|
||||
if (!cam) slices.forEach(function(s) { s.renderSupport() });
|
||||
};
|
||||
|
||||
WP.hideSlices = function() {
|
||||
var showing = false;
|
||||
if (this.slices) this.slices.forEach(function(slice) {
|
||||
showing = showing || slice.view.visible;
|
||||
slice.view.visible = false;
|
||||
});
|
||||
return showing;
|
||||
};
|
||||
|
||||
WP.toggleWireframe = function (color, opacity) {
|
||||
this.setWireframe(!this.wire, color, opacity);
|
||||
};
|
||||
|
||||
WP.setWireframe = function(set, color, opacity) {
|
||||
var mesh = this.mesh,
|
||||
widget = this;
|
||||
if (this.wire) {
|
||||
mesh.remove(this.wire);
|
||||
this.wire = null;
|
||||
this.setOpacity(solid_opacity);
|
||||
this.hideSlices();
|
||||
}
|
||||
if (set) {
|
||||
widget.wire = base.render.wireframe(mesh, this.getPoints(), color);
|
||||
widget.setOpacity(opacity);
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
317
js/kiri-work.js
Normal file
317
js/kiri-work.js
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
if (self.window) {
|
||||
|
||||
if (!self.kiri) self.kiri = {};
|
||||
|
||||
var loc = self.location,
|
||||
host = loc.hostname,
|
||||
port = loc.port,
|
||||
proto = loc.protocol,
|
||||
pre = host.indexOf(".space") > 0 || host === "localhost" ?
|
||||
proto + "//" + host + ":" + port :
|
||||
"",
|
||||
time = function() { return new Date().getTime() },
|
||||
KIRI = self.kiri,
|
||||
BASE = self.base,
|
||||
seqid = 1,
|
||||
running = {},
|
||||
worker = new Worker(pre + "/code/worker.js/" + exports.VERSION);
|
||||
|
||||
// new moto.Ajax(function(body) {
|
||||
// console.log({body:body});
|
||||
// var blob = new Blob([body], {type : 'application/json'});
|
||||
// worker = new Worker(URL.createObjectURL(blob));
|
||||
// }).request(pre + "/code/worker.js/");
|
||||
|
||||
worker.onmessage = function(e) {
|
||||
var now = time(),
|
||||
reply = e.data,
|
||||
record = running[reply.seq],
|
||||
onreply = record.fn;
|
||||
|
||||
if (reply.done) delete running[reply.seq];
|
||||
|
||||
// calculate and replace recv time
|
||||
reply.time_recv = now - reply.time_recv;
|
||||
|
||||
onreply(reply.data, reply);
|
||||
};
|
||||
|
||||
function send(fn, data, onreply, async, zerocopy) {
|
||||
var seq = seqid++;
|
||||
|
||||
running[seq] = {fn:onreply, async:async||false};
|
||||
|
||||
worker.postMessage({
|
||||
seq: seq,
|
||||
task: fn,
|
||||
time: time(),
|
||||
data: data
|
||||
}, zerocopy);
|
||||
}
|
||||
|
||||
KIRI.work = {
|
||||
decimate : function(vertices, callback) {
|
||||
var vertices = vertices.buffer.slice(0);
|
||||
send("decimate", vertices, function(output) {
|
||||
callback(output);
|
||||
});
|
||||
},
|
||||
|
||||
clear : function(widget) {
|
||||
send("clear", widget ? {id:widget.id} : {}, function(reply) {
|
||||
// console.log({clear:reply});
|
||||
});
|
||||
},
|
||||
|
||||
cancel : function(widget) {
|
||||
send("cancel", widget ? {id:widget.id} : {});
|
||||
},
|
||||
|
||||
slice : function(settings, widget, callback) {
|
||||
var vertices = widget.getGeoVertices().buffer.slice(0);
|
||||
send("slice", {
|
||||
id: widget.id,
|
||||
settings: settings,
|
||||
vertices: vertices,
|
||||
position: widget.mesh.position
|
||||
}, function(reply) {
|
||||
callback(reply);
|
||||
}, null, [vertices]);
|
||||
},
|
||||
|
||||
printSetup : function(settings, callback) {
|
||||
send("printSetup", {settings:settings}, function(reply) {
|
||||
callback(reply);
|
||||
});
|
||||
},
|
||||
|
||||
printGCode : function(callback) {
|
||||
var gcode = [],
|
||||
start = BASE.util.time();
|
||||
send("printGCode", {}, function(reply) {
|
||||
if (reply.line) {
|
||||
gcode.push(reply.line);
|
||||
} else {
|
||||
if (!reply.gcode) reply.gcode = gcode.join("\n");
|
||||
// console.log({printGCode:(BASE.util.time() - start)});
|
||||
callback(reply);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
sliceToGCode : function(settings, vertices, callback) {
|
||||
var wid = new Date().toString(36);
|
||||
var vertices = widget.getGeoVertices().buffer.slice(0);
|
||||
send("slice", {settings:settings, id:wiwd, vertices:vertices, position:{x:0,y:0,z:0}}, function(reply) {
|
||||
send("printSetup", {settings:settings}, function(reply) {
|
||||
send("printGCode", {}, function(reply) {
|
||||
callback(reply);
|
||||
});
|
||||
}, null, [vertices]);
|
||||
callback(reply);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
} else {
|
||||
module = { exports: {} };
|
||||
|
||||
var loc = self.location,
|
||||
host = loc.hostname,
|
||||
ver = exports.VERSION,
|
||||
time = function() { return new Date().getTime() };
|
||||
|
||||
console.log("kiri | init work | " + ver);
|
||||
|
||||
// when running the server in 'web' mode, the obfuscated code is served as a
|
||||
// unified ball via /code/work.js -- otherwise, map "localhost" to "debug"
|
||||
// to debug unobfuscated code. if not, /js/ paths will 404.
|
||||
if (host !== 'grid.space' && host !== 'debug') {
|
||||
[
|
||||
"license","ext-n3d","ext-clip","add-array",
|
||||
"add-three","geo","geo-point","geo-debug","geo-bounds",
|
||||
"geo-line","geo-slope","geo-polygon","geo-polygons",
|
||||
"kiri-slice","kiri-slicer","kiri-driver-fdm","kiri-driver-cam",
|
||||
"kiri-driver-laser","kiri-widget","kiri-pack","kiri-print","kiri-codec"
|
||||
].forEach(function(scr) {
|
||||
importScripts(["/js/",scr,".js","/v"+ver].join(''));
|
||||
})
|
||||
} else {
|
||||
importScripts("/code/work.js/"+ver);
|
||||
base.debug.disable();
|
||||
}
|
||||
|
||||
var base = self.base,
|
||||
moto = self.moto,
|
||||
dbug = base.debug,
|
||||
util = base.util,
|
||||
kiri = self.kiri,
|
||||
Widget = kiri.Widget,
|
||||
currentPrint,
|
||||
cache = {};
|
||||
|
||||
var dispatch = {
|
||||
decimate: function(vertices, send) {
|
||||
vertices = new Float32Array(vertices),
|
||||
vertices = Widget.pointsToVertices(Widget.verticesToPoints(vertices, true));
|
||||
send.done(vertices);
|
||||
},
|
||||
|
||||
cancel: function(data) {
|
||||
if (data.id) {
|
||||
var widget = cache[data.id];
|
||||
if (widget) widget.cancel = true;
|
||||
} else {
|
||||
for (var id in cache) {
|
||||
if (cache.hasOwnProperty(id)) cache[id].cancel = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
slice: function(data, send) {
|
||||
var settings = data.settings,
|
||||
vertices = new Float32Array(data.vertices),
|
||||
position = data.position,
|
||||
points = Widget.verticesToPoints(vertices);
|
||||
|
||||
send.data({update:0.05, updateStatus:"slicing"});
|
||||
|
||||
var widget = kiri.newWidget(data.id).setPoints(points),
|
||||
last = util.time(),
|
||||
now;
|
||||
|
||||
// clear previous widget data before reslice
|
||||
delete cache[data.id];
|
||||
|
||||
// fake mesh object to satisfy printing
|
||||
widget.mesh = {
|
||||
widget: widget,
|
||||
position: position
|
||||
};
|
||||
|
||||
widget.slice(settings, function() {
|
||||
send.data({send_start:time()});
|
||||
send.data({
|
||||
topo: settings.synth.sendTopo ? widget.topo : null,
|
||||
stats: widget.stats,
|
||||
slices: widget.slices.length
|
||||
});
|
||||
widget.slices.forEach(function(slice,index) {
|
||||
send.data({index:index, slice:slice.encode()});
|
||||
})
|
||||
send.data({send_end:time()});
|
||||
send.done({done:true});
|
||||
// cache results for future printing
|
||||
cache[data.id] = widget;
|
||||
}, function(update, msg) {
|
||||
now = util.time();
|
||||
if (now - last < 10 && update < 0.99) return;
|
||||
// on update
|
||||
send.data({update:(0.05 + update * 0.95), updateStatus:msg});
|
||||
last = now;
|
||||
});
|
||||
},
|
||||
|
||||
printSetup: function(data, send) {
|
||||
var widgets = [], key;
|
||||
for (key in cache) {
|
||||
if (cache.hasOwnProperty(key)) widgets.push(cache[key]);
|
||||
}
|
||||
|
||||
send.data({update:0.05, updateStatus:"preview"});
|
||||
|
||||
currentPrint = kiri.newPrint(data.settings, widgets, data.id);
|
||||
currentPrint.setup(false, function(update, msg) {
|
||||
send.data({
|
||||
update: update,
|
||||
updateStatus: msg
|
||||
});
|
||||
}, function() {
|
||||
send.done({
|
||||
done: true,
|
||||
output: currentPrint.encodeOutput()
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
printGCode: function(data, send) {
|
||||
currentPrint.exportGCode(false, function(gcode) {
|
||||
send.done({
|
||||
gcode: gcode,
|
||||
lines: currentPrint.lines,
|
||||
bytes: currentPrint.bytes,
|
||||
bounds: currentPrint.bounds,
|
||||
distance: currentPrint.distance,
|
||||
time: currentPrint.time
|
||||
});
|
||||
}, function(line) {
|
||||
send.data({line:line});
|
||||
});
|
||||
},
|
||||
|
||||
clear: function(data, send) {
|
||||
if (!data.id) {
|
||||
cache = {};
|
||||
currentPrint = null;
|
||||
send.done({ clear: true });
|
||||
return;
|
||||
}
|
||||
var had = cache[data.id] !== undefined;
|
||||
delete cache[data.id];
|
||||
send.done({
|
||||
id: data.id,
|
||||
had: had,
|
||||
has: cache[data.id] !== undefined
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
self.onmessage = function(e) {
|
||||
var time_recv = util.time(),
|
||||
msg = e.data,
|
||||
run = dispatch[msg.task],
|
||||
send = {
|
||||
data : function(data,direct) {
|
||||
self.postMessage({
|
||||
seq: msg.seq,
|
||||
task: msg.task,
|
||||
done: false,
|
||||
data: data
|
||||
},direct);
|
||||
},
|
||||
done : function(data,direct) {
|
||||
self.postMessage({
|
||||
seq: msg.seq,
|
||||
task: msg.task,
|
||||
done: true,
|
||||
data: data
|
||||
},direct);
|
||||
}
|
||||
};
|
||||
|
||||
if (run) {
|
||||
var time_xfer = (time_recv - msg.time),
|
||||
output = run(msg.data, send),
|
||||
time_send = util.time(),
|
||||
time_proc = time_send - time_recv;
|
||||
|
||||
if (output) self.postMessage({
|
||||
seq: msg.seq,
|
||||
task: msg.task,
|
||||
time_send: time_xfer,
|
||||
time_proc: time_proc,
|
||||
// replaced on reply side
|
||||
time_recv: util.time(),
|
||||
data: output
|
||||
});
|
||||
} else {
|
||||
console.log({kiri_msg:e});
|
||||
}
|
||||
};
|
||||
|
||||
// catch clipper alerts and convert to console messages
|
||||
self.alert = function(o) {
|
||||
console.log(o);
|
||||
};
|
||||
|
||||
}
|
||||
3291
js/kiri.js
Normal file
3291
js/kiri.js
Normal file
File diff suppressed because it is too large
Load diff
7
js/license.js
Normal file
7
js/license.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
var exports = {
|
||||
COPYRIGHT:"Copyright (C) 2014-2017 Stewart Allen <stewart@neuron.com> - All Rights Reserved",
|
||||
LICENSE:"Unauthorized copying, modification and re-distribution are strictly prohibited without prior written consent.",
|
||||
VERSION:"1.0.59"
|
||||
};
|
||||
if (!module) var module = {};
|
||||
module.exports = exports;
|
||||
2136
js/meta.js
Normal file
2136
js/meta.js
Normal file
File diff suppressed because it is too large
Load diff
77
js/moto-ajax.js
Normal file
77
js/moto-ajax.js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
"use strict";
|
||||
|
||||
var gs_moto_ajax = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
if (!self.moto) self.moto = {};
|
||||
if (self.moto.Ajax) return;
|
||||
|
||||
self.moto.Ajax = Ajax;
|
||||
self.moto.callAjax = function(url, handler) {
|
||||
new Ajax(handler).request(url);
|
||||
};
|
||||
|
||||
var STATES = [
|
||||
"request not initialized", // 0
|
||||
"server connection established", // 1
|
||||
"request recieved", // 2
|
||||
"processing request", // 3
|
||||
"request complete" // 4
|
||||
];
|
||||
|
||||
function Ajax(callback, responseType) {
|
||||
this.ajax = new XMLHttpRequest();
|
||||
this.ajax.onreadystatechange = this.onStateChange.bind(this);
|
||||
this.ajax.withCredentials = true;
|
||||
this.state = STATES[0];
|
||||
this.callback = callback;
|
||||
this.responseType = responseType;
|
||||
}
|
||||
|
||||
function rnd() {
|
||||
return Math.round(Math.random()*0xffffffff).toString(36);
|
||||
}
|
||||
|
||||
var AP = Ajax.prototype,
|
||||
KV = moto.KV,
|
||||
KEY = "moto-ajax",
|
||||
TIME = function() { return new Date().getTime() },
|
||||
MOKEY = KV.getItem(KEY) || (TIME().toString(36)+rnd()+rnd());
|
||||
|
||||
KV.setItem(KEY, MOKEY);
|
||||
|
||||
AP.onStateChange = function() {
|
||||
this.state = STATES[this.ajax.readyState];
|
||||
if (this.ajax.readyState === 4 && this.callback) {
|
||||
var status = this.ajax.status;
|
||||
if (status >= 200 && status < 300) {
|
||||
this.callback(this.ajax.responseType ? this.ajax.response : this.ajax.responseText, this.ajax);
|
||||
} else {
|
||||
this.callback(null, this.ajax);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} url
|
||||
* @param {Object} [send]
|
||||
* @param {Object} [headers]
|
||||
*/
|
||||
AP.request = function(url, send, headers) {
|
||||
this.ajax.open(send ? "POST" : "GET", url, true);
|
||||
if (this.responseType) this.ajax.responseType = this.responseType;
|
||||
headers = headers || {};
|
||||
headers["X-Moto-Ajax"] = MOKEY;
|
||||
for (var k in headers) {
|
||||
this.ajax.setRequestHeader(k, headers[k]);
|
||||
}
|
||||
if (send) {
|
||||
this.ajax.send(send);
|
||||
} else {
|
||||
this.ajax.send();
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
566
js/moto-ctrl.js
Normal file
566
js/moto-ctrl.js
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
"use strict";
|
||||
|
||||
var gs_moto_ctrl = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
/**
|
||||
* Adapted from OrbitControls
|
||||
*/
|
||||
|
||||
THREE.CubeControls = function (object, domElement, notify, slider) {
|
||||
|
||||
this.object = object;
|
||||
this.domElement = ( domElement !== undefined ) ? domElement : document;
|
||||
|
||||
// Set to false to disable this control
|
||||
this.enabled = true;
|
||||
|
||||
// "target" sets the location of focus, where the control orbits around
|
||||
// and where it pans with respect to.
|
||||
this.target = new THREE.Vector3();
|
||||
|
||||
// center is old, deprecated; use "target" instead
|
||||
this.center = this.target;
|
||||
|
||||
// This option actually enables dollying in and out; left as "zoom" for
|
||||
// backwards compatibility
|
||||
this.noZoom = false;
|
||||
this.zoomSpeed = 1.0;
|
||||
this.reverseZoom = false;
|
||||
|
||||
// Limits to how far you can dolly in and out
|
||||
this.minDistance = 0;
|
||||
this.maxDistance = Infinity;
|
||||
|
||||
// Set to true to disable this control
|
||||
this.noRotate = false;
|
||||
this.rotateSpeed = 1.0;
|
||||
|
||||
// Set to true to disable this control
|
||||
this.noPan = false;
|
||||
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
|
||||
|
||||
// How far you can orbit vertically, upper and lower limits.
|
||||
// Range is 0 to Math.PI radians.
|
||||
this.minPolarAngle = 0; // radians
|
||||
this.maxPolarAngle = Math.PI; // radians
|
||||
|
||||
// How far you can orbit horizontally, upper and lower limits.
|
||||
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
|
||||
this.minAzimuthAngle = -Infinity; // radians
|
||||
this.maxAzimuthAngle = Infinity; // radians
|
||||
|
||||
// Set to true to disable use of the keys
|
||||
this.noKeys = false;
|
||||
|
||||
// The four arrow keys
|
||||
this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
|
||||
|
||||
// Mouse buttons
|
||||
this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
|
||||
|
||||
var scope = this,
|
||||
domEl = scope.domElement,
|
||||
EPS = 0.000001,
|
||||
rotateStart = new THREE.Vector2(),
|
||||
rotateEnd = new THREE.Vector2(),
|
||||
rotateDelta = new THREE.Vector2(),
|
||||
panStart = new THREE.Vector2(),
|
||||
panEnd = new THREE.Vector2(),
|
||||
panDelta = new THREE.Vector2(),
|
||||
panOffset = new THREE.Vector3(),
|
||||
offset = new THREE.Vector3(),
|
||||
dollyStart = new THREE.Vector2(),
|
||||
dollyEnd = new THREE.Vector2(),
|
||||
dollyDelta = new THREE.Vector2(),
|
||||
theta,
|
||||
thetaDelta = 0,
|
||||
thetaSet = null,
|
||||
phi,
|
||||
phiDelta = 0,
|
||||
phiSet = null,
|
||||
scale = 1,
|
||||
scaleSave = 1,
|
||||
pan = new THREE.Vector3(),
|
||||
lastPosition = new THREE.Vector3(),
|
||||
lastQuaternion = new THREE.Quaternion(),
|
||||
// so camera.up is the orbit axis
|
||||
quat = new THREE.Quaternion().setFromUnitVectors(object.up, new THREE.Vector3(0, 1, 0)),
|
||||
quatInverse = quat.clone().inverse(),
|
||||
// events
|
||||
changeEvent = { type: 'change'},
|
||||
startEvent = { type: 'start'},
|
||||
endEvent = { type: 'end'};
|
||||
|
||||
var STATE = { NONE: -1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5},
|
||||
state = STATE.NONE,
|
||||
MODE = { PERSPECTIVE: 1, ORTHOGRAPHIC: 2, UNKNOWN: 3},
|
||||
mode = isValue(object.fov) ? MODE.PERSPECTIVE : isValue(object.top) ? MODE.ORTHOGRAPHIC : MODE.UNKNOWN;
|
||||
|
||||
// for reset
|
||||
this.target0 = this.target.clone();
|
||||
this.position0 = this.object.position.clone();
|
||||
|
||||
this.rotateLeft = function (angle) {
|
||||
thetaDelta -= angle;
|
||||
};
|
||||
|
||||
this.rotateUp = function (angle) {
|
||||
phiDelta -= angle;
|
||||
};
|
||||
|
||||
// pass in distance in world space to move left
|
||||
this.panLeft = function (distance) {
|
||||
var te = this.object.matrix.elements;
|
||||
|
||||
// get X column of matrix
|
||||
panOffset.set(te[ 0 ], te[ 1 ], te[ 2 ]);
|
||||
panOffset.multiplyScalar(-distance);
|
||||
pan.add(panOffset);
|
||||
};
|
||||
|
||||
// pass in distance in world space to move up
|
||||
this.panUp = function (distance) {
|
||||
var te = this.object.matrix.elements;
|
||||
|
||||
// get Y column of matrix
|
||||
panOffset.set(te[ 4 ], te[ 5 ], te[ 6 ]);
|
||||
panOffset.multiplyScalar(distance);
|
||||
pan.add(panOffset);
|
||||
};
|
||||
|
||||
// pass in x,y of change desired in pixel space,
|
||||
// right and down are positive
|
||||
this.pan = function (deltaX, deltaY) {
|
||||
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
|
||||
|
||||
switch (mode) {
|
||||
case MODE.PERSPECTIVE:
|
||||
var position = scope.object.position;
|
||||
var offset = position.clone().sub(scope.target);
|
||||
var targetDistance = offset.length();
|
||||
|
||||
// half of the fov is center to top of screen
|
||||
targetDistance *= Math.tan(( scope.object.fov / 2 ) * Math.PI / 180.0);
|
||||
|
||||
// we actually don't use screenWidth, since perspective camera is fixed to screen height
|
||||
scope.panLeft(2 * deltaX * targetDistance / element.clientHeight);
|
||||
scope.panUp(2 * deltaY * targetDistance / element.clientHeight);
|
||||
break;
|
||||
case MODE.ORTHOGRAPHIC:
|
||||
scope.panLeft(deltaX * (scope.object.right - scope.object.left) / element.clientWidth);
|
||||
scope.panUp(deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
function isValue(v) {
|
||||
return v !== null && v !== undefined;
|
||||
}
|
||||
|
||||
function firstValue(choices) {
|
||||
for (var i=0; i<choices.length; i++) {
|
||||
var v = choices[i];
|
||||
if (isValue(v)) return v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
this.setZoom = function(reverse, speed) {
|
||||
scope.reverseZoom = reverse;
|
||||
scope.zoomSpeed = speed || 1.0;
|
||||
};
|
||||
|
||||
this.setPosition = function(set) {
|
||||
thetaSet = firstValue([set.left, set.theta, thetaSet]);
|
||||
phiSet = firstValue([set.up, set.phi, phiSet]);
|
||||
if (set.panX) this.target.x = set.panX;
|
||||
if (set.panY) this.target.y = set.panY;
|
||||
if (set.panZ) this.target.z = set.panZ;
|
||||
if (set.scale) scale = set.scale;
|
||||
};
|
||||
|
||||
this.getPosition = function(scaled) {
|
||||
var t = this.target,
|
||||
pos = { left:theta, up:phi, panX:t.x, panY:t.y, panZ:t.z, scale:scaled ? scaleSave : 1 };
|
||||
return pos;
|
||||
};
|
||||
|
||||
this.dollyIn = function (dollyScale) {
|
||||
if (dollyScale === undefined) {
|
||||
dollyScale = getZoomScale();
|
||||
}
|
||||
scale *= dollyScale;
|
||||
};
|
||||
|
||||
this.dollyOut = function (dollyScale) {
|
||||
if (dollyScale === undefined) {
|
||||
dollyScale = getZoomScale();
|
||||
}
|
||||
scale /= dollyScale;
|
||||
};
|
||||
|
||||
this.update = function () {
|
||||
var position = this.object.position;
|
||||
|
||||
offset.copy(position).sub(this.target);
|
||||
|
||||
// rotate offset to "y-axis-is-up" space
|
||||
offset.applyQuaternion(quat);
|
||||
|
||||
// angle from z-axis around y-axis
|
||||
theta = isValue(thetaSet) ? thetaSet : Math.atan2(offset.x, offset.z);
|
||||
|
||||
// angle from y-axis
|
||||
phi = isValue(phiSet) ? phiSet : Math.atan2(Math.sqrt(offset.x * offset.x + offset.z * offset.z), offset.y);
|
||||
|
||||
theta += thetaDelta;
|
||||
phi += phiDelta;
|
||||
|
||||
// restrict theta to be between desired limits
|
||||
theta = Math.max(this.minAzimuthAngle, Math.min(this.maxAzimuthAngle, theta));
|
||||
|
||||
// restrict phi to be between desired limits
|
||||
phi = Math.max(this.minPolarAngle, Math.min(this.maxPolarAngle, phi));
|
||||
|
||||
// restrict phi to be betwee EPS and PI-EPS
|
||||
phi = Math.max(EPS, Math.min(Math.PI - EPS, phi));
|
||||
|
||||
var radius = offset.length() * (mode === MODE.PERSPECTIVE ? scale : 1);
|
||||
|
||||
// restrict radius to be between desired limits
|
||||
radius = Math.max(this.minDistance, Math.min(this.maxDistance, radius));
|
||||
|
||||
// move target to panned location
|
||||
this.target.add(pan);
|
||||
|
||||
offset.x = radius * Math.sin(phi) * Math.sin(theta);
|
||||
offset.y = radius * Math.cos(phi);
|
||||
offset.z = radius * Math.sin(phi) * Math.cos(theta);
|
||||
|
||||
// rotate offset back to "camera-up-vector-is-up" space
|
||||
offset.applyQuaternion(quatInverse);
|
||||
|
||||
position.copy(this.target).add(offset);
|
||||
this.object.lookAt(this.target);
|
||||
|
||||
thetaDelta = 0;
|
||||
thetaSet = null;
|
||||
phiDelta = 0;
|
||||
phiSet = null;
|
||||
pan.set(0, 0, 0);
|
||||
|
||||
scaleSave *= scale;
|
||||
|
||||
if (mode === MODE.ORTHOGRAPHIC) {
|
||||
scope.object.zoom = 1/scale;
|
||||
scope.object.updateProjectionMatrix();
|
||||
} else {
|
||||
scale = 1;
|
||||
}
|
||||
|
||||
// update condition is:
|
||||
// min(camera displacement, camera rotation in radians)^2 > EPS
|
||||
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
|
||||
if (lastPosition.distanceToSquared(this.object.position) > EPS
|
||||
|| 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS) {
|
||||
|
||||
this.dispatchEvent(changeEvent);
|
||||
lastPosition.copy(this.object.position);
|
||||
lastQuaternion.copy(this.object.quaternion);
|
||||
if (notify) notify(position, true);
|
||||
} else {
|
||||
if (notify) notify(position, false);
|
||||
}
|
||||
};
|
||||
|
||||
this.reset = function () {
|
||||
state = STATE.NONE;
|
||||
scale = 1;
|
||||
scaleSave = 1;
|
||||
this.target.copy(this.target0);
|
||||
this.object.position.copy(this.position0);
|
||||
};
|
||||
|
||||
this.getPolarAngle = function () {
|
||||
return phi;
|
||||
};
|
||||
|
||||
this.getAzimuthalAngle = function () {
|
||||
return theta
|
||||
};
|
||||
|
||||
function getZoomScale() {
|
||||
return Math.pow(0.95, scope.zoomSpeed);
|
||||
}
|
||||
|
||||
function onMouseDown(event) {
|
||||
if (scope.enabled === false) return;
|
||||
event.preventDefault();
|
||||
|
||||
switch (event.button) {
|
||||
case scope.mouseButtons.ORBIT:
|
||||
state = event.metaKey ? STATE.PAN : STATE.ROTATE;
|
||||
break;
|
||||
case scope.mouseButtons.ZOOM:
|
||||
state = STATE.DOLLY;
|
||||
break;
|
||||
case scope.mouseButtons.PAN:
|
||||
state = STATE.PAN;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case STATE.ROTATE:
|
||||
if (scope.noRotate === true) return state = STATE.NONE;
|
||||
rotateStart.set(event.clientX, event.clientY);
|
||||
break;
|
||||
case STATE.DOLLY:
|
||||
if (scope.noZoom === true) return state = STATE.NONE;
|
||||
dollyStart.set(event.clientX, event.clientY);
|
||||
break;
|
||||
case STATE.PAN:
|
||||
if (scope.noPan === true) return state = STATE.NONE;
|
||||
panStart.set(event.clientX, event.clientY);
|
||||
break;
|
||||
}
|
||||
|
||||
document.addEventListener('mousemove', onMouseMove, false);
|
||||
document.addEventListener('mouseup', onMouseUp, false);
|
||||
scope.dispatchEvent(startEvent);
|
||||
}
|
||||
|
||||
function onMouseMove(event) {
|
||||
if (scope.enabled === false) return;
|
||||
event.preventDefault();
|
||||
|
||||
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
|
||||
|
||||
if (state === STATE.ROTATE) {
|
||||
if (scope.noRotate === true) return;
|
||||
|
||||
rotateEnd.set(event.clientX, event.clientY);
|
||||
rotateDelta.subVectors(rotateEnd, rotateStart);
|
||||
|
||||
// rotating across whole screen goes 360 degrees around
|
||||
scope.rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed);
|
||||
// rotating up and down along whole screen attempts to go 360, but limited to 180
|
||||
scope.rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed);
|
||||
|
||||
rotateStart.copy(rotateEnd);
|
||||
|
||||
} else if (state === STATE.DOLLY) {
|
||||
if (scope.noZoom === true) return;
|
||||
|
||||
dollyEnd.set(event.clientX, event.clientY);
|
||||
dollyDelta.subVectors(dollyEnd, dollyStart);
|
||||
|
||||
if (dollyDelta.y > 0) {
|
||||
scope.dollyIn();
|
||||
} else {
|
||||
scope.dollyOut();
|
||||
}
|
||||
|
||||
dollyStart.copy(dollyEnd);
|
||||
|
||||
} else if (state === STATE.PAN) {
|
||||
if (scope.noPan === true) return;
|
||||
|
||||
panEnd.set(event.clientX, event.clientY);
|
||||
panDelta.subVectors(panEnd, panStart);
|
||||
scope.pan(panDelta.x, panDelta.y);
|
||||
panStart.copy(panEnd);
|
||||
}
|
||||
|
||||
scope.update();
|
||||
}
|
||||
|
||||
function onMouseUp(/* event */) {
|
||||
if (scope.enabled === false) return;
|
||||
|
||||
document.removeEventListener('mousemove', onMouseMove, false);
|
||||
document.removeEventListener('mouseup', onMouseUp, false);
|
||||
scope.dispatchEvent(endEvent);
|
||||
state = STATE.NONE;
|
||||
}
|
||||
|
||||
function onMouseWheel(event) {
|
||||
if (scope.enabled === false || scope.noZoom === true) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if (event.shiftKey && slider) {
|
||||
slider(event.deltaX || event.wheelDelta || event.detail);
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = -(event.deltaY || event.wheelDelta || event.detail || 0);
|
||||
|
||||
if (delta === 0) return;
|
||||
|
||||
if (scope.reverseZoom) {
|
||||
delta = -delta;
|
||||
}
|
||||
|
||||
if (delta > 0) {
|
||||
scope.dollyOut();
|
||||
} else if (delta < 0) {
|
||||
scope.dollyIn();
|
||||
}
|
||||
|
||||
scope.update();
|
||||
scope.dispatchEvent(startEvent);
|
||||
scope.dispatchEvent(endEvent);
|
||||
}
|
||||
|
||||
function onKeyDown(event) {
|
||||
if (scope.enabled === false || scope.noKeys === true || scope.noPan === true) return;
|
||||
|
||||
switch (event.keyCode) {
|
||||
|
||||
case scope.keys.UP:
|
||||
scope.pan(0, scope.keyPanSpeed);
|
||||
scope.update();
|
||||
break;
|
||||
|
||||
case scope.keys.BOTTOM:
|
||||
scope.pan(0, -scope.keyPanSpeed);
|
||||
scope.update();
|
||||
break;
|
||||
|
||||
case scope.keys.LEFT:
|
||||
scope.pan(scope.keyPanSpeed, 0);
|
||||
scope.update();
|
||||
break;
|
||||
|
||||
case scope.keys.RIGHT:
|
||||
scope.pan(-scope.keyPanSpeed, 0);
|
||||
scope.update();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function touchstart(event) {
|
||||
if (scope.enabled === false) return;
|
||||
|
||||
switch (event.touches.length) {
|
||||
case 1: // one-fingered touch: rotate
|
||||
if (scope.noRotate === true) return;
|
||||
|
||||
state = STATE.TOUCH_ROTATE;
|
||||
|
||||
rotateStart.set(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY);
|
||||
break;
|
||||
|
||||
case 2: // two-fingered touch: dolly
|
||||
if (scope.noZoom === true) return;
|
||||
|
||||
state = STATE.TOUCH_DOLLY;
|
||||
|
||||
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
|
||||
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
|
||||
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||
dollyStart.set(0, distance);
|
||||
break;
|
||||
|
||||
case 3: // three-fingered touch: pan
|
||||
if (scope.noPan === true) return;
|
||||
|
||||
state = STATE.TOUCH_PAN;
|
||||
|
||||
panStart.set(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY);
|
||||
break;
|
||||
|
||||
default:
|
||||
state = STATE.NONE;
|
||||
}
|
||||
scope.dispatchEvent(startEvent);
|
||||
}
|
||||
|
||||
function touchmove(event) {
|
||||
if (scope.enabled === false) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
|
||||
|
||||
switch (event.touches.length) {
|
||||
case 1: // one-fingered touch: rotate
|
||||
if (scope.noRotate === true) return;
|
||||
if (state !== STATE.TOUCH_ROTATE) return;
|
||||
|
||||
rotateEnd.set(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY);
|
||||
rotateDelta.subVectors(rotateEnd, rotateStart);
|
||||
|
||||
// rotating across whole screen goes 360 degrees around
|
||||
scope.rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed);
|
||||
// rotating up and down along whole screen attempts to go 360, but limited to 180
|
||||
scope.rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed);
|
||||
|
||||
rotateStart.copy(rotateEnd);
|
||||
scope.update();
|
||||
break;
|
||||
|
||||
case 2: // two-fingered touch: dolly
|
||||
if (scope.noZoom === true) return;
|
||||
if (state !== STATE.TOUCH_DOLLY) return;
|
||||
|
||||
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
|
||||
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
|
||||
var distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (reverseZoom) distance = -distance;
|
||||
|
||||
dollyEnd.set(0, distance);
|
||||
dollyDelta.subVectors(dollyEnd, dollyStart);
|
||||
|
||||
if (dollyDelta.y > 0) {
|
||||
scope.dollyOut();
|
||||
} else {
|
||||
scope.dollyIn();
|
||||
}
|
||||
|
||||
dollyStart.copy(dollyEnd);
|
||||
scope.update();
|
||||
break;
|
||||
|
||||
case 3: // three-fingered touch: pan
|
||||
if (scope.noPan === true) return;
|
||||
if (state !== STATE.TOUCH_PAN) return;
|
||||
|
||||
panEnd.set(event.touches[ 0 ].pageX, event.touches[ 0 ].pageY);
|
||||
panDelta.subVectors(panEnd, panStart);
|
||||
scope.pan(panDelta.x, panDelta.y);
|
||||
panStart.copy(panEnd);
|
||||
|
||||
scope.update();
|
||||
break;
|
||||
|
||||
default:
|
||||
state = STATE.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
function touchend(/* event */) {
|
||||
if (scope.enabled === false) return;
|
||||
scope.dispatchEvent(endEvent);
|
||||
state = STATE.NONE;
|
||||
}
|
||||
|
||||
this.onMouseUp = onMouseUp;
|
||||
|
||||
domEl.addEventListener('contextmenu', function (event) { event.preventDefault() }, false);
|
||||
domEl.addEventListener('mousedown', onMouseDown, false);
|
||||
domEl.addEventListener('mousewheel', onMouseWheel, false);
|
||||
domEl.addEventListener('DOMMouseScroll', onMouseWheel, false); // firefox
|
||||
domEl.addEventListener('touchstart', touchstart, false);
|
||||
domEl.addEventListener('touchend', touchend, false);
|
||||
domEl.addEventListener('touchmove', touchmove, false);
|
||||
|
||||
window.addEventListener('keydown', onKeyDown, false);
|
||||
};
|
||||
|
||||
THREE.CubeControls.prototype = Object.create(THREE.EventDispatcher.prototype);
|
||||
211
js/moto-db.js
Normal file
211
js/moto-db.js
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
"use strict";
|
||||
|
||||
var gs_moto_db = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.moto) self.moto = {};
|
||||
if (self.moto.Storage) return;
|
||||
|
||||
self.moto.Storage = Storage;
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
|
||||
|
||||
var IDB = self.indexedDB || self.mozIndexedDB || self.webkitIndexedDB || self.msIndexedDB,
|
||||
IRR = self.IDBKeyRange,
|
||||
local = null;
|
||||
|
||||
/**
|
||||
* @param {String} dbname
|
||||
* @param {number} [version]
|
||||
* @constructor
|
||||
*/
|
||||
function Storage(dbname, version) {
|
||||
this.db = null;
|
||||
this.name = dbname;
|
||||
this.version = version || 1;
|
||||
this.queue = [];
|
||||
this.initCalled = false;
|
||||
}
|
||||
|
||||
Storage.delete = function(dbname) {
|
||||
IDB.deleteDatabase(dbname);
|
||||
};
|
||||
|
||||
var SP = Storage.prototype;
|
||||
|
||||
/** ******************************************************************
|
||||
* indexedDB implementation
|
||||
******************************************************************* */
|
||||
|
||||
SP.keys = function(callback, lower, upper) {
|
||||
var out = [];
|
||||
this.iterate(function(k,v) {
|
||||
if (k) out.push(k);
|
||||
}, lower, upper, true);
|
||||
callback(out);
|
||||
};
|
||||
|
||||
SP.iterate = function(callback, lower, upper, nullterm) {
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(["iterate", callback, lower, upper]);
|
||||
}
|
||||
var range = lower && upper ? IRR.bound(lower,upper) :
|
||||
lower ? IRR.lowerBound(lower) :
|
||||
upper ? IRR.upperBound(upper) : undefined;
|
||||
// iterate over all db values for debugging
|
||||
this.db.transaction(this.name).objectStore(this.name).openCursor(range).onsuccess = function(event) {
|
||||
var cursor = event.target.result;
|
||||
if (cursor) {
|
||||
callback(cursor.key,cursor.value);
|
||||
cursor.continue();
|
||||
} else if (nullterm) {
|
||||
callback(null);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
SP.deleteStore = function() {
|
||||
if (this.initCalled) throw "cannot delete ObjectStore after init() called";
|
||||
this.version = Number.MAX_SAFE_INTEGER || Number.MAX_VALUE;
|
||||
return this.init(true);
|
||||
};
|
||||
|
||||
SP.init = function(deleteOS) {
|
||||
if (this.initCalled) return;
|
||||
|
||||
var storage = this,
|
||||
name = this.name,
|
||||
request = null;
|
||||
|
||||
function fallback() {
|
||||
console.log("in private browsing mode or browser lacks support for IndexedDB. unable to setup storage for '" + name + "'.");
|
||||
local = {};
|
||||
storage.runQueue();
|
||||
}
|
||||
|
||||
try {
|
||||
request = IDB.open(name, this.version);
|
||||
|
||||
request.onupgradeneeded = function(event) {
|
||||
if (deleteOS) {
|
||||
storage.db.deleteObjectStore(name);
|
||||
return;
|
||||
}
|
||||
storage.db = request.result;
|
||||
storage.store = storage.db.createObjectStore(name);
|
||||
setTimeout(function() { storage.runQueue() });
|
||||
};
|
||||
|
||||
request.onsuccess = function(event) {
|
||||
storage.db = request.result;
|
||||
storage.runQueue();
|
||||
};
|
||||
|
||||
request.onerror = function(event) {
|
||||
console.log({error:event});
|
||||
fallback();
|
||||
};
|
||||
} catch (e) {
|
||||
fallback();
|
||||
return;
|
||||
}
|
||||
|
||||
this.initCalled = true;
|
||||
return this;
|
||||
};
|
||||
|
||||
SP.runQueue = function() {
|
||||
if (this.queue.length > 0) {
|
||||
var i = 0, q = this.queue, e;
|
||||
while (i < q.length) {
|
||||
e = q[i++];
|
||||
switch (e[0]) {
|
||||
case 'iterate': this.iterate(e[1], e[2], e[3]); break;
|
||||
case 'put': this.put(e[1], e[2], e[3]); break;
|
||||
case 'get': this.get(e[1], e[2]); break;
|
||||
case 'remove': this.remove(e[1], e[2]); break;
|
||||
case 'clear': this.clear();
|
||||
}
|
||||
}
|
||||
this.queue = [];
|
||||
}
|
||||
};
|
||||
|
||||
SP.put = function(key, value, callback) {
|
||||
if (local) {
|
||||
local[key] = value;
|
||||
if (callback) callback(true);
|
||||
return;
|
||||
}
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(['put', key, value, callback]);
|
||||
}
|
||||
try {
|
||||
var req = this.db.transaction(this.name, "readwrite").objectStore(this.name).put(value, key);
|
||||
if (callback) {
|
||||
req.onsuccess = function(event) { callback(true) };
|
||||
req.onerror = function(event) { callback(false) };
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (callback) callback(false);
|
||||
}
|
||||
};
|
||||
|
||||
SP.get = function(key, callback) {
|
||||
if (local) {
|
||||
if (callback) callback(local[key]);
|
||||
return;
|
||||
}
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(['get', key, callback]);
|
||||
}
|
||||
try {
|
||||
var req = this.db.transaction(this.name).objectStore(this.name).get(key);
|
||||
if (callback) {
|
||||
req.onsuccess = function(event) { callback(req.result) };
|
||||
req.onerror = function(event) { callback(null) };
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (callback) callback(null);
|
||||
}
|
||||
};
|
||||
|
||||
SP.remove = function(key, callback) {
|
||||
if (local) {
|
||||
delete local[key];
|
||||
if (callback) callback(true);
|
||||
return;
|
||||
}
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(['remove', key, callback]);
|
||||
}
|
||||
try {
|
||||
var req = this.db.transaction(this.name, "readwrite").objectStore(this.name).delete(key);
|
||||
if (callback) {
|
||||
req.onsuccess = function(event) { callback(true) };
|
||||
req.onerror = function(event) { callback(false) };
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (callback) callback(false);
|
||||
}
|
||||
};
|
||||
|
||||
SP.clear = function(key) {
|
||||
if (!this.db) {
|
||||
this.init();
|
||||
return this.queue.push(['clear']);
|
||||
}
|
||||
this.db.transaction(this.name, "readwrite").objectStore(this.name).clear();
|
||||
};
|
||||
|
||||
})();
|
||||
56
js/moto-kv.js
Normal file
56
js/moto-kv.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
"use strict";
|
||||
|
||||
var gs_moto_kv = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.moto) self.moto = {};
|
||||
if (self.moto.KV) return;
|
||||
|
||||
try {
|
||||
self.moto.KV = self.localStorage;
|
||||
self.moto.KV.setItem('__test',1);
|
||||
self.moto.KV.getItem('__test');
|
||||
} catch (e) {
|
||||
console.log("in private browsing mode or 3rd party storage blocked. some settings will be lost.");
|
||||
self.moto.KV = new KV();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {String} dbname
|
||||
* @param {number} [version]
|
||||
* @constructor
|
||||
*/
|
||||
function KV() {
|
||||
this.__data__ = {};
|
||||
this.__mem__ = true;
|
||||
}
|
||||
|
||||
var KP = KV.prototype;
|
||||
|
||||
KP.getItem = function(key) {
|
||||
return this[key];
|
||||
};
|
||||
|
||||
KP.setItem = function(key, val) {
|
||||
this.__data__[key] = val;
|
||||
this[key] = val;
|
||||
};
|
||||
|
||||
KP.removeItem = function(key) {
|
||||
delete this.__data__[key];
|
||||
};
|
||||
|
||||
KP.clear = function() {
|
||||
var d = this.__data__, key;
|
||||
for (key in d) {
|
||||
if (d.hasOwnProperty(key)) {
|
||||
delete d[key];
|
||||
delete this[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
239
js/moto-load-obj.js
Normal file
239
js/moto-load-obj.js
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
/**
|
||||
* adapted from THREE.OBJLoader example
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/Wavefront_.obj_file
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
|
||||
if (!self.moto) self.moto = {};
|
||||
|
||||
self.moto.OBJ = {
|
||||
parse : parse
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} text
|
||||
* @returns {Array}
|
||||
*/
|
||||
function parse(text) {
|
||||
|
||||
var object,
|
||||
objects = [],
|
||||
geometry,
|
||||
material;
|
||||
|
||||
function parseVertexIndex(value) {
|
||||
var index = parseInt(value);
|
||||
return (index >= 0 ? index - 1 : index + vertices.length / 3) * 3;
|
||||
}
|
||||
|
||||
function parseNormalIndex(value) {
|
||||
var index = parseInt(value);
|
||||
return (index >= 0 ? index - 1 : index + normals.length / 3) * 3;
|
||||
}
|
||||
|
||||
function parseUVIndex(value) {
|
||||
var index = parseInt(value);
|
||||
return (index >= 0 ? index - 1 : index + uvs.length / 2) * 2;
|
||||
}
|
||||
|
||||
function addVertex(a, b, c) {
|
||||
geometry.vertices.push(
|
||||
vertices[a], vertices[a + 1], vertices[a + 2],
|
||||
vertices[b], vertices[b + 1], vertices[b + 2],
|
||||
vertices[c], vertices[c + 1], vertices[c + 2]
|
||||
);
|
||||
}
|
||||
|
||||
function addNormal(a, b, c) {
|
||||
geometry.normals.push(
|
||||
normals[a], normals[a + 1], normals[a + 2],
|
||||
normals[b], normals[b + 1], normals[b + 2],
|
||||
normals[c], normals[c + 1], normals[c + 2]
|
||||
);
|
||||
}
|
||||
|
||||
function addUV(a, b, c) {
|
||||
geometry.uvs.push(
|
||||
uvs[a], uvs[a + 1],
|
||||
uvs[b], uvs[b + 1],
|
||||
uvs[c], uvs[c + 1]
|
||||
);
|
||||
}
|
||||
|
||||
function addFace(a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd) {
|
||||
|
||||
var ia = parseVertexIndex(a);
|
||||
var ib = parseVertexIndex(b);
|
||||
var ic = parseVertexIndex(c);
|
||||
var id;
|
||||
|
||||
if (d === undefined) {
|
||||
addVertex(ia, ib, ic);
|
||||
} else {
|
||||
id = parseVertexIndex(d);
|
||||
addVertex(ia, ib, id);
|
||||
addVertex(ib, ic, id);
|
||||
}
|
||||
|
||||
if (ua !== undefined) {
|
||||
ia = parseUVIndex(ua);
|
||||
ib = parseUVIndex(ub);
|
||||
ic = parseUVIndex(uc);
|
||||
|
||||
if (d === undefined) {
|
||||
addUV(ia, ib, ic);
|
||||
} else {
|
||||
id = parseUVIndex(ud);
|
||||
addUV(ia, ib, id);
|
||||
addUV(ib, ic, id);
|
||||
}
|
||||
}
|
||||
|
||||
if (na !== undefined) {
|
||||
ia = parseNormalIndex(na);
|
||||
ib = parseNormalIndex(nb);
|
||||
ic = parseNormalIndex(nc);
|
||||
|
||||
if (d === undefined) {
|
||||
addNormal(ia, ib, ic);
|
||||
} else {
|
||||
id = parseNormalIndex(nd);
|
||||
addNormal(ia, ib, id);
|
||||
addNormal(ib, ic, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create mesh if no objects in text
|
||||
|
||||
if (/^o /gm.test(text) === false) {
|
||||
geometry = {
|
||||
vertices: [],
|
||||
normals: [],
|
||||
uvs: []
|
||||
};
|
||||
|
||||
object = {
|
||||
name: '',
|
||||
geometry: geometry
|
||||
};
|
||||
|
||||
objects.push(object);
|
||||
}
|
||||
|
||||
var vertices = [],
|
||||
normals = [],
|
||||
uvs = [],
|
||||
// v float float float
|
||||
vertex_pattern = /v(+[\d|\.|\+|\-|e|E]+)(+[\d|\.|\+|\-|e|E]+)(+[\d|\.|\+|\-|e|E]+)/,
|
||||
// vn float float float
|
||||
normal_pattern = /vn(+[\d|\.|\+|\-|e|E]+)(+[\d|\.|\+|\-|e|E]+)(+[\d|\.|\+|\-|e|E]+)/,
|
||||
// vt float float
|
||||
uv_pattern = /vt(+[\d|\.|\+|\-|e|E]+)(+[\d|\.|\+|\-|e|E]+)/,
|
||||
// f vertex vertex vertex ...
|
||||
face_pattern1 = /f(+-?\d+)(+-?\d+)(+-?\d+)(+-?\d+)?/,
|
||||
// f vertex/uv vertex/uv vertex/uv ...
|
||||
face_pattern2 = /f(+(-?\d+)\/(-?\d+))(+(-?\d+)\/(-?\d+))(+(-?\d+)\/(-?\d+))(+(-?\d+)\/(-?\d+))?/,
|
||||
// f vertex/uv/normal vertex/uv/normal vertex/uv/normal ...
|
||||
face_pattern3 = /f(+(-?\d+)\/(-?\d+)\/(-?\d+))(+(-?\d+)\/(-?\d+)\/(-?\d+))(+(-?\d+)\/(-?\d+)\/(-?\d+))(+(-?\d+)\/(-?\d+)\/(-?\d+))?/,
|
||||
// f vertex//normal vertex//normal vertex//normal ...
|
||||
face_pattern4 = /f(+(-?\d+)\/\/(-?\d+))(+(-?\d+)\/\/(-?\d+))(+(-?\d+)\/\/(-?\d+))(+(-?\d+)\/\/(-?\d+))?/,
|
||||
// split text into lines
|
||||
lines = text.split('\n');
|
||||
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
|
||||
var line = lines[i];
|
||||
line = line.trim();
|
||||
|
||||
var result;
|
||||
|
||||
if (line.length === 0 || line.charAt(0) === '#') {
|
||||
continue;
|
||||
} else if ((result = vertex_pattern.exec(line)) !== null) {
|
||||
// ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
|
||||
vertices.push(
|
||||
parseFloat(result[1]),
|
||||
parseFloat(result[2]),
|
||||
parseFloat(result[3])
|
||||
);
|
||||
} else if ((result = normal_pattern.exec(line)) !== null) {
|
||||
// ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
|
||||
normals.push(
|
||||
parseFloat(result[1]),
|
||||
parseFloat(result[2]),
|
||||
parseFloat(result[3])
|
||||
);
|
||||
} else if ((result = uv_pattern.exec(line)) !== null) {
|
||||
// ["vt 0.1 0.2", "0.1", "0.2"]
|
||||
uvs.push(
|
||||
parseFloat(result[1]),
|
||||
parseFloat(result[2])
|
||||
);
|
||||
} else if ((result = face_pattern1.exec(line)) !== null) {
|
||||
// ["f 1 2 3", "1", "2", "3", undefined]
|
||||
addFace(
|
||||
result[1], result[2], result[3], result[4]
|
||||
);
|
||||
} else if ((result = face_pattern2.exec(line)) !== null) {
|
||||
// ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined]
|
||||
addFace(
|
||||
result[2], result[5], result[8], result[11],
|
||||
result[3], result[6], result[9], result[12]
|
||||
);
|
||||
} else if ((result = face_pattern3.exec(line)) !== null) {
|
||||
// ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined]
|
||||
addFace(
|
||||
result[2], result[6], result[10], result[14],
|
||||
result[3], result[7], result[11], result[15],
|
||||
result[4], result[8], result[12], result[16]
|
||||
);
|
||||
} else if ((result = face_pattern4.exec(line)) !== null) {
|
||||
// ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined]
|
||||
addFace(
|
||||
result[2], result[5], result[8], result[11],
|
||||
undefined, undefined, undefined, undefined,
|
||||
result[3], result[6], result[9], result[12]
|
||||
);
|
||||
} else if (/^o /.test(line)) {
|
||||
|
||||
geometry = {
|
||||
vertices: [],
|
||||
normals: [],
|
||||
uvs: []
|
||||
};
|
||||
|
||||
object = {
|
||||
name: line.substring(2).trim(),
|
||||
geometry: geometry
|
||||
};
|
||||
|
||||
objects.push(object)
|
||||
|
||||
} else if (/^g /.test(line)) {
|
||||
// group
|
||||
} else if (/^usemtl /.test(line)) {
|
||||
// material
|
||||
material.name = line.substring(7).trim();
|
||||
} else if (/^mtllib /.test(line)) {
|
||||
// mtl file
|
||||
} else if (/^s /.test(line)) {
|
||||
// smooth shading
|
||||
} else {
|
||||
// unhandled
|
||||
}
|
||||
}
|
||||
|
||||
objects.forEach(function(object) {
|
||||
object.geometry.vertices = new Float32Array(object.geometry.vertices);
|
||||
object.geometry.normals = new Float32Array(object.geometry.normals);
|
||||
object.geometry.uvs = new Float32Array(object.geometry.uvs);
|
||||
});
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
})();
|
||||
250
js/moto-load-stl.js
Normal file
250
js/moto-load-stl.js
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
/**
|
||||
* adapted from THREE.STL-LOADER example
|
||||
*
|
||||
* https://en.wikipedia.org/wiki/STL_(file_format)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
(function() {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function STL() {
|
||||
this.vertices = null;
|
||||
this.normals = null;
|
||||
this.colors = null;
|
||||
}
|
||||
|
||||
var SP = STL.prototype;
|
||||
|
||||
if (!self.moto) self.moto = {};
|
||||
|
||||
self.moto.STL = STL;
|
||||
|
||||
SP.load = function(url, callback) {
|
||||
var 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);
|
||||
if (callback) callback(stl.vertices);
|
||||
} 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('GET', url, true);
|
||||
xhr.responseType = 'arraybuffer';
|
||||
xhr.send(null);
|
||||
};
|
||||
|
||||
SP.encode = function(vertices, normals) {
|
||||
if (!(vertices && vertices.length % 3 === 0)) throw "invalid vertices";
|
||||
|
||||
var 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;
|
||||
};
|
||||
|
||||
SP.parse = function(data) {
|
||||
var binData = this.convertToBinary(data);
|
||||
|
||||
var isBinary = function () {
|
||||
var 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)
|
||||
: this.parseASCII(this.convertToString(data));
|
||||
};
|
||||
|
||||
SP.parseBinary = function(data) {
|
||||
var 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 (var 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;
|
||||
}
|
||||
}
|
||||
|
||||
var 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 (var face = 0; face < faces; face ++) {
|
||||
|
||||
var start = dataOffset + face * faceLength,
|
||||
normalX = reader.getFloat32(start, true),
|
||||
normalY = reader.getFloat32(start + 4, true),
|
||||
normalZ = reader.getFloat32(start + 8, true);
|
||||
|
||||
if (hasColors) {
|
||||
var 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;
|
||||
}
|
||||
}
|
||||
|
||||
var i = 1, vertexstart;
|
||||
|
||||
while (i <= 3) {
|
||||
vertexstart = start + (i++) * 12;
|
||||
vertices[offset ] = reader.getFloat32 (vertexstart, true);
|
||||
vertices[offset + 1] = reader.getFloat32 (vertexstart + 4, true);
|
||||
vertices[offset + 2] = reader.getFloat32 (vertexstart + 8, true);
|
||||
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) {
|
||||
var 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]));
|
||||
vertices.push(parseFloat(result[3]));
|
||||
vertices.push(parseFloat(result[5]));
|
||||
}
|
||||
}
|
||||
|
||||
var 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") {
|
||||
var array_buffer = new Uint8Array(buf);
|
||||
var str = '';
|
||||
for (var 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") {
|
||||
var array_buffer = new Uint8Array(buf.length);
|
||||
for (var i = 0; i < buf.length; i++) {
|
||||
array_buffer[i] = buf.charCodeAt(i) & 0xff;
|
||||
}
|
||||
return array_buffer.buffer || array_buffer;
|
||||
} else {
|
||||
return buf;
|
||||
}
|
||||
};
|
||||
|
||||
})();
|
||||
622
js/moto-space.js
Normal file
622
js/moto-space.js
Normal file
|
|
@ -0,0 +1,622 @@
|
|||
"use strict";
|
||||
|
||||
var gs_moto_space = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
var WIN = window,
|
||||
DOC = document,
|
||||
SCENE = new THREE.Scene(),
|
||||
WORLD = new THREE.Group(),
|
||||
WC = WORLD.children,
|
||||
PI = Math.PI,
|
||||
PI2 = PI / 2,
|
||||
PI4 = PI / 4,
|
||||
ROUND = Math.round,
|
||||
panY = 0,
|
||||
gridZOff = 0,
|
||||
platformZOff = 0,
|
||||
perspective = 35,
|
||||
refreshRequested = false,
|
||||
selectRecurse = false,
|
||||
defaultKeys = true,
|
||||
lightIntensity = 0.3,
|
||||
initialized = false,
|
||||
alignedTracking = false,
|
||||
skyColor = 0xbbbbbb,
|
||||
skyGridColor = 0xcccccc,
|
||||
showSkyGrid = true,
|
||||
showPlatform = true,
|
||||
hidePlatformBelow = true,
|
||||
trackcam = addLight(0, 0, 0, lightIntensity/3),
|
||||
trackDelta = {x:0, y:0, z:0},
|
||||
mouse = {x: 0, y: 0},
|
||||
mouseMoved = false,
|
||||
mouseDragPoint = null,
|
||||
mouseDragStart = null,
|
||||
mouseDownSelect,
|
||||
mouseUpSelect,
|
||||
mouseHover,
|
||||
mouseDrag,
|
||||
gridUnitMinor,
|
||||
gridUnitMajor,
|
||||
gridView,
|
||||
viewControl,
|
||||
trackPlane,
|
||||
platform,
|
||||
platformHover,
|
||||
platformClick,
|
||||
platformClickAt,
|
||||
platformOnMove,
|
||||
platformMoveTimer,
|
||||
light1,
|
||||
light2,
|
||||
light3,
|
||||
light4,
|
||||
light5,
|
||||
camera,
|
||||
renderer,
|
||||
container;
|
||||
|
||||
/** ******************************************************************
|
||||
* TWEENing Functions
|
||||
******************************************************************* */
|
||||
|
||||
function tweenit() {
|
||||
TWEEN.update();
|
||||
setTimeout(tweenit, 20);
|
||||
}
|
||||
|
||||
tweenit();
|
||||
|
||||
function tweenCamPan(x,y,z) {
|
||||
var pos = viewControl.getPosition();
|
||||
pos.panX = x;
|
||||
pos.panY = y;
|
||||
pos.panZ = z;
|
||||
tweenCam(pos);
|
||||
}
|
||||
|
||||
function tweenCam(pos) {
|
||||
var tf = function () {
|
||||
viewControl.setPosition(this);
|
||||
refresh();
|
||||
};
|
||||
new TWEEN.Tween(viewControl.getPosition()).
|
||||
to(pos, 500).
|
||||
onUpdate(tf).
|
||||
start();
|
||||
}
|
||||
|
||||
function tweenPlatform(w,h,d) {
|
||||
var from = {x: platform.scale.x, y: platform.scale.y, z: platform.scale.z},
|
||||
to = {x:w, y:h, z:d},
|
||||
gridMajor = gridUnitMajor,
|
||||
gridMinor = gridUnitMinor,
|
||||
start = function() {
|
||||
setGrid(0);
|
||||
},
|
||||
update = function() {
|
||||
setPlatformSize(this.x, this.y, this.z);
|
||||
refresh();
|
||||
},
|
||||
complete = function() {
|
||||
setGrid(gridMajor, gridMinor);
|
||||
};
|
||||
new TWEEN.Tween(from).
|
||||
to(to, 500).
|
||||
onStart(start).
|
||||
onUpdate(update).
|
||||
onComplete(complete).
|
||||
start();
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Utility Functions
|
||||
******************************************************************* */
|
||||
|
||||
function width() { return WIN.innerWidth }
|
||||
|
||||
function height() { return WIN.innerHeight }
|
||||
|
||||
function aspect() { return width() / height() }
|
||||
|
||||
function addEventListener(el, key, fn) {
|
||||
el.addEventListener(key, fn);
|
||||
}
|
||||
|
||||
function addEventHandlers(el, pairs) {
|
||||
for (var i=0; i<pairs.length; i += 2) {
|
||||
addEventListener(el, pairs[i], pairs[i+1]);
|
||||
}
|
||||
}
|
||||
|
||||
function onEnterKey(el, fn) {
|
||||
if (Array.isArray(el)) {
|
||||
for (var i=0; i<el.length; i += 2) onEnterKey(el[i], el[i+1]);
|
||||
return;
|
||||
}
|
||||
addEventListener(el, 'keyup', function(event) {
|
||||
if (event.keyCode === 13) fn(event);
|
||||
});
|
||||
}
|
||||
|
||||
function addLight(x,y,z,i) {
|
||||
var l = new THREE.PointLight(0xffffff, i, 0);
|
||||
l.position.set(x,y,z);
|
||||
SCENE.add(l);
|
||||
return l;
|
||||
}
|
||||
|
||||
function updatePlatformPosition() {
|
||||
platform.position.y = -platform.scale.z/2 - platformZOff;
|
||||
// platform.position.y = -(platform.scale.z / 2 + platformZOff);
|
||||
requestRefresh();
|
||||
}
|
||||
|
||||
function setPlatformSize(width, depth, height) {
|
||||
platform.scale.set(width || 300, depth || 175, height || 5);
|
||||
viewControl.maxDistance = Math.max(width,depth) * 2;
|
||||
updatePlatformPosition();
|
||||
var y = Math.max(width, height) * 1;
|
||||
light1.position.set( width, y, depth);
|
||||
light2.position.set(-width, y, -depth);
|
||||
light4.position.set( width, light4.position.y, -depth);
|
||||
light5.position.set(-width, light5.position.y, depth);
|
||||
}
|
||||
|
||||
function setPlatformSizeUpdateGrid(width, depth, height) {
|
||||
setPlatformSize(width, depth, height);
|
||||
setGrid(gridUnitMajor, gridUnitMinor);
|
||||
}
|
||||
|
||||
function setPlatformColor(color) {
|
||||
platform.material.color.set(color);
|
||||
requestRefresh();
|
||||
}
|
||||
|
||||
function setGrid(unitMajor, unitMinor, colorMajor, colorMinor) {
|
||||
if (gridView) Space.scene.remove(gridView);
|
||||
if (!unitMajor) return;
|
||||
gridView = new THREE.Group();
|
||||
gridUnitMajor = unitMajor;
|
||||
gridUnitMinor = unitMinor;
|
||||
var x = platform.scale.x,
|
||||
y = platform.scale.y,
|
||||
z = platform.scale.z,
|
||||
xr = ROUND(x / unitMajor) * unitMajor,
|
||||
yr = ROUND(y / unitMajor) * unitMajor,
|
||||
xo = Math.ceil(xr / 2),
|
||||
yo = Math.ceil(yr / 2),
|
||||
w = x / 2,
|
||||
h = y / 2,
|
||||
d = z / 2,
|
||||
zp = -d - platformZOff + gridZOff,
|
||||
majors = [], minors = unitMinor ? [] : null, i;
|
||||
|
||||
for (i = -xo; i <= xo; i++) {
|
||||
if (i >= -w && i <= w) {
|
||||
if (i % unitMajor === 0) majors.append({x:i, y:-h, z:zp}).append({x:i, y:h, z:zp});
|
||||
else if (minors && i % unitMinor === 0) minors.append({x:i, y:-h, z:zp}).append({x:i, y:h, z:zp});
|
||||
}
|
||||
}
|
||||
for (i = -yo; i <= yo; i++) {
|
||||
if (i >= -h && i <= h) {
|
||||
if (i % unitMajor === 0) majors.append({x:-w, y:i, z:zp}).append({x:w, y:i, z:zp});
|
||||
else if (minors && i % unitMinor === 0) minors.append({x:-w, y:i, z:zp}).append({x:w, y:i, z:zp});
|
||||
}
|
||||
}
|
||||
gridView.add(makeLinesFromPoints(majors, colorMajor || 0x999999, 1));
|
||||
if (minors) gridView.add(makeLinesFromPoints(minors, colorMinor || 0xcccccc, 1));
|
||||
Space.scene.add(gridView);
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
refreshRequested = false;
|
||||
viewControl.update();
|
||||
}
|
||||
|
||||
/** deferred refresh that collapses multiple requests */
|
||||
function requestRefresh(timeout) {
|
||||
if (refreshRequested === false) {
|
||||
refreshRequested = true;
|
||||
setTimeout(refresh, timeout || 10);
|
||||
}
|
||||
}
|
||||
|
||||
function onResize() {
|
||||
camera.aspect = aspect();
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(width(), height());
|
||||
container.style.width = width();
|
||||
container.style.height = height();
|
||||
requestRefresh();
|
||||
}
|
||||
|
||||
function alignTracking(point, rot, out) {
|
||||
if (point && rot) {
|
||||
alignedTracking = true;
|
||||
trackPlane.position.set(point.x, point.y, point.z);
|
||||
trackPlane.rotation.set(rot.x, rot.y, rot.z);
|
||||
trackDelta = out;
|
||||
} else {
|
||||
alignedTracking = false;
|
||||
trackPlane.position.set(0, 0, 0);
|
||||
trackPlane.rotation.set(PI2, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function cca(c) {
|
||||
return c.charCodeAt(0);
|
||||
}
|
||||
|
||||
function inputHasFocus() {
|
||||
return DOC.activeElement && (DOC.activeElement != DOC.body);
|
||||
}
|
||||
|
||||
function keyHandler(evt) {
|
||||
if (!defaultKeys || inputHasFocus()) return false;
|
||||
if (evt.metaKey) return false;
|
||||
var handled = true;
|
||||
switch (evt.charCode) {
|
||||
case cca('z'):
|
||||
Space.view.reset();
|
||||
break;
|
||||
case cca('h'):
|
||||
Space.view.home();
|
||||
break;
|
||||
case cca('f'):
|
||||
Space.view.front();
|
||||
break;
|
||||
case cca('l'):
|
||||
Space.view.left();
|
||||
break;
|
||||
case cca('r'):
|
||||
Space.view.right();
|
||||
break;
|
||||
case cca('t'):
|
||||
Space.view.top();
|
||||
break;
|
||||
case cca('b'):
|
||||
Space.view.back();
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
if (handled) evt.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* ThreeJS Helper Functions
|
||||
******************************************************************* */
|
||||
|
||||
function makeLinesFromPoints(points, color, width) {
|
||||
if (points.length % 2 != 0) throw "invalid line : "+points.length;
|
||||
var geo = new THREE.Geometry(),
|
||||
i = 0, p1, p2, mesh;
|
||||
while (i < points.length) {
|
||||
p1 = points[i++];
|
||||
p2 = points[i++];
|
||||
geo.vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z));
|
||||
geo.vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z));
|
||||
}
|
||||
geo.verticesNeedUpdate = true;
|
||||
mesh = new THREE.LineSegments(geo, new THREE.LineBasicMaterial({
|
||||
color: color,
|
||||
linewidth: width || 1
|
||||
}));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
function intersect(objects, recurse) {
|
||||
var lookAt = new THREE.Vector3(mouse.x, mouse.y, 0.0).unproject(camera);
|
||||
var ray = new THREE.Raycaster(camera.position, lookAt.sub(camera.position).normalize());
|
||||
return ray.intersectObjects(objects, recurse);
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Mouse Functions
|
||||
******************************************************************* */
|
||||
|
||||
function onMouseDown(event) {
|
||||
if (event.target === renderer.domElement) {
|
||||
DOC.activeElement.blur();
|
||||
event.preventDefault();
|
||||
var selection = null,
|
||||
trackTo = alignedTracking ? trackPlane : platform;
|
||||
if (mouseDownSelect) selection = mouseDownSelect();
|
||||
if (selection && selection.length > 0) {
|
||||
trackTo.visible = true;
|
||||
var int = intersect(selection.slice().append(trackTo), false);
|
||||
trackTo.visible = false;
|
||||
if (int.length > 0) {
|
||||
var trackInt, selectInt;
|
||||
for (var i=0; i<int.length; i++) {
|
||||
if (!trackInt && int[i].object === trackTo) {
|
||||
trackInt = int[i];
|
||||
} else if (!selectInt && selection.contains(int[i].object)) {
|
||||
selectInt = int[i];
|
||||
}
|
||||
}
|
||||
if (trackInt && selectInt) {
|
||||
mouseDragPoint = trackInt.point.clone();
|
||||
mouseDragStart = mouseDragPoint;
|
||||
viewControl.enabled = false;
|
||||
mouseDownSelect(selectInt, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (platformClick) {
|
||||
var vis = platform.visible;
|
||||
platform.visible = true;
|
||||
int = intersect([platform], false);
|
||||
platform.visible = vis;
|
||||
platformClickAt = int && int.length > 0 ? int[0].point : null;
|
||||
}
|
||||
} else {
|
||||
viewControl.enabled = false;
|
||||
}
|
||||
mouseMoved = false;
|
||||
}
|
||||
|
||||
function onMouseUp(event) {
|
||||
if (!viewControl.enabled) {
|
||||
viewControl.enabled = true;
|
||||
viewControl.onMouseUp(event);
|
||||
}
|
||||
if (!mouseMoved) {
|
||||
event.preventDefault();
|
||||
if (!mouseMoved) {
|
||||
var refresh = false,
|
||||
selection = null;
|
||||
if (mouseUpSelect) selection = mouseUpSelect();
|
||||
if (selection && selection.length > 0) {
|
||||
var int = intersect(selection, selectRecurse);
|
||||
if (int.length > 0) {
|
||||
mouseUpSelect(int[0], event);
|
||||
refresh = true;
|
||||
} else {
|
||||
mouseUpSelect(null, event);
|
||||
}
|
||||
}
|
||||
if (!refresh && platformClickAt) {
|
||||
platformClick(platformClickAt);
|
||||
}
|
||||
if (refresh) requestRefresh();
|
||||
}
|
||||
} else if (mouseDrag && mouseDragStart) {
|
||||
mouseDrag(null,null,true);
|
||||
}
|
||||
mouseDragPoint = null;
|
||||
mouseDragStart = null;
|
||||
}
|
||||
|
||||
function onMouseMove(event) {
|
||||
var int, vis;
|
||||
if (viewControl.enabled) {
|
||||
event.preventDefault();
|
||||
var selection = mouseHover ? mouseHover() : null;
|
||||
if (selection && selection.length > 0) {
|
||||
int = intersect(selection, selectRecurse);
|
||||
if (int.length > 0) mouseHover(int[0], event);
|
||||
}
|
||||
if ((!int || int.length == 0) && platformHover) {
|
||||
vis = platform.visible;
|
||||
platform.visible = true;
|
||||
int = intersect([platform], false);
|
||||
platform.visible = vis;
|
||||
if (int && int.length > 0) platformHover(int[0].point);
|
||||
}
|
||||
} else if (mouseDragPoint && mouseDrag && mouseDrag()) {
|
||||
event.preventDefault();
|
||||
var trackTo = alignedTracking ? trackPlane : platform;
|
||||
trackTo.visible = true;
|
||||
int = intersect([trackTo], false);
|
||||
trackTo.visible = false;
|
||||
if (int.length > 0 && int[0].object === trackTo) {
|
||||
var delta = mouseDragPoint.clone().sub(int[0].point);
|
||||
var offset = mouseDragStart.clone().sub(int[0].point);
|
||||
mouseDragPoint = int[0].point;
|
||||
mouseDrag({x: -delta.x, y: delta.z}, offset.multiplyVectors(offset, trackDelta));
|
||||
requestRefresh();
|
||||
}
|
||||
}
|
||||
mouseMoved = true;
|
||||
mouse = {
|
||||
x: (event.clientX / width()) * 2 - 1,
|
||||
y: -(event.clientY / height()) * 2 + 1};
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Space Object
|
||||
******************************************************************* */
|
||||
|
||||
var Space = {
|
||||
alignTracking: alignTracking,
|
||||
addEventListener: addEventListener,
|
||||
addEventHandlers: addEventHandlers,
|
||||
onEnterKey: onEnterKey,
|
||||
onResize: onResize,
|
||||
update: requestRefresh,
|
||||
|
||||
showSkyGrid: function(b) { showSkyGrid = b },
|
||||
setSkyColor: function(c) { skyColor = c },
|
||||
setSkyGridColor: function(c) { skyGridColor = c },
|
||||
|
||||
scene: {
|
||||
add: function (o) {
|
||||
o.rotation.x = WORLD.rotation.x;
|
||||
return SCENE.add(o);
|
||||
},
|
||||
remove: function (o) {
|
||||
return SCENE.remove(o);
|
||||
}
|
||||
},
|
||||
|
||||
platform: {
|
||||
tweenTo: tweenPlatform,
|
||||
setSize: setPlatformSizeUpdateGrid,
|
||||
setColor: setPlatformColor,
|
||||
setGrid: setGrid,
|
||||
|
||||
add: function(o) { WORLD.add(o) },
|
||||
remove: function(o) { WORLD.remove(o) },
|
||||
setMaxZ: function(z) { panY = z / 2 },
|
||||
isHidden: function() { return !showPlatform },
|
||||
setHidden: function(b) { showPlatform = !b; platform.visible = !b },
|
||||
setHiding: function(b) { hidePlatformBelow = b },
|
||||
setZOff: function(z) { platformZOff = z; updatePlatformPosition() },
|
||||
setGZOff: function(z) { gridZOff = z; updatePlatformPosition() },
|
||||
opacity: function(o) { platform.material.opacity = o },
|
||||
|
||||
onMove: function(f) { platformOnMove = f },
|
||||
onHover: function(f) { platformHover = f },
|
||||
onClick: function(f) { platformClick = f},
|
||||
|
||||
size: function() { return platform.scale },
|
||||
isVisible: function() { return platform.visible },
|
||||
|
||||
showGrid: function(b) { gridView.visible = b }
|
||||
},
|
||||
|
||||
view: {
|
||||
top: function() { tweenCam({left: 0, up: 0, panX: 0, panY: panY, panZ: 0}) },
|
||||
back: function() { tweenCam({left: PI, up: PI2, panX: 0, panY: panY, panZ: 0}) },
|
||||
home: function() { tweenCam({left: 0, up: PI4, panX: 0, panY: panY, panZ: 0}) },
|
||||
front: function() { tweenCam({left: 0, up: PI2, panX: 0, panY: panY, panZ: 0}) },
|
||||
right: function() { tweenCam({left: PI2, up: PI2, panX: 0, panY: panY, panZ: 0}) },
|
||||
left: function() { tweenCam({left: -PI2, up: PI2, panX: 0, panY: panY, panZ: 0}) },
|
||||
|
||||
panTo: function(x,y,z) { tweenCamPan(x,y,z) },
|
||||
|
||||
setZoom: function(r,v) { viewControl.setZoom(r,v) },
|
||||
|
||||
reset: function() { viewControl.reset(); requestRefresh() },
|
||||
load: function(cam) { viewControl.setPosition(cam) },
|
||||
save: function() { return viewControl.getPosition(true) }
|
||||
},
|
||||
|
||||
mouse: {
|
||||
downSelect: function(f) { mouseDownSelect = f },
|
||||
upSelect: function(f) { mouseUpSelect = f },
|
||||
onDrag: function(f) { mouseDrag = f },
|
||||
onHover: function(f) { mouseHover = f }
|
||||
},
|
||||
|
||||
useDefaultKeys: function(b) {
|
||||
defaultKeys = b;
|
||||
},
|
||||
|
||||
selectRecurse: function(b) {
|
||||
selectRecurse = b;
|
||||
},
|
||||
|
||||
objects: function() {
|
||||
return WC;
|
||||
},
|
||||
|
||||
init: function(domelement, slider) {
|
||||
container = domelement;
|
||||
|
||||
WORLD.rotation.x = -PI2;
|
||||
SCENE.add(WORLD);
|
||||
|
||||
domelement.style.width = width();
|
||||
domelement.style.height = height();
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
camera = perspective ?
|
||||
new THREE.PerspectiveCamera(perspective, aspect(), 1, 100000) :
|
||||
new THREE.OrthographicCamera(-100 * aspect(), 100 * aspect(), 100, -100, 0.1, 100000);
|
||||
|
||||
camera.position.set(0, 200, 340);
|
||||
renderer.setSize(width(), height());
|
||||
domelement.appendChild(renderer.domElement);
|
||||
|
||||
viewControl = new THREE.CubeControls(camera, domelement, function (position, moved) {
|
||||
if (platform) platform.visible = hidePlatformBelow ? initialized && position.y >= 0 && showPlatform : showPlatform;
|
||||
if (trackcam) trackcam.position.copy(camera.position);
|
||||
renderer.render(SCENE, camera);
|
||||
if (moved && platformOnMove) {
|
||||
if (platformMoveTimer) clearTimeout(platformMoveTimer);
|
||||
platformMoveTimer = setTimeout(platformOnMove, 500);
|
||||
}
|
||||
}, slider);
|
||||
|
||||
viewControl.noKeys = true;
|
||||
viewControl.maxDistance = 1000;
|
||||
|
||||
SCENE.add(new THREE.AmbientLight(0x707070));
|
||||
|
||||
light1 = addLight( 200, 250, 200, lightIntensity * 1.15);
|
||||
light2 = addLight(-200, 250, -200, lightIntensity * 0.95);
|
||||
light3 = addLight( 0, -200, 0, lightIntensity * 0.5);
|
||||
light4 = addLight( 200, 5, -200, lightIntensity * 0.35);
|
||||
light5 = addLight(-200, 5, 200, lightIntensity * 0.4);
|
||||
|
||||
platform = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(1, 1, 1),
|
||||
new THREE.MeshPhongMaterial({
|
||||
color: 0xcccccc,
|
||||
specular: 0xcccccc,
|
||||
shininess: 5,
|
||||
transparent: true,
|
||||
opacity: 0.6,
|
||||
side: THREE.DoubleSide
|
||||
})
|
||||
);
|
||||
|
||||
platform.position.y = platformZOff;
|
||||
platform.rotation.x = -PI2;
|
||||
platform.visible = showPlatform;
|
||||
|
||||
trackPlane = new THREE.Mesh(
|
||||
new THREE.PlaneBufferGeometry(2000, 2000, 1, 1),
|
||||
new THREE.MeshBasicMaterial( { color: 0x777777, opacity: 0.3, transparent: false, side:THREE.DoubleSide } )
|
||||
);
|
||||
trackPlane.visible = false;
|
||||
trackPlane.rotation.x = PI2;
|
||||
|
||||
var sky = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(50000, 50000, 50000, 1, 1, 1),
|
||||
new THREE.MeshBasicMaterial({ color: skyColor, side: THREE.DoubleSide })
|
||||
),
|
||||
skygrid = new THREE.Mesh(
|
||||
new THREE.BoxGeometry(5000, 5000, 5000, 10, 10, 10),
|
||||
new THREE.MeshBasicMaterial({ color: skyGridColor, side: THREE.DoubleSide })
|
||||
);
|
||||
|
||||
|
||||
SCENE.add(platform);
|
||||
SCENE.add(trackPlane);
|
||||
SCENE.add(sky);
|
||||
|
||||
if (showSkyGrid) {
|
||||
skygrid.material.wireframe = true;
|
||||
SCENE.add(skygrid);
|
||||
}
|
||||
|
||||
addEventHandlers(WIN, [
|
||||
'resize', onResize,
|
||||
'mousemove', onMouseMove,
|
||||
'mousedown', onMouseDown,
|
||||
'mouseup', onMouseUp,
|
||||
'keypress', keyHandler
|
||||
]);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
};
|
||||
|
||||
/** ******************************************************************
|
||||
* Connect to moto
|
||||
******************************************************************* */
|
||||
|
||||
if (!window.moto) window.moto = {};
|
||||
window.moto.Space = Space;
|
||||
|
||||
})();
|
||||
323
js/moto-ui.js
Normal file
323
js/moto-ui.js
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
"use strict";
|
||||
|
||||
var gs_moto_ui = {
|
||||
copyright:"stewart allen <stewart@neuron.com> -- all rights reserved"
|
||||
};
|
||||
|
||||
(function() {
|
||||
|
||||
var moto = self.moto = self.moto || {};
|
||||
if (moto.ui) return;
|
||||
|
||||
var SELF = self,
|
||||
lastGroup = null,
|
||||
lastDiv = null,
|
||||
hideAction = null,
|
||||
inputAction = null,
|
||||
hasModes = [],
|
||||
SDB = moto.KV,
|
||||
DOC = SELF.document,
|
||||
prefix = "tab";
|
||||
|
||||
SELF.$ = (SELF.$ || function (id) { return DOC.getElementById(id) } );
|
||||
|
||||
moto.ui = {
|
||||
prefix: function(pre) { prefix = pre; return moto.ui },
|
||||
hideAction: function(fn) { hideAction = fn; return moto.ui },
|
||||
inputAction: function(fn) { inputAction = fn; return moto.ui },
|
||||
setMode: setMode,
|
||||
bound: bound,
|
||||
toInt: toInt,
|
||||
toFloat: toFloat,
|
||||
newLabel: newLabel,
|
||||
newRange: newRangeField,
|
||||
newInput: newInputField,
|
||||
newButton: newButton,
|
||||
newBoolean: newBooleanField,
|
||||
newSelectField: newSelectField,
|
||||
newTable: newTables,
|
||||
newTableRow: newTableRow,
|
||||
newRow: newRow,
|
||||
newBlank: newBlank,
|
||||
newGroup: newGroup,
|
||||
setGroup: setGroup
|
||||
};
|
||||
|
||||
function setMode(mode) {
|
||||
hasModes.forEach(function(div) {
|
||||
div.setMode(mode);
|
||||
});
|
||||
}
|
||||
|
||||
function isControlGroupVisible(label, key) {
|
||||
var ls = SDB.getItem(key),
|
||||
dv = true;
|
||||
return ls ? ls !== 'false' : dv !== undefined ? dv : true;
|
||||
}
|
||||
|
||||
function setGroup(div) {
|
||||
lastDiv = div;
|
||||
return div;
|
||||
}
|
||||
|
||||
function newGroup(label, div, options) {
|
||||
lastDiv = div || lastDiv;
|
||||
return addCollapsableGroup(label, lastDiv, options);
|
||||
}
|
||||
|
||||
function addCollapsableGroup(label, div, options) {
|
||||
var row = DOC.createElement('div'),
|
||||
a = DOC.createElement('a'),
|
||||
dbkey = prefix+'-show-'+label;
|
||||
|
||||
lastGroup = "ck_"+label;
|
||||
div.appendChild(row);
|
||||
row.setAttribute("class", "grouphead noselect");
|
||||
row.setAttribute("id", lastGroup);
|
||||
row.appendChild(a);
|
||||
a.appendChild(DOC.createTextNode(label));
|
||||
a.setAttribute("ck", lastGroup);
|
||||
a.setAttribute("id", lastGroup+"_label");
|
||||
addModeControls(row, options);
|
||||
return row;
|
||||
}
|
||||
|
||||
function toInt() {
|
||||
var nv = this.value !== '' ? parseInt(this.value) : null;
|
||||
if (nv !== null && this.bound) nv = this.bound(nv);
|
||||
this.value = nv;
|
||||
return nv;
|
||||
}
|
||||
|
||||
function toFloat() {
|
||||
var nv = this.value !== '' ? parseFloat(this.value) : null;
|
||||
if (nv !== null && this.bound) nv = this.bound(nv);
|
||||
this.value = nv;
|
||||
return nv;
|
||||
}
|
||||
|
||||
function bound(low,high) {
|
||||
return function(v) {
|
||||
return v < low ? low : v > high ? high : v;
|
||||
};
|
||||
}
|
||||
|
||||
function raw() {
|
||||
return this.value !== '' ? this.value : null;
|
||||
}
|
||||
|
||||
function newLabel(text) {
|
||||
var label = DOC.createElement('label');
|
||||
label.appendChild(DOC.createTextNode(text));
|
||||
label.setAttribute("class", "noselect");
|
||||
return label;
|
||||
}
|
||||
|
||||
function addId(el, options) {
|
||||
if (options && options.id) {
|
||||
el.setAttribute("id", options.id);
|
||||
}
|
||||
}
|
||||
|
||||
function addModeControls(el, options) {
|
||||
el.__show = true;
|
||||
el.__modeSave = null;
|
||||
el.showMe = function() {
|
||||
if (el.__show) return;
|
||||
el.style.display = el.__modeSave;
|
||||
el.__show = true;
|
||||
el.__modeSave = null;
|
||||
};
|
||||
el.hideMe = function() {
|
||||
if (!el.__show) return;
|
||||
el.__show = false;
|
||||
el.__modeSave = el.style.display;
|
||||
el.style.display = 'none';
|
||||
};
|
||||
el.setVisible = function(show) {
|
||||
if (show) el.showMe();
|
||||
else el.hideMe();
|
||||
};
|
||||
el.setMode = function(mode) {
|
||||
el.setVisible(el.modes.contains(mode));
|
||||
}
|
||||
el.hasMode = function(mode) {
|
||||
return el.modes.contains(mode);
|
||||
}
|
||||
if (options && options.modes) {
|
||||
el.modes = options.modes;
|
||||
hasModes.push(el);
|
||||
}
|
||||
}
|
||||
|
||||
function newDiv(options) {
|
||||
var div = DOC.createElement('div');
|
||||
addModeControls(div, options);
|
||||
return div;
|
||||
}
|
||||
|
||||
function newInputField(label, options) {
|
||||
var row = newDiv(options),
|
||||
hide = options && options.hide,
|
||||
size = options ? options.size || 5 : 5,
|
||||
height = options ? options.height : 0,
|
||||
ip = height > 1 ? DOC.createElement('textarea') : DOC.createElement('input'),
|
||||
action = inputAction;
|
||||
lastDiv.appendChild(row);
|
||||
row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", ["flow-row",lastGroup].join(" "));
|
||||
if (height > 1) {
|
||||
ip.setAttribute("cols", size);
|
||||
ip.setAttribute("rows", height);
|
||||
ip.setAttribute("wrap", "off");
|
||||
} else {
|
||||
ip.setAttribute("size", size);
|
||||
}
|
||||
ip.setAttribute("type", "text");
|
||||
row.style.display = hide ? 'none' : '';
|
||||
if (options) {
|
||||
if (options.disabled) ip.setAttribute("disabled", "true");
|
||||
if (options.title) row.setAttribute("title", options.title);
|
||||
if (options.convert) ip.convert = options.convert.bind(ip);
|
||||
if (options.bound) ip.bound = options.bound;
|
||||
if (options.action) action = options.action;
|
||||
}
|
||||
if (action) {
|
||||
ip.addEventListener('keyup', function(event) {
|
||||
if (event.keyCode === 13) action(event);
|
||||
});
|
||||
ip.addEventListener('blur', function(event) {
|
||||
action(event);
|
||||
});
|
||||
}
|
||||
if (!ip.convert) ip.convert = raw.bind(ip);
|
||||
ip.setVisible = row.setVisible;
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newRangeField(label, options) {
|
||||
var row = newDiv(options),
|
||||
ip = DOC.createElement('input'),
|
||||
hide = options && options.hide,
|
||||
action = inputAction;
|
||||
lastDiv.appendChild(row);
|
||||
if (label) row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", ["flow-row",lastGroup].join(" "));
|
||||
ip.setAttribute("type", "range");
|
||||
ip.setAttribute("min", (options && options.min ? options.min : 0));
|
||||
ip.setAttribute("max", (options && options.max ? options.max : 100));
|
||||
ip.setAttribute("value", 0);
|
||||
row.style.display = hide ? 'none' : '';
|
||||
if (options) {
|
||||
if (options.title) {
|
||||
ip.setAttribute("title", options.title);
|
||||
row.setAttribute("title", options.title);
|
||||
}
|
||||
if (options.action) action = options.action;
|
||||
}
|
||||
ip.setVisible = row.setVisible;
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newSelectField(label, options) {
|
||||
var row = newDiv(options),
|
||||
ip = DOC.createElement('select'),
|
||||
hide = options && options.hide,
|
||||
action = inputAction;
|
||||
lastDiv.appendChild(row);
|
||||
row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", ["flow-row",lastGroup].join(" "));
|
||||
row.style.display = hide ? 'none' : '';
|
||||
if (options) {
|
||||
if (options.convert) ip.convert = options.convert.bind(ip);
|
||||
if (options.disabled) ip.setAttribute("disabled", "true");
|
||||
if (options.title) row.setAttribute("title", options.title);
|
||||
if (options.action) action = options.action;
|
||||
}
|
||||
ip.onchange = function() { action() };
|
||||
ip.setVisible = row.setVisible;
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newBooleanField(label, action, options) {
|
||||
var row = newDiv(options),
|
||||
ip = DOC.createElement('input'),
|
||||
hide = options && options.hide;
|
||||
lastDiv.appendChild(row);
|
||||
if (label) row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", ["flow-row",lastGroup].join(" "));
|
||||
row.style.display = hide ? 'none' : '';
|
||||
ip.setAttribute("type", "checkbox");
|
||||
ip.checked = false;
|
||||
if (options) {
|
||||
if (options.disabled) ip.setAttribute("disabled", "true");
|
||||
if (options.title) {
|
||||
ip.setAttribute("title", options.title);
|
||||
row.setAttribute("title", options.title);
|
||||
}
|
||||
}
|
||||
if (action) ip.onclick = function() { action() };
|
||||
ip.setVisible = row.setVisible;
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newBlank(options) {
|
||||
var row = newDiv(options),
|
||||
hide = options && options.hide;
|
||||
lastDiv.appendChild(row);
|
||||
row.setAttribute("class", ["flow-row",lastGroup].join(" "));
|
||||
row.style.display = hide ? 'none' : '';
|
||||
ip.setVisible = row.setVisible;
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newButton(label, action, options) {
|
||||
var b = DOC.createElement('button'),
|
||||
t = DOC.createTextNode(label);
|
||||
b.appendChild(t);
|
||||
b.onclick = function() { action() };
|
||||
addModeControls(b, options);
|
||||
addId(b, options);
|
||||
return b;
|
||||
}
|
||||
|
||||
function newTableRow(arrayOfArrays, options) {
|
||||
return newRow(newTables(arrayOfArrays), options);
|
||||
}
|
||||
|
||||
function newTables(arrayOfArrays) {
|
||||
var array = [];
|
||||
for (var i=0; i<arrayOfArrays.length; i++) {
|
||||
array.push(newRowTable(arrayOfArrays[i]));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
function newRowTable(array) {
|
||||
var div = DOC.createElement('div');
|
||||
div.setAttribute("class", "tablerow");
|
||||
array.forEach(function(c) {
|
||||
div.appendChild(c);
|
||||
});
|
||||
return div;
|
||||
}
|
||||
|
||||
function newRow(children, options) {
|
||||
var row = addCollapsableElement((options && options.noadd) ? null : lastDiv);
|
||||
if (children) children.forEach(function (c) { row.appendChild(c) });
|
||||
addModeControls(row, options);
|
||||
return row;
|
||||
}
|
||||
|
||||
function addCollapsableElement(parent) {
|
||||
var row = DOC.createElement('div');
|
||||
if (parent) parent.appendChild(row);
|
||||
if (lastGroup) row.setAttribute("class", lastGroup);
|
||||
return row;
|
||||
}
|
||||
|
||||
})();
|
||||
1006
js/web-server.js
Normal file
1006
js/web-server.js
Normal file
File diff suppressed because it is too large
Load diff
8
license.md
Normal file
8
license.md
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
Copyright (C) Stewart Allen <stewart@neuron.com> All Rights Reserved
|
||||
|
||||
Unauthorized copying, modification and re-distribution are
|
||||
strictly prohibited without prior written consent.
|
||||
|
||||
This public repository exists to permit inspection by parties interested
|
||||
in obtaining a licensing to the code or individual persons wishing to run
|
||||
host-local instances for personal use only.
|
||||
112
notes.md
Normal file
112
notes.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
--( global )--
|
||||
|
||||
* prevent text selection of non-input
|
||||
* widget general add-ons (fdm supports, cam tabs)
|
||||
* bail on decimation if it's proving ineffective
|
||||
* improve decimation speed by avoiding in/out of Point
|
||||
* dismissable transient message/alert
|
||||
* modal non-alert-based dialog
|
||||
* modal spinner
|
||||
* ability to cancel slice operations!
|
||||
* server-side processing
|
||||
* saving workspace doesn't preserve widget positions
|
||||
* move more kiri code (like printing) into modules like serial
|
||||
* include device profile name in exported gcode comments
|
||||
* when deleting last selected part, turn off bottom param editor
|
||||
* frame api * https://plus.google.com/u/0/+JakobFlierl/posts/hn6eirr6fXC
|
||||
* refactor / simplify POLY.expand (put onus on collector)
|
||||
* add simple solid (tube-like) rendering in place of lines
|
||||
* extend mesh object to store raw + annotations (rot,scale,pos), share raw data w/ dups, encode/decode
|
||||
* cloned objects share same slices data unless rotated
|
||||
* remember object's original position/orientation for reset/multi-object import alignment
|
||||
* gcode import break up "layers" on z move with no x/y move
|
||||
|
||||
--( onshape )--
|
||||
|
||||
* popup warning when detect 3rd party storage blocked (chrome)
|
||||
* watch for changed part version to prompt re-import
|
||||
* re-used disk cached version of parts if not changed
|
||||
* remap mouse/kbd to match onshape when running inside?
|
||||
* assembly import
|
||||
|
||||
--( cam )--
|
||||
|
||||
* ease-in and ease-out especially on tab cut-out start/stop
|
||||
* import options: unify bodies.
|
||||
* milling order option: by operation or by part
|
||||
* store tab and camshell polys in widget.topo to minimize z on edge moves
|
||||
* trimming linear finishing to tabs
|
||||
* improve 'clockwise' setting to take into account spindle direction, etc
|
||||
* linear finishing cutting out tabs
|
||||
* linear finishing going back to z top too often
|
||||
* fix ease down and re-enable
|
||||
* warn when part > stock or cuts go outside bed
|
||||
* uncheck origin center for carvey
|
||||
* option to skip milling holes that would be drilled
|
||||
* sender speed control slider (0%-200%) ?
|
||||
* add M03 tool feedrate support (https://forum.grid.space/index.php?p=/discussion/14/s-parameter#latest)
|
||||
* fails in pancaking (clone) when there are no sliced layers (like z bottom too high)
|
||||
* crossing open space check point is outside camshell before returning max z
|
||||
* compensate for leave-stock in outside roughing (w/ tabs)
|
||||
* fix zooming, workspace thickness for larger workspaces
|
||||
* only show toolchange alert/pause after the first M6
|
||||
* raise z by leave-stock in roughing? if so, see next
|
||||
* if (raise z) above, add clear-flats to finishing
|
||||
* revisit tabs - just cut polys instead
|
||||
* try chunking topo until smaller blocks for processing
|
||||
* linear x/y scan overflow (y) w/ topo model
|
||||
* linear x/y not obeying inset from pocket only
|
||||
* check normals for downward facing facets. mark top for slice skirt/pancake
|
||||
* detect holes thru bottom and cutout outline vs mill out entire void
|
||||
|
||||
--( fdm )--
|
||||
|
||||
* implement gyroid infill * https://en.wikipedia.org/wiki/Gyroid
|
||||
* fan / layer control * update forum
|
||||
* infill rendering as moves instead of extrusions (firefox?)
|
||||
* run line through center of series of short fills (thin fill optimization)
|
||||
* add rafts, thin wall detection, manual supports
|
||||
* add skirt to raft option as a simpler way to do rafts
|
||||
* fill before shell option (request) ?
|
||||
* separate shell speed control
|
||||
* wipe on infill should follow the closest enclosing shell poly
|
||||
* TAZ from https://code.alephobjects.com/diffusion/P/browse/master/cura/TAZ_flexy_dually_v2/PLA-PVA-support_medium-quality_TAZ_FlexyDually-v2_0.6noz_cura.ini
|
||||
* add control of shortest line/fill line before culling
|
||||
* add retraction distance/speed to gcode profiles
|
||||
* add min layer time (slowdown or cool-off wait)
|
||||
* tops should print inside/out (add odds w/ poly2poly ...)
|
||||
* extend support to interior spaces when 0% infill?
|
||||
* fix multiple part layout export offset (resend position @ print time)
|
||||
* first/last/outline/finish speed settings
|
||||
* check for support / brim intersections on first layer
|
||||
* bottom layer of a bridge: underextrude/stretch?
|
||||
* dual extruder support
|
||||
|
||||
--( laser )--
|
||||
|
||||
* overcuts, radii for drag knives
|
||||
* sla :: svg modified from http://garyhodgson.github.io/slic3rsvgviewer/?file=examples/belt_pulley3.svg
|
||||
|
||||
--( reference )--
|
||||
|
||||
* shader examples to enable object-clipping
|
||||
* -----
|
||||
* http://jsfiddle.net/LK84y/9/
|
||||
* http://www.html5rocks.com/en/tutorials/webgl/shaders/
|
||||
|
||||
* more reading
|
||||
* -----
|
||||
* http://lcamtuf.coredump.cx/gcnc/full/
|
||||
* http://www.tcs.fudan.edu.cn/rudolf/Courses/Algorithms/Alg_cs_07w/Webprojects/Zhaobo_hull/
|
||||
* https://en.wikipedia.org/wiki/Graham_scan
|
||||
* http://www.cambam.info/doc/0.9.7/cam/Pocket.aspx
|
||||
* http://hackaday.com/2016/01/22/pack-your-plywood-cuts-with-genetic-algortihms/
|
||||
* http://wiki.imal.org/howto/cnc-milling-introduction-cutting-tools
|
||||
* http://www.twak.co.uk/2011/01/degeneracy-in-weighted-straight.html
|
||||
|
||||
* grbl & universal json serial port sender
|
||||
* -----
|
||||
* https://github.com/johnlauer/serial-port-json-server
|
||||
* https://github.com/grbl/grbl/wiki/Interfacing-with-Grbl
|
||||
* https://github.com/synthetos/TinyG/wiki/TinyG-Command-Line
|
||||
* https://github.com/synthetos/TinyG/wiki/Tinyg-Communications-Programming
|
||||
31
package.json
Normal file
31
package.json
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "gridspace",
|
||||
"version": "1.0.0",
|
||||
"description": "gridspace 3d slice & modeling tools",
|
||||
"author": "Stewart Allen <stewart.allen@gmail.com>",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/stewartoallen/gridspace.git"
|
||||
},
|
||||
"keywords": ["gridspace", "kiri", "kirimoto", "3d", "slicer", "CAM", "laser", "gcode"],
|
||||
"scripts": {
|
||||
"start": "node js/web-server debug",
|
||||
"start-web": "node js/web-server nolocal"
|
||||
},
|
||||
"dependencies": {
|
||||
"connect": "3.3.x",
|
||||
"compression": "1.4.x",
|
||||
"serve-static": "1.10.x",
|
||||
"n3d-threejs": "72.0.x",
|
||||
"tween.js": "0.14.x",
|
||||
"uglify-js": "2.7.x",
|
||||
"leveldown": "latest",
|
||||
"levelup": "latest",
|
||||
"validator": "4.2.x",
|
||||
"pixi.js": "3.0.x",
|
||||
"moment": "2.11.x",
|
||||
"request": "2.67.x",
|
||||
"express-useragent": "0.2.x"
|
||||
}
|
||||
}
|
||||
21
readme.md
Normal file
21
readme.md
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# KiriMoto
|
||||
|
||||
`KiriMoto` is a unique multi-modal, extensible slicer, gcode generation framework
|
||||
|
||||
## Getting Started
|
||||
|
||||
```
|
||||
npm update
|
||||
npm start
|
||||
```
|
||||
|
||||
to start a local testing instance of KiriMoto on port 8080
|
||||
|
||||
## Other Start Options
|
||||
|
||||
```
|
||||
npm run-script start-web
|
||||
```
|
||||
serves code as obfuscated, compressed bundles. this is the mode used to run on a public
|
||||
web site, so you can't use "localhost" to test. to accomodate this, alias "debug" to 127.0.0.1
|
||||
then access KiriMoto on http://debug:8080
|
||||
BIN
web/kiri/favicon-mobile.png
Normal file
BIN
web/kiri/favicon-mobile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6 KiB |
BIN
web/kiri/favicon.ico
Normal file
BIN
web/kiri/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
22
web/kiri/filter/CAM/Any.Generic.Grbl
Normal file
22
web/kiri/filter/CAM/Any.Generic.Grbl
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"file-ext": "nc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)"
|
||||
],
|
||||
"post":[
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"bed_width": 400,
|
||||
"bed_depth": 400
|
||||
}
|
||||
}
|
||||
24
web/kiri/filter/CAM/Any.Generic.LinuxCNC
Normal file
24
web/kiri/filter/CAM/Any.Generic.LinuxCNC
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"file-ext": "ngc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)",
|
||||
"M03 S20000 ; spindle on"
|
||||
],
|
||||
"post":[
|
||||
"M05 ; spindle off",
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"bed_width": 400,
|
||||
"bed_depth": 400
|
||||
}
|
||||
}
|
||||
1
web/kiri/filter/CAM/Any.Generic.TinyG
Symbolic link
1
web/kiri/filter/CAM/Any.Generic.TinyG
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
Any.Generic.Grbl
|
||||
22
web/kiri/filter/CAM/Carbide3D.Nomad.883Pro
Normal file
22
web/kiri/filter/CAM/Carbide3D.Nomad.883Pro
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"file-ext": "nc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)"
|
||||
],
|
||||
"post":[
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"bed_width": 200,
|
||||
"bed_depth": 200
|
||||
}
|
||||
}
|
||||
22
web/kiri/filter/CAM/Carbide3D.Shapeoko.3
Normal file
22
web/kiri/filter/CAM/Carbide3D.Shapeoko.3
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"file-ext": "nc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)"
|
||||
],
|
||||
"post":[
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"bed_width": 400,
|
||||
"bed_depth": 400
|
||||
}
|
||||
}
|
||||
22
web/kiri/filter/CAM/Carbide3D.Shapeoko.XL
Normal file
22
web/kiri/filter/CAM/Carbide3D.Shapeoko.XL
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"file-ext": "nc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)"
|
||||
],
|
||||
"post":[
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"bed_width": 830,
|
||||
"bed_depth": 400
|
||||
}
|
||||
}
|
||||
22
web/kiri/filter/CAM/Carbide3D.Shapeoko.XXL
Normal file
22
web/kiri/filter/CAM/Carbide3D.Shapeoko.XXL
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"file-ext": "nc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)"
|
||||
],
|
||||
"post":[
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"bed_width": 830,
|
||||
"bed_depth": 830
|
||||
}
|
||||
}
|
||||
24
web/kiri/filter/CAM/Inventables.Carvey.Any
Normal file
24
web/kiri/filter/CAM/Inventables.Carvey.Any
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"file-ext": "nc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)"
|
||||
],
|
||||
"post":[
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"origin_center": false,
|
||||
"spindle_max": 12000,
|
||||
"bed_width": 290,
|
||||
"bed_depth": 200
|
||||
}
|
||||
}
|
||||
22
web/kiri/filter/CAM/Inventables.XCarve.1000mm
Normal file
22
web/kiri/filter/CAM/Inventables.XCarve.1000mm
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"file-ext": "nc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)"
|
||||
],
|
||||
"post":[
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"bed_width": 1000,
|
||||
"bed_depth": 1000
|
||||
}
|
||||
}
|
||||
22
web/kiri/filter/CAM/Inventables.XCarve.500mm
Normal file
22
web/kiri/filter/CAM/Inventables.XCarve.500mm
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"file-ext": "nc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)"
|
||||
],
|
||||
"post":[
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"bed_width": 500,
|
||||
"bed_depth": 500
|
||||
}
|
||||
}
|
||||
22
web/kiri/filter/CAM/Sienci.Mill.One
Normal file
22
web/kiri/filter/CAM/Sienci.Mill.One
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"file-ext": "nc",
|
||||
"token-space": " ",
|
||||
"strip-comments": true,
|
||||
"pre":[
|
||||
"G21 ; set units to MM (required)",
|
||||
"G90 ; absolute position mode (required)"
|
||||
],
|
||||
"post":[
|
||||
"M30 ; program end"
|
||||
],
|
||||
"tool-change":[
|
||||
"M6 T{tool} ; change tool to '{tool_name}'"
|
||||
],
|
||||
"dwell":[
|
||||
"G4 P{time} ; dwell for {time}ms"
|
||||
],
|
||||
"settings": {
|
||||
"bed_width": 235,
|
||||
"bed_depth": 185
|
||||
}
|
||||
}
|
||||
34
web/kiri/filter/FDM/Any.Generic.Marlin
Normal file
34
web/kiri/filter/FDM/Any.Generic.Marlin
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"M140 S{bed_temp} T0 ; set bed temperature",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"G28 ; home axes",
|
||||
"G29 ; level bed",
|
||||
"G92 X0 Y0 Z0 E0 ; reset all axes positions",
|
||||
"G1 X0 Y0 Z0.25 F180 ; move xy to 0,0 and z 0.25mm over bed",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M190 S{bed_temp} T0 ; wait for bed to reach target temp",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 E15 F200 ; purge 15mm from extruder",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 F225 ; set feed speed"
|
||||
],
|
||||
"post":[
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M104 S0 T1 ; turn off left extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"G1 Z{z_max} F1200 ; drop bed",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false
|
||||
}
|
||||
}
|
||||
1
web/kiri/filter/FDM/Any.Generic.Sailfish
Symbolic link
1
web/kiri/filter/FDM/Any.Generic.Sailfish
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
Any.Generic.Marlin
|
||||
26
web/kiri/filter/FDM/Atom.v2
Normal file
26
web/kiri/filter/FDM/Atom.v2
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} ; set extruder temperature",
|
||||
"M140 S{bed_temp} ; set bed temperature",
|
||||
"G28 ; Home",
|
||||
"G1 F5000 Z50 ; lift nozzle",
|
||||
"G0 X0 Y-100 Z50 ; parking",
|
||||
"G0 Z20 ; parking",
|
||||
"G92 E0 ; zero the extruded length",
|
||||
"G1 F200 E3 ; extrude 3mm of feed stock"
|
||||
],
|
||||
"post":[
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 ; turn off right extruder",
|
||||
"M140 S0 ; turn off bed"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": true,
|
||||
"bed_width": 210,
|
||||
"bed_depth": 210,
|
||||
"build_height": 320
|
||||
}
|
||||
}
|
||||
30
web/kiri/filter/FDM/DeltaMaker.2T
Normal file
30
web/kiri/filter/FDM/DeltaMaker.2T
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"G21 ; Make sure we're in metric mode",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"G28 ; home axes",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 F200 E3.5 ; set feed speed, prime nozzle",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 Z30 ; position near the bed before first position"
|
||||
],
|
||||
"post":[
|
||||
"G28 ; home axes",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"nozzle_size": 0.5,
|
||||
"origin_center": true,
|
||||
"bed_width": 240,
|
||||
"bed_depth": 240,
|
||||
"build_height": 480
|
||||
}
|
||||
}
|
||||
30
web/kiri/filter/FDM/DeltaMaker.Original
Normal file
30
web/kiri/filter/FDM/DeltaMaker.Original
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"G21 ; Make sure we're in metric mode",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"G28 ; home axes",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 F200 E3.5 ; set feed speed, prime nozzle",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 Z30 ; position near the bed before first position"
|
||||
],
|
||||
"post":[
|
||||
"G28 ; home axes",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"nozzle_size": 0.35,
|
||||
"origin_center": true,
|
||||
"bed_width": 240,
|
||||
"bed_depth": 240,
|
||||
"build_height": 260
|
||||
}
|
||||
}
|
||||
36
web/kiri/filter/FDM/Leapfrog.CreatrHS
Normal file
36
web/kiri/filter/FDM/Leapfrog.CreatrHS
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"M140 S{bed_temp} T0 ; set bed temperature",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"G28 Z0 ; home Z axis",
|
||||
"G92 X0 Y0 Z0 E0 ; reset all axes positions",
|
||||
"G1 Z0.25 F180 ; move z to 0.25mm over bed",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 E15 F300 ; purge 15mm from extruder",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 F225 ; set feed speed"
|
||||
],
|
||||
"post":[
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M104 S0 T1 ; turn off left extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"G1 Z{z_max} F1200 ; drop bed",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false,
|
||||
"bed_width": 270,
|
||||
"bed_depth": 280,
|
||||
"build_height": 180
|
||||
}
|
||||
}
|
||||
38
web/kiri/filter/FDM/MakerGear.M2
Normal file
38
web/kiri/filter/FDM/MakerGear.M2
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"pre":[
|
||||
"M108 S255 ; set extruder speed",
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"M140 S{bed_temp} T0 ; set bed temperature",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"G28 XY ; home XY axes",
|
||||
"G28 Z ; home Z axis",
|
||||
"G92 X0 Y0 Z0 E0 ; reset all axes positions",
|
||||
"G1 Z0.25 F180 ; move z to 0.25mm over bed",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 E15 F300 ; purge 15mm from extruder",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 F225 ; set feed speed"
|
||||
],
|
||||
"post":[
|
||||
"M108 S0 ; set extruder speed",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M104 S0 T1 ; turn off left extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"G1 Z200 F1200 ; drop bed",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false,
|
||||
"bed_width": 200,
|
||||
"bed_depth": 250,
|
||||
"build_height": 200
|
||||
}
|
||||
}
|
||||
44
web/kiri/filter/FDM/Makerbot.Replicator2
Normal file
44
web/kiri/filter/FDM/Makerbot.Replicator2
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"pre":[
|
||||
"M73 P0 ; set progress to 0%",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"M104 S{temp} T0 ; set extruder temperature for T0 (tool 0)",
|
||||
"M127 ; close extruder valve (if it has one)",
|
||||
"G162 X Y F3000 ; move X,Y axis to maximum position at speed 3000 (top/right of table)",
|
||||
"G161 Z F1200 ; move Z axis to minimum position as speed 1200 (bottom)",
|
||||
"G92 Z-5 ; set Z axis value to -5 (no move)",
|
||||
"G1 Z0 ; move Z axis to 0 (lifting it 5mm off endstop)",
|
||||
"G161 Z F100 ; move Z axis to minimum position at speed 100",
|
||||
"M132 X Y Z A B ; load EEPROM home offsets for all axis",
|
||||
"G1 X{left} Y{bottom+10} Z30 F9000 ; move to wait position off table",
|
||||
"G130 X20 Y20 Z20 A20 B20 ; lower stepper Vrefs to give more juice to the extruder heater",
|
||||
"M133 T0 ; wait for extruder T0 temperature to stabilize",
|
||||
"G130 X127 Y127 Z40 A127 B127 ; default stepper Vrefs now that extruder is hot",
|
||||
"G92 A0 ; set extruder position value to 0 (no move)",
|
||||
"G1 Z0.4 ; move nozzle just above the bed height",
|
||||
"G1 E25 F300 ; purge extruder for 25mm at speed 300",
|
||||
"G1 X{left+10} Y{bottom+10} Z0.15 F1200 ; slow wipe",
|
||||
"G1 X{left+15} Y{bottom+15} Z0.5 F1200 ; lift",
|
||||
"G92 A0 ; set extruder position value to 0 (no move)",
|
||||
"M135 T0 ; set toolhead 0"
|
||||
],
|
||||
"post":[
|
||||
"M127 ; stop filament cooling fan",
|
||||
"M73 P100 ; set build progress to 100%",
|
||||
"G1 Z150 F1000 ; send Z axis to bottom (engaging end stop)",
|
||||
"G162 X Y F3000 ; move X,Y axis to maximum position at speed 3000 (top/right of table)",
|
||||
"M18 ; disable all stepper motors",
|
||||
"M72 P1 ; play song 1 (tada)"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M126 S{fan_speed}",
|
||||
"progress": "M73 P{progress}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": true,
|
||||
"bed_width": 300,
|
||||
"bed_depth": 175,
|
||||
"build_height": 150
|
||||
}
|
||||
}
|
||||
37
web/kiri/filter/FDM/TypeA.Series1
Normal file
37
web/kiri/filter/FDM/TypeA.Series1
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"pre":[
|
||||
"G1 Z15.0 F12000 ; move the platform down 15mm",
|
||||
"G1 X150 Y5 F12000 ; center extruder on X",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M83 ; use relative positioning for extruder",
|
||||
"M104 S{temp} ; set extruder temperature",
|
||||
"G21 ; use metric values",
|
||||
"G90 ; use absolute positioning mode",
|
||||
"G28 ; move to endstops",
|
||||
"G29 ; perform auto-levelling",
|
||||
"M109 S{temp} ; wait for extruder to reach target temp",
|
||||
"G1 X150 Y5 Z0.3 ; move the platform to purge extrusion",
|
||||
"G92 E0 ; zero the extruded length",
|
||||
"G1 F200 X250 E30 ; extrude 30mm of feed stock",
|
||||
"G92 E0 ; zero the extruded length again",
|
||||
"G1 X150 Y150 Z25 F12000 ; center head for print",
|
||||
"G1 F12000 ; set head seek rate"
|
||||
],
|
||||
"post":[
|
||||
"G1 E-1 F300 ; retract the filament to release pressure",
|
||||
"G1 Z{z_max} E-5 F1200 ; drop bed, retract more filament",
|
||||
"G28 X0 Y0 ; move X/Y to min endstops",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 ; turn off extruder",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false,
|
||||
"bed_width": 305,
|
||||
"bed_depth": 305,
|
||||
"build_height": 305
|
||||
}
|
||||
}
|
||||
40
web/kiri/filter/FDM/TypeA.Series1Pro
Normal file
40
web/kiri/filter/FDM/TypeA.Series1Pro
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"pre":[
|
||||
"G1 Z15.0 F12000 ; move the platform down 15mm",
|
||||
"G1 X150 Y5 F12000 ; center extruder on X",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M83 ; use relative positioning for extruder",
|
||||
"M104 S{temp} ; set extruder temperature",
|
||||
"M140 S{bed_temp} ; set bed temperature",
|
||||
"G21 ; use metric values",
|
||||
"G90 ; use absolute positioning mode",
|
||||
"G28 ; move to endstops",
|
||||
"G29 ; perform auto-levelling",
|
||||
"M109 S{temp} ; wait for extruder to reach target temp",
|
||||
"M190 S{bed_temp} ; wait for bed to reach target temp",
|
||||
"G1 X150 Y5 Z0.3 ; move the platform to purge extrusion",
|
||||
"G92 E0 ; zero the extruded length",
|
||||
"G1 F200 X250 E30 ; extrude 30mm of feed stock",
|
||||
"G92 E0 ; zero the extruded length again",
|
||||
"G1 X150 Y150 Z25 F12000 ; center head for print",
|
||||
"G1 F12000 ; set head seek rate"
|
||||
],
|
||||
"post":[
|
||||
"G1 E-1 F300 ; retract the filament to release pressure",
|
||||
"G1 Z{z_max} E-5 F1200 ; drop bed, retract more filament",
|
||||
"G28 X0 Y0 ; move X/Y to min endstops",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 ; turn off extruder",
|
||||
"M140 S0 ; turn off bed",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false,
|
||||
"bed_width": 305,
|
||||
"bed_depth": 305,
|
||||
"build_height": 305
|
||||
}
|
||||
}
|
||||
36
web/kiri/filter/FDM/Ultimaker.Ultimaker2
Normal file
36
web/kiri/filter/FDM/Ultimaker.Ultimaker2
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"M140 S{bed_temp} T0 ; set bed temperature",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"G28 Z0 ; home Z",
|
||||
"G92 X0 Y0 Z0 E0 ; reset all axes positions",
|
||||
"G1 Z0.25 F180 ; move z to 0.25mm over bed",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 E15 F200 ; purge 15mm from extruder",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 F225 ; set feed speed"
|
||||
],
|
||||
"post":[
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M104 S0 T1 ; turn off left extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"G1 Z205 F1200 ; drop bed",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false,
|
||||
"bed_width": 223,
|
||||
"bed_depth": 223,
|
||||
"build_height": 205
|
||||
}
|
||||
}
|
||||
36
web/kiri/filter/FDM/Wanhao.Duplicator_4s
Normal file
36
web/kiri/filter/FDM/Wanhao.Duplicator_4s
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"M140 S{bed_temp} T0 ; set bed temperature",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"G28 Z0 ; home Z",
|
||||
"G92 X0 Y0 Z0 E0 ; reset all axes positions",
|
||||
"G1 Z0.25 F180 ; move z to 0.25mm over bed",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 E15 F200 ; purge 15mm from extruder",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 F225 ; set feed speed"
|
||||
],
|
||||
"post":[
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M104 S0 T1 ; turn off left extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"G1 Z{z_max} F1200 ; drop bed",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false,
|
||||
"bed_width": 225,
|
||||
"bed_depth": 145,
|
||||
"build_height": 150
|
||||
}
|
||||
}
|
||||
36
web/kiri/filter/FDM/Wanhao.Duplicator_5s
Normal file
36
web/kiri/filter/FDM/Wanhao.Duplicator_5s
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"M140 S{bed_temp} T0 ; set bed temperature",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"G28 Z0 ; home Z",
|
||||
"G92 X0 Y0 Z0 E0 ; reset all axes positions",
|
||||
"G1 Z0.25 F180 ; move z to 0.25mm over bed",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 E15 F200 ; purge 15mm from extruder",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 F225 ; set feed speed"
|
||||
],
|
||||
"post":[
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M104 S0 T1 ; turn off left extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"G1 Z{z_max} F1200 ; drop bed",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false,
|
||||
"bed_width": 305,
|
||||
"bed_depth": 205,
|
||||
"build_height": 575
|
||||
}
|
||||
}
|
||||
36
web/kiri/filter/FDM/Wanhao.Duplicator_6
Normal file
36
web/kiri/filter/FDM/Wanhao.Duplicator_6
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"M140 S{bed_temp} T0 ; set bed temperature",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"G28 Z0 ; home Z",
|
||||
"G92 X0 Y0 Z0 E0 ; reset all axes positions",
|
||||
"G1 Z0.25 F180 ; move z to 0.25mm over bed",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 E15 F200 ; purge 15mm from extruder",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 F225 ; set feed speed"
|
||||
],
|
||||
"post":[
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M104 S0 T1 ; turn off left extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"G1 Z{z_max} F1200 ; drop bed",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false,
|
||||
"bed_width": 200,
|
||||
"bed_depth": 200,
|
||||
"build_height": 180
|
||||
}
|
||||
}
|
||||
36
web/kiri/filter/FDM/Wanhao.Duplicator_i3
Normal file
36
web/kiri/filter/FDM/Wanhao.Duplicator_i3
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"pre":[
|
||||
"M104 S{temp} T0 ; set extruder temperature",
|
||||
"M140 S{bed_temp} T0 ; set bed temperature",
|
||||
"G90 ; set absolute positioning mode",
|
||||
"M83 ; set relative positioning for extruder",
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"G28 Z0 ; home Z",
|
||||
"G92 X0 Y0 Z0 E0 ; reset all axes positions",
|
||||
"G1 Z0.25 F180 ; move z to 0.25mm over bed",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"M109 S{temp} T0 ; wait for extruder to reach target temp",
|
||||
"G1 E15 F200 ; purge 15mm from extruder",
|
||||
"G92 E0 ; zero the extruded",
|
||||
"G1 F225 ; set feed speed"
|
||||
],
|
||||
"post":[
|
||||
"M107 ; turn off filament cooling fan",
|
||||
"M104 S0 T0 ; turn off right extruder",
|
||||
"M104 S0 T1 ; turn off left extruder",
|
||||
"M140 S0 T0 ; turn off bed",
|
||||
"G1 Z{z_max} F1200 ; drop bed",
|
||||
"G28 X0 Y0 ; home XY axes",
|
||||
"M84 ; disable stepper motors"
|
||||
],
|
||||
"cmd":{
|
||||
"fan_power": "M106 S{fan_speed}"
|
||||
},
|
||||
"settings":{
|
||||
"origin_center": false,
|
||||
"bed_width": 200,
|
||||
"bed_depth": 200,
|
||||
"build_height": 180
|
||||
}
|
||||
}
|
||||
27
web/kiri/help-kiri.html
Normal file
27
web/kiri/help-kiri.html
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<button id="help-close">X</button>
|
||||
|
||||
<center><b>Quick Start</b></center>
|
||||
|
||||
<p>
|
||||
<a href="#" title="Kiri-e is the Japanese art of papercutting"><b>Kiri:Moto</b></a>
|
||||
is an extensible, multi-purpose slicing<br>and visualization engine that produces output for:
|
||||
|
||||
<li><label>CAM</label> : 3 axis CNC toolpaths
|
||||
<li><label>FDM</label> : GCode for 3D printers
|
||||
<li><label>LASER</label> : DXG / SVG cut paths
|
||||
<li><label><a target="_onshape" href="https://appstore.onshape.com/apps/CAM/EAAEWYIOMQKBENEMYW2N7MF253CT4WYL6SUJGEY=/description">Onshape</a></label> : Directly Integrated
|
||||
<li><label><a target="_thingiverse" href="http://www.thingiverse.com/apps/kirimoto/">Thingiverse</a></label> : Thing App
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b><a>Files</a></b> are loaded via drag/drop or 'load' menu
|
||||
<li>Loaded files are cached in the lower-left menu
|
||||
<li>Check device mode (FDM, CAM, Laser)
|
||||
<li>Select a GCode profile for your device
|
||||
<li>Check slicing parameters in right menu
|
||||
<li>Slice can be found under 'function'
|
||||
</p>
|
||||
|
||||
<p>Refer to the <a target="_help" href="https://wiki.grid.space/wiki/Kiri:Moto">Grid.Space Wiki</a> for more information</p>
|
||||
|
||||
<p id="kiri-version"></p>
|
||||
251
web/kiri/index.html
Normal file
251
web/kiri/index.html
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="copyright" content="stewart allen [stewart@neuron.com]">
|
||||
<meta name="description" content="3d slice modeler, gcode and CNC toolpath generator">
|
||||
<meta name="keywords" content="3d slicer,slicer,3d slicing,cnc,cam,toolpaths,toolpath generation,kiri,kirimoto,kiri:moto,gcode" />
|
||||
<meta name="author" content="Stewart Allen">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta property="og:description" content="Kiri:Moto is a unique multi-modal 3D slicer that runs entirely in browser and creates output for your favorite maker tools: 3D Printers, CNC Mills and Laser Cutters. Advanted layer view helps debug prints ahead of time.">
|
||||
<meta property="og:title" content="Cloud-based Slicer for Makers">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://grid.space/kiri">
|
||||
<meta property="og:image" content="https://grid.space/img/logo_km_og.png">
|
||||
<title>Kiri:Moto</title>
|
||||
<link rel="icon" href="/kiri/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/kiri/favicon-mobile.png">
|
||||
<link rel="stylesheet" type="text/css" href="/moto/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/kiri/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
/* because TWEEN complains */
|
||||
module = {};
|
||||
</script>
|
||||
<!--{kiri}-->
|
||||
<!-- app title home page links -->
|
||||
<div id="appid">
|
||||
<span><a href="#" id="kiri">Kiri:Moto</a> by <a href="/" target="home">Grid.Space</a></span>
|
||||
</div>
|
||||
<!-- left control menu -->
|
||||
<div id="control-left" class="control"><div id="assets"></div></div>
|
||||
<!-- right control menu -->
|
||||
<div id="control-right" class="control"><div id="control"></div></div>
|
||||
<!-- SPJS gcode sender -->
|
||||
<div id="sender">
|
||||
<div id="sender-bar">
|
||||
<button id="sender-spjs">?</button>
|
||||
<button id="sender-close">X</button>
|
||||
</div>
|
||||
<span>direct device control</span>
|
||||
<div class="sender-sel">
|
||||
<table>
|
||||
<tr>
|
||||
<th colspan="2" class="flow-row">
|
||||
<input class="flow-grow" id="sender-host" size="30" spellcheck="false" placeholder="host:port of SPJS server" />
|
||||
</th>
|
||||
<td> <button id="sender-connect">connect</button> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="flow-row">
|
||||
<select class="flow-grow" id="sender-port" disabled="true"><option>select port</option></select>
|
||||
<select id="sender-mode" disabled="true">
|
||||
<option id="mode-option">set mode</option>
|
||||
<option value="grbl">grbl</option>
|
||||
<option value="tinyg">tinyg</option>
|
||||
</select>
|
||||
</th>
|
||||
<td> <button id="sender-port-close">close</button> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="sender-sel"> <div id="sender-log"></div> </div>
|
||||
<div class="sender-sel" id="sender-cc">
|
||||
<table>
|
||||
<tr>
|
||||
<th>
|
||||
<label>status</label>
|
||||
<input id="sender-status" size="5" disabled="true" />
|
||||
<label>send</label>
|
||||
<input id="sender-command" size="30" />
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="flow-row" id="sender-quick">
|
||||
<button id="sender-ctrlx">soft reset</button>
|
||||
<button id="sender-hold">feed hold</button>
|
||||
<button id="sender-resume">feed resume</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="sender-sel flow-row flow-space-between" id="sender-pad">
|
||||
<div>
|
||||
<table>
|
||||
<tr><th>
|
||||
<button id="sjx-">X-</button>
|
||||
<button id="sjx+">X+</button>
|
||||
</th></tr>
|
||||
<tr><th>
|
||||
<button id="sjy-">Y-</button>
|
||||
<button id="sjy+">Y+</button>
|
||||
</th></tr>
|
||||
<tr><th>
|
||||
<button id="sjz-">Z-</button>
|
||||
<button id="sjz+">Z+</button>
|
||||
</th></tr>
|
||||
</table>
|
||||
</div><div>
|
||||
<table>
|
||||
<tr><th> <label>jog</label><input id="sender-jog" size="4" value="1" title="jog distance in mm" /><label>mm</label> </th></tr>
|
||||
<tr><th> <button id="sender-set-zero">zero out axes</button> </th></tr>
|
||||
<tr><th> <button id="sender-goto-zero">goto zero axis</button> </th></tr>
|
||||
</table>
|
||||
</div><div>
|
||||
<table>
|
||||
<tr><th><label>X</label> <input id="saxi" disabled="true" size="9" /> <input id="srxi" disabled="true" size="9" /></th></tr>
|
||||
<tr><th><label>Y</label> <input id="sayi" disabled="true" size="9" /> <input id="sryi" disabled="true" size="9" /></th></tr>
|
||||
<tr><th><label>Z</label> <input id="sazi" disabled="true" size="9" /> <input id="srzi" disabled="true" size="9" /></th></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sender-sel" id="sender-control">
|
||||
<table>
|
||||
<tr><td>
|
||||
<label>gcode control</label>
|
||||
<div id="sender-control-buttons">
|
||||
<button id="sender-gc-send">send</button>
|
||||
<button id="sender-gc-pause">pause</button>
|
||||
<button id="sender-gc-abort">abort</button>
|
||||
: <button id="sender-gc-runbox">runbox</button>
|
||||
</div>
|
||||
</td></tr>
|
||||
<tr><td class="flow-row flow-space-between">
|
||||
<div><label>queue</label><input id="sender-gc-queue" disabled="true" size="9" /> </div>
|
||||
<div><label>sent</label><input id="sender-gc-sent" disabled="true" size="9" /> </div>
|
||||
<div><label>total</label><input id="sender-gc-lines" disabled="true" size="9" /> </div>
|
||||
</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- all modal dialogs -->
|
||||
<div id="modal">
|
||||
<div id="help"></div>
|
||||
<!-- changeable print dialog -->
|
||||
<div id="print"></div>
|
||||
<!-- hidden file input loader -->
|
||||
<input id="load-file" type="file" name="loadme" style="display:none" accept=".stl,.gcode,.nc"/>
|
||||
</div>
|
||||
<!-- 3js -->
|
||||
<div id="container"></div>
|
||||
<!-- progress bar -->
|
||||
<div id="loading"><div id="progress"><span id="prostatus">status</span></div></div>
|
||||
<!-- file load catalog -->
|
||||
<div id="catalog" class="dialog">
|
||||
<div id="catalogBody" class="flow-col">
|
||||
<span id="localCache">local cache</span>
|
||||
<div id="catalogList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- settings load dialog -->
|
||||
<div id="settings" class="dialog">
|
||||
<div id="settingsBody" class="flow-col">
|
||||
<span id="settingsCache">saved settings</span>
|
||||
<div id="settingsList"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- GCode filter editor -->
|
||||
<div id="devices" class="dialog">
|
||||
<div id="devices-body" class="flow-col">
|
||||
<div id="device-labels" class="flow-row">
|
||||
<span>device</span>
|
||||
<span>settings</span>
|
||||
</div>
|
||||
<div id="device-cols" class="flow-row">
|
||||
<div id="device-list" class="flow-col">
|
||||
<select id="device-select" size="15"></select>
|
||||
</div>
|
||||
<div id="device-info">
|
||||
<div id="device"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="device-action" class="flow-row">
|
||||
<button id="device-add">+</button>
|
||||
<button id="device-del">-</button>
|
||||
<span id="device-spacer"></span>
|
||||
<button id="device-save">save</button>
|
||||
<button id="device-close">close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- CAM tools dialog -->
|
||||
<div id="tools" class="dialog">
|
||||
<div id="tools-body" class="flow-col">
|
||||
<div id="tool-labels" class="flow-row">
|
||||
<span>tools</span>
|
||||
<span>details</span>
|
||||
</div>
|
||||
<div id="tool-cols" class="flow-row">
|
||||
<div id="tool-list" class="flow-col">
|
||||
<select id="tool-select" size="15"></select>
|
||||
</div>
|
||||
<div id="tool-info" class="flow-col">
|
||||
<div class="flow-row"><label>type</label>
|
||||
<select id="tool-type">
|
||||
<option value="endmill" selected>endmill</option>
|
||||
<option value="ballmill">ballmill</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flow-row"><label>name</label><input id="tool-name" size=8></input></div>
|
||||
<div class="flow-row"><label>tool #</label><input id="tool-num" size=5></input></div>
|
||||
<div class="flow-row"><label>metric</label><input id="tool-metric" type="checkbox"></input></div>
|
||||
<div class="grouphead">flute</div>
|
||||
<div class="flow-row" title="flute diameter"><label>diameter</label><input id="tool-fdiam" size=5></input></div>
|
||||
<div class="flow-row" title="flute length"><label>length</label><input id="tool-flen" size=5></input></div>
|
||||
<div class="grouphead">shaft</div>
|
||||
<div class="flow-row" title="shaft diameter"><label>diameter</label><input id="tool-sdiam" size=5></input></div>
|
||||
<div class="flow-row" title="shaft length"><label>length</label><input id="tool-slen" size=5></input></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="tool-action" class="flow-row">
|
||||
<button id="tool-add">+</button>
|
||||
<button id="tool-del">-</button>
|
||||
<span id="tool-spacer"></span>
|
||||
<button id="tools-save">save</button>
|
||||
<button id="tools-close">done</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- selection info -->
|
||||
<div id="selection">
|
||||
<span>
|
||||
<label class="label">width:</label><label id="sel_width" class="value">mm</label>
|
||||
<label class="label">depth:</label><label id="sel_depth" class="value">mm</label>
|
||||
<label class="label">height:</label><label id="sel_height" class="value">mm</label>
|
||||
|
||||
<label class="label">scale</label>
|
||||
<label class="label">x</label><input id="scale_x" size="3" value="1"/>
|
||||
<label class="label">y</label><input id="scale_y" size="3" value="1"/>
|
||||
<label class="label">z</label><input id="scale_z" size="3" value="1"/>
|
||||
<label class="label">uniform</label><input type="checkbox" id="scale_uni" checked>
|
||||
|
||||
<label class="label">rotate</label>
|
||||
<button id="x+" title="hold SHIFT to rotate 5 degrees">x+</button><button id="x-" title="hold SHIFT to rotate 5 degrees">x-</button>
|
||||
<button id="y+" title="hold SHIFT to rotate 5 degrees">y+</button><button id="y-" title="hold SHIFT to rotate 5 degrees">y-</button>
|
||||
<button id="z+" title="hold SHIFT to rotate 5 degrees">z+</button><button id="z-" title="hold SHIFT to rotate 5 degrees">z-</button>
|
||||
</span>
|
||||
</div>
|
||||
<!-- layer slider -->
|
||||
<div id="layer-view" class="flow-row">
|
||||
<span>
|
||||
<button id="layer-toggle">layers</button>
|
||||
<div id="layers"></div>
|
||||
<input id="layer-id" size="4">
|
||||
<input id="layer-slider" type="range">
|
||||
<button id="layer-range"> section</button>
|
||||
<input id="layer-span" size="4" value="1">
|
||||
</span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
47
web/kiri/output-gcode.html
Normal file
47
web/kiri/output-gcode.html
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<button id="print-close">X</button>
|
||||
|
||||
<span id="print-title">output options</span>
|
||||
|
||||
<div>
|
||||
<table id="print-info">
|
||||
<tr><th>file name</th><td><input id="print-filename" size="30" spellcheck="false" /></td></tr>
|
||||
<tr><th>file size (bytes)</th><td><input id="print-filesize" size="10" disabled="true" /></td></tr>
|
||||
<tr id="mill-info"><th>estimated mill time (h:m:s)</th><td><input id="mill-time" size="10" disabled="true" /></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="print-filament-row">
|
||||
<table>
|
||||
<tr><th>filament used (mm)</th><td><input id="print-filament" size="10" disabled="true" /></td></tr>
|
||||
<tr><th>filament density (g/cm^3)</th><td><input id="print-density" size="10" value="1.25" /></td></tr>
|
||||
<tr><th>printed weight (g)</th><td><input id="print-weight" size="10" disabled="true" /></td></tr>
|
||||
<tr><th>estimated print time (h:m:s)</th><td><input id="print-time" size="10" disabled="true" /></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="print-sel">
|
||||
<table><tr><th>
|
||||
<button id="print-download">download gcode file</button>
|
||||
<button id="print-serial">use gcode sender</button>
|
||||
</th></tr></table>
|
||||
</div>
|
||||
|
||||
<div class="print-sel" id="send-to-octoprint">
|
||||
<table>
|
||||
<tr><th colspan="2"><button id="print-octoprint">send to octoprint</button></th></tr>
|
||||
<tr id="ophost"><th>host:port</th><td><input id="octo-host" size="35" placeholder="http(s)://host:port" /></td></tr>
|
||||
<tr id="opapik"><th>api key</th><td><input id="octo-apik" size="35" /></td></tr>
|
||||
<!--<tr><th>options</th><td><input type="checkbox" id="octo-print">start print</td></tr>-->
|
||||
<tr id="ophint"><td colspan="2"><span id="octo-hint"><a href="http://docs.octoprint.org/en/master/api/general.html?highlight=cors#cross-origin-requests" target="_help">CORS support</a> must be enabled in API settings</span></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="print-sel" id="send-to-gridprint">
|
||||
<table>
|
||||
<tr><th colspan="2"><button id="print-gridprint">send to grid:print</button></th></tr>
|
||||
<tr><th>host:port</th><td><input id="grid-host" size="35" placeholder="http(s)://host:port" /></td></tr>
|
||||
<tr><th>api key</th><td><input id="grid-apik" size="35" /></td></tr>
|
||||
<tr><th>target</th><td><input id="grid-target" size="35" /></td></tr>
|
||||
<tr><td colspan="2"><span id="grid-hint">setup <a href="https://github.com/stewartoallen/grid-print" target="_help">grid:print</a> for your network</span></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
18
web/kiri/output-laser.html
Normal file
18
web/kiri/output-laser.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<button id="print-close">X</button>
|
||||
|
||||
<span>output options</span>
|
||||
|
||||
<div>
|
||||
<table id="print-info">
|
||||
<tr><th>file name</th><td><input id="print-filename" size="30" spellcheck="false" /></td></tr>
|
||||
<tr><th>segments</th><td><input id="print-lines" size="10" disabled="true" /></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="print-sel">
|
||||
<table>
|
||||
<tr><th><button id="print-svg">download as svg</button></th></tr>
|
||||
<tr><th><button id="print-dxf">download as dxf</button></th></tr>
|
||||
<tr><th><button id="print-lg">download as gcode</button></th></tr>
|
||||
</table>
|
||||
</div>
|
||||
31
web/kiri/style-atom3dp.css
Normal file
31
web/kiri/style-atom3dp.css
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/** atom3dp css */
|
||||
|
||||
button {
|
||||
color: #000;
|
||||
border: 1px solid rgba(20,20,20,0.5);
|
||||
}
|
||||
label {
|
||||
border-bottom: 1px solid rgba(200,200,0,0.75);
|
||||
}
|
||||
.grouphead {
|
||||
background-color: rgba(0,0,0,0.85);
|
||||
}
|
||||
#control-left {
|
||||
background-color: rgba(240,240,20,0.5);
|
||||
}
|
||||
#control-right {
|
||||
color: #000;
|
||||
background-color: rgba(240,240,20,0.5);
|
||||
}
|
||||
#appid span {
|
||||
background-color: rgba(240,240,20,0.5) !important;
|
||||
}
|
||||
#selection span {
|
||||
background-color: rgba(240,240,20,0.5) !important;
|
||||
}
|
||||
#layer-view span {
|
||||
background-color: rgba(240,240,20,0.5) !important;
|
||||
}
|
||||
#modeCAM {
|
||||
display: none !important;
|
||||
}
|
||||
578
web/kiri/style.css
Normal file
578
web/kiri/style.css
Normal file
|
|
@ -0,0 +1,578 @@
|
|||
th, tr, td, span, div, label, button {
|
||||
user-select: none;
|
||||
}
|
||||
input[type=range]::-moz-focus-outer {
|
||||
border: 0;
|
||||
}
|
||||
.dialog {
|
||||
display: none;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border: 1px solid rgba(255,255,255,0.75);
|
||||
background-color: rgba(20,20,20,0.75);
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
position: fixed;
|
||||
top: 25px;
|
||||
xleft: 25px;
|
||||
}
|
||||
.dialog span {
|
||||
padding: 2px 5px 2px 5px;
|
||||
margin: 0 5px 5px 5px;
|
||||
background-color: #333;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
border: 1px solid #555;
|
||||
text-align: center;
|
||||
}
|
||||
.helper {
|
||||
position: relative;
|
||||
display: none;
|
||||
}
|
||||
.mono {
|
||||
unicode-bidi: embed;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
#selection {
|
||||
display: none;
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
bottom: 3px;
|
||||
}
|
||||
#selection span {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 3px 8px 8px 8px;
|
||||
background-color: rgba(170,221,255,0.75);
|
||||
color: rgba(0,0,0,0.5);
|
||||
}
|
||||
#selection label {
|
||||
border: 0;
|
||||
}
|
||||
#selection .value {
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
color: black;
|
||||
}
|
||||
#selection button {
|
||||
margin-left: 1px;
|
||||
border: 1px solid rgba(255,255,255,0.75);
|
||||
}
|
||||
#appid {
|
||||
text-align: center;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
top: 3px;
|
||||
}
|
||||
#appid span {
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
padding: 5px 8px 3px 8px;
|
||||
background-color: rgba(170,221,255,0.75);
|
||||
color: rgba(0,0,0,0.5);
|
||||
}
|
||||
#appid a {
|
||||
color: rgba(0,0,0,0.75);
|
||||
font-weight: bold;
|
||||
}
|
||||
#appid a:hover {
|
||||
color: rgba(0,50,50,0.75);
|
||||
}
|
||||
#container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#loading {
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
border: 2px solid rgba(10,10,10,0.25);
|
||||
border-radius: 5px;
|
||||
margin: 2px;
|
||||
background-color: rgba(200,200,200,0.75);
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
#progress {
|
||||
position: relative;
|
||||
width: 1%;
|
||||
height: 100%;
|
||||
background-color: #f00;
|
||||
text-align: left;
|
||||
}
|
||||
#progress > span {
|
||||
color: white;
|
||||
padding-left: 10px;
|
||||
}
|
||||
#modal {
|
||||
z-index: 20000;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
display: none;
|
||||
}
|
||||
|
||||
/** PRINT DIALOG */
|
||||
|
||||
#print {
|
||||
position: fixed;
|
||||
-webkit-transform: translate(-50%, 0);
|
||||
transform: translate(-50%, 0);
|
||||
text-align: center;
|
||||
top: 50px;
|
||||
left: 50%;
|
||||
color: white;
|
||||
border: 1px solid white;
|
||||
background-color: rgba(20,20,20,0.75);
|
||||
padding: 10px;
|
||||
}
|
||||
#print-close {
|
||||
position: absolute;
|
||||
z-index: 200;
|
||||
right: 3px;
|
||||
top: 3px;
|
||||
}
|
||||
#print > div {
|
||||
border: 1px solid #888;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
padding: 2px;
|
||||
margin: 6px 0 6px 0;
|
||||
}
|
||||
#print .print-sel:hover {
|
||||
border: 1px solid yellow;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
#print table {
|
||||
width: 100%;
|
||||
}
|
||||
#print th {
|
||||
text-align: left;
|
||||
}
|
||||
#print td {
|
||||
text-align: right;
|
||||
}
|
||||
#print input {
|
||||
text-align: right;
|
||||
}
|
||||
#print input[disabled] {
|
||||
background-color: #bbb;
|
||||
border-color: #ccc;
|
||||
color: black;
|
||||
}
|
||||
#print-density {
|
||||
disabled: false;
|
||||
}
|
||||
#grid-hint {
|
||||
font-style: italic;
|
||||
font-weight: lighter;
|
||||
font-size: 10px;
|
||||
}
|
||||
#octo-hint {
|
||||
font-style: italic;
|
||||
font-weight: lighter;
|
||||
font-size: 10px;
|
||||
}
|
||||
#print a {
|
||||
color: #8ae;
|
||||
}
|
||||
#print a:hover {
|
||||
color: #fff;
|
||||
background-color: #888;
|
||||
}
|
||||
|
||||
/** sender dialog */
|
||||
|
||||
#sender {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 25px;
|
||||
right: 0px;
|
||||
color: white;
|
||||
border: 1px solid white;
|
||||
background-color: rgba(20,20,20,0.75);
|
||||
padding: 10px;
|
||||
display: none;
|
||||
}
|
||||
#sender-bar {
|
||||
border: 0 !important;
|
||||
margin: 2px 2px 0 0 !important;
|
||||
padding: 0 !important;
|
||||
position: absolute;
|
||||
z-index: 200;
|
||||
right: 3px;
|
||||
top: 3px;
|
||||
}
|
||||
#sender-bar button {
|
||||
margin: 0;
|
||||
}
|
||||
#sender > div {
|
||||
border: 1px solid #888;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
padding: 2px;
|
||||
margin: 6px 0 6px 0;
|
||||
}
|
||||
#sender .sender-sel:hover {
|
||||
border: 1px solid yellow;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
#sender table {
|
||||
width: 100%;
|
||||
}
|
||||
#sender th {
|
||||
text-align: left;
|
||||
}
|
||||
#sender td {
|
||||
text-align: right;
|
||||
}
|
||||
#sender input[disabled] {
|
||||
background-color: #bbb;
|
||||
border-color: #ccc;
|
||||
color: black;
|
||||
}
|
||||
#v a {
|
||||
color: #8ae;
|
||||
}
|
||||
#sender a:hover {
|
||||
color: #fff;
|
||||
background-color: #888;
|
||||
}
|
||||
#sender-log {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
padding: 5px;
|
||||
overflow: auto;
|
||||
text-align: left;
|
||||
font-size: 11px;
|
||||
max-width: 100em;
|
||||
}
|
||||
#sender-set-zero, #sender-goto-zero, #sender-connect, #sender-port-close {
|
||||
width: 100%;
|
||||
}
|
||||
#sender-jog {
|
||||
flex-grow: 1;
|
||||
}
|
||||
#sender label {
|
||||
color: #ddd;
|
||||
background-color: #555;
|
||||
border: 1px solid #aaa;
|
||||
padding-top: 1px;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
#sender span {
|
||||
font-weight: normal;
|
||||
}
|
||||
#sender-control td {
|
||||
text-align: justify;
|
||||
}
|
||||
#sender-control input {
|
||||
margin-left: 2px;
|
||||
}
|
||||
#sender-control-buttons {
|
||||
float:right;
|
||||
}
|
||||
#sender-status {
|
||||
text-align: center;
|
||||
xbackground-color: #555 !important;
|
||||
xborder: 1px solid black !important;
|
||||
xmargin: 2px 2px 2px 0;
|
||||
xpadding: 2px 2px 2px 0;
|
||||
}
|
||||
#sender-quick button {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/** help dialog */
|
||||
|
||||
#help {
|
||||
position: fixed;
|
||||
-webkit-transform: translate(-50%, 0);
|
||||
transform: translate(-50%, 0);
|
||||
text-align: left;
|
||||
top: 50px;
|
||||
left: 50%;
|
||||
color: white;
|
||||
border: 1px solid white;
|
||||
background-color: rgba(20,20,20,0.75);
|
||||
padding: 10px;
|
||||
}
|
||||
#help-close {
|
||||
position: absolute;
|
||||
z-index: 200;
|
||||
right: 3px;
|
||||
top: 3px;
|
||||
}
|
||||
#help a {
|
||||
color: #8ae;
|
||||
}
|
||||
#help a:hover {
|
||||
color: #fff;
|
||||
background-color: #888;
|
||||
}
|
||||
#help p {
|
||||
font-weight: normal;
|
||||
}
|
||||
button.selected {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
/** tool dialog */
|
||||
|
||||
#tools {
|
||||
text-align: center;
|
||||
}
|
||||
#tools button {
|
||||
margin-left: 1px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
#tools span {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
#tools-body {
|
||||
height: 100%;
|
||||
}
|
||||
#tool-labels span {
|
||||
width: 200px;
|
||||
padding: 2px 5px 2px 5px;
|
||||
margin: 0 5px 5px 5px;
|
||||
background-color: #333;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
border: 1px solid #555;
|
||||
text-align: center;
|
||||
}
|
||||
#tool-cols > div {
|
||||
width: 200px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border: 1px solid #aaa;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
#tool-info > div {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
flex-basis: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
#tool-action > button {
|
||||
text-align: center;
|
||||
}
|
||||
#tool-action > span {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
}
|
||||
#tools label {
|
||||
text-align: left;
|
||||
}
|
||||
#tools input {
|
||||
background-color: #ddd;
|
||||
margin-bottom: 1px;
|
||||
text-align: right;
|
||||
}
|
||||
#tools input:focus {
|
||||
background-color: #dee;
|
||||
}
|
||||
#tool-list > select {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: white;
|
||||
border: 0;
|
||||
margin-bottom: 2px;
|
||||
padding-bottom: 2px;
|
||||
background-color: transparent;
|
||||
overflow-y: auto;
|
||||
outline: none;
|
||||
}
|
||||
#tool-list select::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/** device dialog */
|
||||
|
||||
#devices {
|
||||
text-align: center;
|
||||
}
|
||||
#devices button {
|
||||
margin-left: 1px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
#devices span {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
#devices-body {
|
||||
height: 100%;
|
||||
}
|
||||
#device-labels span {
|
||||
width: 200px;
|
||||
padding: 2px 5px 2px 5px;
|
||||
margin: 0 5px 5px 5px;
|
||||
background-color: #333;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
border: 1px solid #555;
|
||||
text-align: center;
|
||||
}
|
||||
#device-cols > div {
|
||||
width: 200px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
border: 1px solid #aaa;
|
||||
border-top-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
#device-info > div {
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
flex-basis: auto;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
#device-action > button {
|
||||
text-align: center;
|
||||
}
|
||||
#device-action > span {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
}
|
||||
#devices label {
|
||||
text-align: left;
|
||||
}
|
||||
#devices input {
|
||||
background-color: #ddd;
|
||||
margin-bottom: 1px;
|
||||
text-align: right;
|
||||
}
|
||||
#devices input:focus {
|
||||
background-color: #dee;
|
||||
}
|
||||
#device-list select {
|
||||
flex-grow: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: white;
|
||||
border: 0;
|
||||
margin-bottom: 2px;
|
||||
padding-bottom: 2px;
|
||||
background-color: transparent;
|
||||
overflow-y: auto;
|
||||
outline: none;
|
||||
}
|
||||
#device-list select::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
#device-list option[local="1"] {
|
||||
color: #ff5;
|
||||
}
|
||||
#device label {
|
||||
white-space: nowrap;
|
||||
}
|
||||
#device textarea {
|
||||
overflow: hidden;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
/** file catalog */
|
||||
|
||||
#import {
|
||||
width: 100%;
|
||||
}
|
||||
button[import="1"] {
|
||||
width: 5px;
|
||||
padding-left: 2px;
|
||||
padding-right: 2px;
|
||||
}
|
||||
#catalog {
|
||||
text-align: center;
|
||||
min-height: 25%;
|
||||
max-height: 75%;
|
||||
height: 1000px;
|
||||
}
|
||||
#catalog div {
|
||||
position: relative;
|
||||
}
|
||||
#catalogBody {
|
||||
height: 100%;
|
||||
}
|
||||
#catalogBody div::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
#catalogList {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
#catalogList button {
|
||||
margin: 0px 0px 2px 2px;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/** saved settings list */
|
||||
#settings button {
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
/** layer slider and controls */
|
||||
|
||||
#layer-view {
|
||||
display: none;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 25px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#layer-view span {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
padding: 3px 8px 8px 8px;
|
||||
background-color: rgba(170,221,255,0.75);
|
||||
color: rgba(0,0,0,0.5);
|
||||
}
|
||||
#layer-view input {
|
||||
vertical-align: middle;
|
||||
}
|
||||
#layer-view button {
|
||||
position: relative;
|
||||
margin-left: 1px;
|
||||
border: 1px solid rgba(255,255,255,0.75);
|
||||
}
|
||||
#layer-slider {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
}
|
||||
#layers {
|
||||
display: none;
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
background-color: rgba(0,0,0,0.2);
|
||||
border: 1px solid rgba(0,0,0,0.5);
|
||||
border-radius: 3px;
|
||||
padding: 6px;
|
||||
-webkit-transform: translate(-10px, 0);
|
||||
transform: translate(-10px, 0);
|
||||
}
|
||||
#layers label {
|
||||
text-align: left;
|
||||
color: rgba(0,0,0,0.85);
|
||||
}
|
||||
BIN
web/meta/favicon-mobile.png
Normal file
BIN
web/meta/favicon-mobile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
BIN
web/meta/favicon.ico
Normal file
BIN
web/meta/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
26
web/meta/index.html
Normal file
26
web/meta/index.html
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="copyright" content="stewart allen [stewart@neuron.com]">
|
||||
<meta name="description" content="3d block modeler">
|
||||
<meta name="keywords" content="gridspace,meta,metamoto,3d,block,builder,modeler,modeling">
|
||||
<meta name="author" content="Stewart Allen">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<title>Meta:Moto</title>
|
||||
<link rel="icon" href="/meta/favicon.ico">
|
||||
<link rel="apple-touch-icon" href="/meta/favicon-mobile.png">
|
||||
<link rel="stylesheet" type="text/css" href="/moto/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="/meta/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
/* because TWEEN complains */
|
||||
module = {};
|
||||
</script>
|
||||
<!--{meta}-->
|
||||
<div id="control-left"><div id="assets"><div class="title"><a href="/"><span>grid</span>:<span>space</span></a></div></div></div>
|
||||
<div id="control-right"><div id="control"><div class="title"><a href="/meta/"><span>meta</span>:<span>moto</span></a></div></div></div>
|
||||
<div id="container"></div>
|
||||
</body>
|
||||
</html>
|
||||
56
web/meta/style.css
Normal file
56
web/meta/style.css
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
.ck_space button {
|
||||
margin: 2px 0px 1px 2px !important;
|
||||
}
|
||||
|
||||
.ck_spaces {
|
||||
padding-top: 2px;
|
||||
}
|
||||
.ck_spaces div {
|
||||
-webkit-flex-direction: row;
|
||||
-webkit-justify-content: flex-start;
|
||||
-webkit-align-items: center;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.ck_spaces button {
|
||||
margin: 0px 0px 1px 2px !important;
|
||||
}
|
||||
|
||||
.ck_meshes {
|
||||
padding-top: 2px;
|
||||
}
|
||||
.ck_meshes div {
|
||||
-webkit-flex-direction: row;
|
||||
-webkit-justify-content: flex-start;
|
||||
-webkit-align-items: center;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.ck_meshes button {
|
||||
margin: 0px 0px 1px 2px !important;
|
||||
}
|
||||
|
||||
button.load {
|
||||
-webkit-flex-basis: 1px;
|
||||
-webkit-flex-grow: 1;
|
||||
flex-basis: 1px;
|
||||
flex-grow: 1;
|
||||
text-align: left;
|
||||
}
|
||||
button.del {
|
||||
}
|
||||
|
||||
#dropbutton {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
background-color: #ccc;
|
||||
border-width: 1px;
|
||||
}
|
||||
277
web/moto/style.css
Normal file
277
web/moto/style.css
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
/** ******************************************************************
|
||||
* Element
|
||||
******************************************************************* */
|
||||
|
||||
a, a:hover, a:visited {
|
||||
border: none;
|
||||
outline: none;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
body,div {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
font-weight: normal;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
label {
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #888;
|
||||
margin-right: 2px;
|
||||
}
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
button {
|
||||
outline: none;
|
||||
max-width: 25ex;
|
||||
border: 1px solid rgba(220,220,220,0.8);
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
background-color: rgba(210,210,210,0.8);
|
||||
}
|
||||
button:hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
button[load] {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
button[del] {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Class
|
||||
******************************************************************* */
|
||||
|
||||
.noselect {
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.title {
|
||||
text-align:center;
|
||||
border-radius: 4px;
|
||||
border-bottom: 2px;
|
||||
color: #222;
|
||||
padding: 4px 5px 3px 5px;
|
||||
margin-bottom: 2px;
|
||||
background-color: rgba(170,221,255,0.75);
|
||||
border: 1px solid rgba(255,255,255,0.5);
|
||||
font-size: 12pt !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
.tablerow {
|
||||
-webkit-flex-direction: row;
|
||||
-webkit-justify-content: center;
|
||||
-webkit-align-items: center;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.tablerow button {
|
||||
-webkit-flex-basis: auto;
|
||||
-webkit-flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
flex-basis: auto;
|
||||
width: 33.33%;
|
||||
}
|
||||
.grouphead {
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 4px 5px 3px 5px;
|
||||
margin-top: 3px;
|
||||
margin-bottom: 1px;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
font-size: 10pt;
|
||||
font-weight: normal;
|
||||
background-color: rgba(0,0,0,0.75);
|
||||
}
|
||||
.grouphead:hover {
|
||||
cursor: default;
|
||||
}
|
||||
.ck_catalog {
|
||||
padding-top: 2px;
|
||||
}
|
||||
.ck_catalog div {
|
||||
-webkit-flex-direction: row;
|
||||
-webkit-justify-content: center;
|
||||
-webkit-align-items: center;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.ck_catalog button {
|
||||
margin: 0px 0px 1px 2px !important;
|
||||
}
|
||||
.buton {
|
||||
font-weight: bold;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.flow-row {
|
||||
position: relative;
|
||||
-webkit-flex-direction: row;
|
||||
-webkit-justify-content: flex-end;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.flow-row label {
|
||||
-webkit-flex-basis: auto;
|
||||
-webkit-flex-grow: 1;
|
||||
flex-basis: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.flow-col {
|
||||
position: relative;
|
||||
-webkit-flex-direction: column;
|
||||
-webkit-justify-content: flex-start;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.flow-left {
|
||||
-webkit-justify-content: flex-start;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.flow-grow {
|
||||
-webkit-flex-grow: 1;
|
||||
flex-grow: 1
|
||||
}
|
||||
.flow-space-between {
|
||||
-webkit-justify-content: space-between;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.control input {
|
||||
background-color: #ddd;
|
||||
margin-bottom: 1px;
|
||||
text-align: right;
|
||||
}
|
||||
.control input:focus {
|
||||
background-color: #dee;
|
||||
}
|
||||
.control input[disabled] {
|
||||
background-color: #aaa;
|
||||
border-color: #bbb;
|
||||
color: #000;
|
||||
border-width: 1px;
|
||||
}
|
||||
.control input[type="range"] {
|
||||
width: 85px;
|
||||
background-color: transparent;
|
||||
}
|
||||
.control button {
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* ID
|
||||
******************************************************************* */
|
||||
|
||||
#container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
xoverflow: hidden;
|
||||
}
|
||||
#control-left {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
color: #eee;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
padding: 5px;
|
||||
border-top: 1px solid #bbb;
|
||||
border-right: 1px solid #bbb;
|
||||
border-bottom: 1px solid #bbb;
|
||||
background-color: rgba(20,20,20,0.5);
|
||||
overflow-y: scroll;
|
||||
}
|
||||
#control-left::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
#control-right::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
#control-right {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
padding: 5px;
|
||||
border-top: 1px solid #bbb;
|
||||
border-left: 1px solid #bbb;
|
||||
border-bottom: 1px solid #bbb;
|
||||
background-color: rgba(20,20,20,.5);
|
||||
color: #eee;
|
||||
overflow-y: scroll;
|
||||
overflow-x: visible;
|
||||
}
|
||||
#loading {
|
||||
position: fixed;
|
||||
top: 5px;
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
border: 1px solid white;
|
||||
margin: 2px;
|
||||
background-color: #ddd;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
#modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 20000;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
background-color: rgba(0,0,0,0);
|
||||
}
|
||||
#progress {
|
||||
position: relative;
|
||||
width: 1%;
|
||||
height: 100%;
|
||||
background-color: #f00;
|
||||
}
|
||||
#welcome {
|
||||
color: white;
|
||||
padding: 10px;
|
||||
border: 1px solid white;
|
||||
background-color: rgba(20,20,20,0.75);
|
||||
position: fixed;
|
||||
top: 50px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 100px;
|
||||
margin: 0 auto;
|
||||
width: 50%;
|
||||
max-width: 800px;
|
||||
min-width: 500px;
|
||||
display: block;
|
||||
unicode-bidi: embed;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
overflow-y: scroll
|
||||
}
|
||||
BIN
web/obj/cube.stl
Normal file
BIN
web/obj/cube.stl
Normal file
Binary file not shown.
BIN
web/obj/meta-corner-1.stl
Normal file
BIN
web/obj/meta-corner-1.stl
Normal file
Binary file not shown.
BIN
web/obj/meta-corner-2.stl
Normal file
BIN
web/obj/meta-corner-2.stl
Normal file
Binary file not shown.
BIN
web/obj/meta-corner-3.stl
Normal file
BIN
web/obj/meta-corner-3.stl
Normal file
Binary file not shown.
BIN
web/obj/meta-corner-4.stl
Normal file
BIN
web/obj/meta-corner-4.stl
Normal file
Binary file not shown.
Loading…
Reference in a new issue