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