move src2 -> src, web2 -> web, remove old module system, fix deps
This commit is contained in:
parent
1d821d4e24
commit
ce4f5d6e78
488 changed files with 26179 additions and 26609 deletions
78
src.old/kiri/alerts.js
Normal file
78
src.old/kiri/alerts.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.api
|
||||
gapp.register("kiri.alerts", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const { api } = kiri;
|
||||
|
||||
let alerts = [];
|
||||
|
||||
function show(message, time) {
|
||||
if (message === undefined || message === null) {
|
||||
return update(true);
|
||||
}
|
||||
let rec = [message, Date.now(), time, true];
|
||||
if (api.feature.alert_event) {
|
||||
api.event.emit('alert', rec);
|
||||
} else {
|
||||
alerts.push(rec);
|
||||
setTimeout( update, time+0.02);
|
||||
update();
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
function hide(rec, recs) {
|
||||
if (Array.isArray(recs)) {
|
||||
for (let r of recs) {
|
||||
hide(r);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (api.feature.alert_event) {
|
||||
api.event.emit('alert.cancel', rec);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(rec)) {
|
||||
rec[3] = false;
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
function update(clear) {
|
||||
if (clear) {
|
||||
alerts = [];
|
||||
}
|
||||
const now = Date.now();
|
||||
const { ui } = api;
|
||||
// filter out by age and active flag
|
||||
alerts = alerts.filter(alert => {
|
||||
return alert[3] && (now - alert[1]) < ((alert[2] || 5) * 1000);
|
||||
});
|
||||
// limit to 5 showing
|
||||
while (alerts.length > 5) {
|
||||
alerts.shift();
|
||||
}
|
||||
// return if called before UI configured
|
||||
if (!ui.alert) {
|
||||
return;
|
||||
}
|
||||
if (alerts.length > 0) {
|
||||
ui.alert.text.innerHTML = alerts.map(v => ['<p>',v[0],'</p>'].join('')).join('');
|
||||
ui.alert.dialog.style.display = 'flex';
|
||||
} else {
|
||||
ui.alert.dialog.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// extend API
|
||||
Object.assign(api.alerts, {
|
||||
hide,
|
||||
show,
|
||||
update
|
||||
});
|
||||
|
||||
});
|
||||
149
src.old/kiri/api.js
Normal file
149
src.old/kiri/api.js
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: moto.broker
|
||||
// dep: data.local
|
||||
// dep: kiri.utils
|
||||
// dep: kiri.consts
|
||||
gapp.register("kiri.api", (root, exports) => {
|
||||
|
||||
let { data, kiri, moto, noop } = root,
|
||||
{ consts, utils } = kiri,
|
||||
{ ajax, o2js, js2o } = utils,
|
||||
lists = consts.LISTS,
|
||||
clone = Object.clone,
|
||||
isHover = false,
|
||||
feature = {
|
||||
seed: true, // seed profiles on first use
|
||||
meta: true, // show selected widget metadata
|
||||
frame: true, // receive frame events
|
||||
alert_event: false, // emit alerts as events instead of display
|
||||
controls: true, // show or not side menus
|
||||
device_filter: undefined, // function to limit devices shown
|
||||
drop_group: undefined, // optional array to group multi drop
|
||||
drop_layout: true, // layout on new drop
|
||||
hoverAdds: false, // when true only searches widget additions
|
||||
on_key: undefined, // function override default key handlers
|
||||
on_key2: [], // allows for multiple key handlers
|
||||
on_load: undefined, // function override file drop loads
|
||||
on_add_stl: undefined, // legacy override stl drop loads
|
||||
on_mouse_up: undefined, // function intercepts mouse up select
|
||||
on_mouse_down: undefined, // function intercepts mouse down
|
||||
work_alerts: true, // allow disabling work progress alerts
|
||||
pmode: consts.PMODES.SPEED, // preview modes
|
||||
// hover: false, // when true fires mouse hover events
|
||||
get hover() {
|
||||
return isHover;
|
||||
},
|
||||
set hover(b) {
|
||||
isHover = b;
|
||||
moto.broker.publish("feature.hover", b);
|
||||
}
|
||||
},
|
||||
onkey = (fn) => {
|
||||
api.feature.on_key2.push(fn);
|
||||
},
|
||||
doit = {
|
||||
undo: noop, // do.js
|
||||
redo: noop // do.js
|
||||
},
|
||||
devel = {
|
||||
xray(layers, raw) {
|
||||
let proc = api.conf.get().process,
|
||||
size = proc.sliceHeight || proc.slaSlice || 1,
|
||||
base = (proc.firstSliceHeight || size);
|
||||
layers = Array.isArray(layers) ? layers : [ layers ];
|
||||
proc.xray = layers.map(l => raw ? l : base + l * size - size / 2);
|
||||
proc.xrayi = layers.slice();
|
||||
api.function.slice();
|
||||
}
|
||||
},
|
||||
local = {
|
||||
get: (key) => localGet(key),
|
||||
getInt: (key) => parseInt(localGet(key)),
|
||||
getFloat: (key) => parseFloat(localGet(key)),
|
||||
getBoolean: (key, def = true) => {
|
||||
let val = localGet(key);
|
||||
return val === true || val === 'true' || val === def;
|
||||
},
|
||||
toggle: (key, val, def) => localSet(key, val ?? !api.local.getBoolean(key, def)),
|
||||
put: (key, val) => localSet(key, val),
|
||||
set: (key, val) => localSet(key, val),
|
||||
},
|
||||
tweak = {
|
||||
line_precision(v) { api.work.config({base:{clipperClean: v}}) },
|
||||
gcode_decimals(v) { api.work.config({base:{gcode_decimals: v}}) }
|
||||
},
|
||||
und = undefined,
|
||||
api = exports({
|
||||
ajax, // via utils
|
||||
alerts: {}, // alerts.js
|
||||
busy: {}, // main.js
|
||||
catalog: und, // main.js
|
||||
clip, // <--
|
||||
clone, // <--
|
||||
color: und, // main.js
|
||||
conf: {}, // settings.js
|
||||
const: {}, // main.js
|
||||
devel, // <--
|
||||
device: {}, // devices.js
|
||||
devices: {}, // devices.js
|
||||
dialog: {}, // main.js
|
||||
doit, // <--
|
||||
event: {}, // main.js
|
||||
feature, // <--
|
||||
function: {}, // function.js
|
||||
group: {}, // main.js
|
||||
help: {}, // main.js
|
||||
hide: {}, // main.js
|
||||
image: {}, // main.js
|
||||
js2o, // via utils
|
||||
language: und, // main.js
|
||||
lists, // <--
|
||||
local, // <--
|
||||
modal: {}, // main.js
|
||||
mode: {}, // main.js
|
||||
o2js, // via utils
|
||||
onkey, // <--
|
||||
platform: {}, // platform.js
|
||||
probe: {}, // main.js
|
||||
process: {}, // main.js
|
||||
sdb: data.local,
|
||||
selection: {}, // selection.js
|
||||
settings: {}, // settings.js
|
||||
show: {}, // main.js
|
||||
space: {}, // main.js
|
||||
tool: {}, // kiri-mode/cam/tools.js
|
||||
tweak, // <--
|
||||
uc: {}, // main.js
|
||||
ui: {}, // main.js
|
||||
util: {}, // main.js
|
||||
var: {
|
||||
layer_lo: 0,
|
||||
layer_hi: 0,
|
||||
layer_max: 0
|
||||
},
|
||||
view: {}, // main.js
|
||||
widgets: {}, // widgets.js
|
||||
work: und, // main.js
|
||||
});
|
||||
|
||||
function clip(text) {
|
||||
navigator.clipboard
|
||||
.writeText(text)
|
||||
.catch(err => console.error('Clipboard Error:', err));
|
||||
}
|
||||
|
||||
function localGet(key) {
|
||||
let sloc = api.conf.get().local;
|
||||
return sloc[key] || api.sdb[key];
|
||||
}
|
||||
|
||||
function localSet(key, val) {
|
||||
let sloc = api.conf.get().local;
|
||||
sloc[key] = api.sdb[key] = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
});
|
||||
342
src.old/kiri/client.js
Normal file
342
src.old/kiri/client.js
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// use: kiri.api
|
||||
gapp.register("kiri.client", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
|
||||
// this code runs in kiri's main loop
|
||||
let loc = self.location,
|
||||
host = loc.hostname,
|
||||
port = loc.port,
|
||||
proto = loc.protocol,
|
||||
debug = self.debug === true,
|
||||
time = Date.now,
|
||||
seqid = 1,
|
||||
syncd = {},
|
||||
running = {},
|
||||
worker = null,
|
||||
minions = false,
|
||||
restarting = false
|
||||
// occ = new Worker("/kiri/ext/occ-worker.js", {type:"module"}),
|
||||
;
|
||||
|
||||
/**
|
||||
* @param {Function} fn name of function in kiri.worker
|
||||
* @param {Object} data to send to server
|
||||
* @param {Function} onreply function to call on reply messages
|
||||
* @param {Object[]} zerocopy array of objects to pass using zerocopy
|
||||
*/
|
||||
function send(fn, data, onreply, zerocopy) {
|
||||
let seq = seqid++;
|
||||
|
||||
if (onreply) {
|
||||
// track it only when we expect and can handle a reply
|
||||
running[seq] = { fn:onreply };
|
||||
}
|
||||
let msg = {
|
||||
seq: seq,
|
||||
task: fn,
|
||||
time: time(),
|
||||
data: data
|
||||
};
|
||||
// console.log('client send', msg);
|
||||
// if (!fn) { console.trace({empty_fn: data}) };
|
||||
try {
|
||||
worker.postMessage(msg, zerocopy);
|
||||
} catch (error) {
|
||||
console.trace('work send error', {data, error});
|
||||
}
|
||||
}
|
||||
|
||||
// code is running in the browser / client context
|
||||
const client = exports({
|
||||
send: send,
|
||||
|
||||
pool: {
|
||||
start() {
|
||||
minions = true;
|
||||
send("pool_start", {}, noop);
|
||||
},
|
||||
|
||||
stop() {
|
||||
minions = false;
|
||||
send("pool_stop", {}, noop);
|
||||
}
|
||||
},
|
||||
|
||||
newWorker() {
|
||||
if (self.createWorker) {
|
||||
return self.createWorker();
|
||||
} else if (self.kiri_work) {
|
||||
const work = atob(self.kiri_work);
|
||||
const blob = new Blob([ work ], { type: 'application/javascript' });
|
||||
return new Worker(URL.createObjectURL(blob));
|
||||
} else {
|
||||
let _ = debug ? '_' : '';
|
||||
return new Worker(`/code/kiri_work.js?${_}${gapp.version}`);
|
||||
}
|
||||
},
|
||||
|
||||
isBusy() {
|
||||
let current = 0;
|
||||
|
||||
for (let rec of Object.values(running)) {
|
||||
if (rec.fn) current++;
|
||||
}
|
||||
return current > 0;
|
||||
},
|
||||
|
||||
restart() {
|
||||
// prevent re-entry from cancel callback
|
||||
if (restarting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (worker) {
|
||||
worker.terminate();
|
||||
}
|
||||
|
||||
restarting = true;
|
||||
|
||||
for (let key in running) {
|
||||
let rec = running[key];
|
||||
if (rec.fn) {
|
||||
rec.fn({error: "cancelled operation"});
|
||||
}
|
||||
}
|
||||
|
||||
syncd = {};
|
||||
running = {};
|
||||
worker = client.newWorker();
|
||||
|
||||
client.onmessage = worker.onmessage = function(e) {
|
||||
let now = time(),
|
||||
reply = e.data,
|
||||
record = running[reply.seq],
|
||||
onreply = record ? record.fn : undefined;
|
||||
|
||||
// console.log('client recv', e)
|
||||
if (reply.done) {
|
||||
delete running[reply.seq];
|
||||
}
|
||||
|
||||
// calculate and replace recv time
|
||||
reply.time_recv = now - reply.time_recv;
|
||||
|
||||
if (onreply) {
|
||||
onreply(reply.data, reply);
|
||||
} else {
|
||||
console.log({unexpected_reply: reply});
|
||||
}
|
||||
};
|
||||
|
||||
// restart minions if they were running
|
||||
if (minions) {
|
||||
client.pool.start();
|
||||
}
|
||||
|
||||
restarting = false;
|
||||
},
|
||||
|
||||
config(obj) {
|
||||
send("config", obj, noop);
|
||||
},
|
||||
|
||||
clear() {
|
||||
syncd = {};
|
||||
send("clear", {}, noop);
|
||||
},
|
||||
|
||||
snap(data) {
|
||||
send("snap", data, noop);
|
||||
},
|
||||
|
||||
png(data, ondone) {
|
||||
send("png", data, ondone);
|
||||
},
|
||||
|
||||
// widget sync
|
||||
sync(widgets) {
|
||||
if (!widgets) {
|
||||
widgets = kiri.api.widgets.all();
|
||||
}
|
||||
// sync any widget that has changed
|
||||
for (let widget of widgets.filter(w => w.modified || !syncd[w.id])) {
|
||||
syncd[widget.id] = true;
|
||||
let vertices = widget.getGeoVertices();
|
||||
send("sync", {
|
||||
id: widget.id,
|
||||
meta: widget.meta,
|
||||
group: widget.group.id,
|
||||
track: widget.track,
|
||||
vertices,
|
||||
position: widget.mesh.position,
|
||||
}, done => {
|
||||
widget.modified = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// for belt / angled printing
|
||||
rotate(settings, callback) {
|
||||
send("rotate", { settings }, reply => {
|
||||
if (reply.group) {
|
||||
// collect post-rotation data for slice/preview renders
|
||||
for (let widget of kiri.Widget.Groups.forid(reply.group)) {
|
||||
widget.belt = reply.belt;
|
||||
}
|
||||
} else if (callback) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// called once for each widget
|
||||
slice(settings, widget, callback) {
|
||||
send("slice", {
|
||||
id: widget.id,
|
||||
anno: widget.annotations(),
|
||||
settings: settings
|
||||
}, reply => {
|
||||
callback(reply);
|
||||
if (reply.done || reply.error) {
|
||||
// in belt mode, slicing modifies a widget and requires re-sync
|
||||
if (settings.device.bedBelt) {
|
||||
widget.modified = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// called once after each widget is sliced
|
||||
// performs merged actions that consider all widget slice data
|
||||
sliceAll(settings, callback) {
|
||||
send("sliceAll", { settings }, callback);
|
||||
},
|
||||
|
||||
// turn widget slices into unified path output
|
||||
prepare(settings, update, done) {
|
||||
send("prepare", { settings }, reply => {
|
||||
if (reply.progress) {
|
||||
update(reply.progress, reply.message, reply.layer);
|
||||
}
|
||||
if (reply.done) {
|
||||
done(reply.output, reply.maxSpeed, reply.minSpeed);
|
||||
}
|
||||
if (reply.error) {
|
||||
done(reply);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// convert path output to gcode or other target file output
|
||||
export(settings, online, ondone) {
|
||||
send("export", { settings }, reply => {
|
||||
if (reply.line) {
|
||||
online(reply.line);
|
||||
}
|
||||
if (reply.done) {
|
||||
ondone(reply.output);
|
||||
}
|
||||
if (reply.error) {
|
||||
ondone(null, reply.error);
|
||||
}
|
||||
if (reply.debug) {
|
||||
kiri.api.event.emit("export.debug", reply.debug);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
colors(colors, max, done) {
|
||||
send("colors", { colors, max }, reply => {
|
||||
done(reply);
|
||||
});
|
||||
},
|
||||
|
||||
parse(args, progress, done) {
|
||||
// have to do this client side because DOMParser is not in workers
|
||||
if (args.type === 'svg') {
|
||||
const { settings, code, type } = args;
|
||||
const origin = settings.origin;
|
||||
const offset = {
|
||||
x: origin.x,
|
||||
y: -origin.y,
|
||||
z: origin.z
|
||||
};
|
||||
const output = kiri.newPrint().parseSVG(code, offset);
|
||||
send("parse_svg", output, reply => {
|
||||
if (reply.parsed) {
|
||||
done(kiri.codec.decode(reply.parsed));
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
send("parse", args, reply => {
|
||||
if (reply.progress) {
|
||||
progress(reply.progress);
|
||||
}
|
||||
if (reply.parsed) {
|
||||
done(kiri.codec.decode(reply.parsed), reply.maxSpeed, reply.minSpeed);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
image2mesh(data, progress, output) {
|
||||
send("image2mesh", data, reply => {
|
||||
if (reply.progress) {
|
||||
progress(reply.progress);
|
||||
}
|
||||
if (reply.vertices) {
|
||||
output(reply.vertices);
|
||||
}
|
||||
}, [ data.png ]);
|
||||
},
|
||||
|
||||
gerber2mesh(data, progress, output) {
|
||||
send('gerber2mesh', data, reply => {
|
||||
if (reply.progress) {
|
||||
progress(reply.progress);
|
||||
}
|
||||
if (reply.vertices) {
|
||||
output(reply.vertices);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
zip(files, progress, output) {
|
||||
send("zip", {files}, reply => {
|
||||
if (reply.percent !== undefined) {
|
||||
progress(reply);
|
||||
} else {
|
||||
output(reply);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
wasm(enable) {
|
||||
send("wasm", {enable}, reply => {
|
||||
// console.log({wasm_worker_said: reply});
|
||||
});
|
||||
},
|
||||
|
||||
putCache(key, data) {
|
||||
console.log({ putCache: key, data });
|
||||
send("putCache", {key, data}, reply => {
|
||||
// console.log({ putCache_reply: reply });
|
||||
});
|
||||
},
|
||||
|
||||
clearCache() {
|
||||
send("clearCache", {}, reply => {
|
||||
// console.log({ clearCache_reply: reply });
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// start worker
|
||||
client.restart();
|
||||
|
||||
});
|
||||
466
src.old/kiri/codec.js
Normal file
466
src.old/kiri/codec.js
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: main.kiri
|
||||
// dep: geo.base
|
||||
// dep: geo.polygon
|
||||
// dep: kiri.widget
|
||||
// dep: kiri.slice
|
||||
// dep: kiri.layers
|
||||
gapp.register("kiri.codec", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const decoders = {};
|
||||
const freeMem = true;
|
||||
|
||||
const codec = exports({
|
||||
undef: undefined,
|
||||
encode: encode,
|
||||
decode: decode,
|
||||
registerDecoder: registerDecoder,
|
||||
allocFloat32Array: allocFloat32Array,
|
||||
encodePointArray,
|
||||
decodePointArray,
|
||||
encodePointArray2D,
|
||||
decodePointArray2D,
|
||||
toCodable
|
||||
});
|
||||
|
||||
const TYPE = {
|
||||
WIDGET: 100,
|
||||
SLICE: 200,
|
||||
TOP: 300,
|
||||
POLY: 400,
|
||||
LAYERS: 500,
|
||||
};
|
||||
|
||||
function toCodable(object) {
|
||||
switch (typeof object) {
|
||||
case 'function':
|
||||
return undefined;
|
||||
// case 'boolean':
|
||||
case 'string':
|
||||
case 'number':
|
||||
return object;
|
||||
case 'object':
|
||||
if (Array.isArray(object)) {
|
||||
return object.map(v => toCodable(v));
|
||||
}
|
||||
break;
|
||||
}
|
||||
let o = {};
|
||||
for (let [key, val] of Object.entries(object)) {
|
||||
switch (typeof val) {
|
||||
case 'function':
|
||||
break;
|
||||
case 'object':
|
||||
if (Array.isArray(val)) {
|
||||
val = val.map(v => toCodable(v));
|
||||
} else {
|
||||
val = toCodable(val);
|
||||
}
|
||||
default:
|
||||
o[key] = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
function allocFloat32Array(arg, zeros) {
|
||||
if (arg === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (arg.length === 0) {
|
||||
return [];
|
||||
}
|
||||
let f32;
|
||||
if (arg.byteLength) {
|
||||
// already a float array
|
||||
f32 = arg;
|
||||
} else if (Array.isArray(arg)) {
|
||||
// create float array from array
|
||||
f32 = new Float32Array(arg);
|
||||
} else {
|
||||
// usually a number (size) for array
|
||||
f32 = new Float32Array(arg);
|
||||
}
|
||||
if (zeros && f32.byteLength > 0) {
|
||||
zeros.push(f32.buffer);
|
||||
}
|
||||
return f32;
|
||||
}
|
||||
|
||||
function encode(o, state) {
|
||||
state = state || {};
|
||||
if (o === null) return null;
|
||||
if (o === undefined) return undefined;
|
||||
if (Array.isArray(o)) {
|
||||
let arr = new Array(o.length), i=0;
|
||||
while (i < o.length) arr[i] = encode(o[i++], state);
|
||||
return arr;
|
||||
}
|
||||
switch (typeof(o)) {
|
||||
case 'boolean':
|
||||
case 'string':
|
||||
case 'number':
|
||||
return o;
|
||||
case 'object':
|
||||
if (o.encode) return o.encode(state);
|
||||
return genericObjectEncode(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 (let i=0; i < o.length; i++) o[i] = decode(o[i],state);
|
||||
return o;
|
||||
}
|
||||
switch (typeof(o)) {
|
||||
case 'boolean':
|
||||
case 'string':
|
||||
case 'number':
|
||||
return o;
|
||||
case 'object':
|
||||
if (o.type && decoders[o.type]) return decoders[o.type](o,state);
|
||||
return genODecode(o, state);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function registerDecoder(type, handler) {
|
||||
decoders[type] = handler;
|
||||
}
|
||||
|
||||
function genericObjectEncode(o, state) {
|
||||
if (o instanceof Float32Array) return o;
|
||||
let out = {};
|
||||
for (let 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;
|
||||
let out = {};
|
||||
for (let k in o) {
|
||||
if (o.hasOwnProperty(k)) out[k] = decode(o[k], state);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function encodePointArray(points, state, z) {
|
||||
if (!points) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const length = points.length;
|
||||
|
||||
if (length === 0) {
|
||||
return points;
|
||||
}
|
||||
|
||||
const zeros = state ? state.zeros : undefined;
|
||||
const array = codec.allocFloat32Array(length * 3, zeros);
|
||||
|
||||
for (let i=0, pos=0, point; i<length; i++) {
|
||||
point = points[i];
|
||||
array[pos++] = point.x;
|
||||
array[pos++] = point.y;
|
||||
array[pos++] = z || point.z;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
function encodePointArray2D(points, state) {
|
||||
if (!points) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const length = points.length;
|
||||
|
||||
if (length === 0) {
|
||||
return points;
|
||||
}
|
||||
|
||||
const zeros = state ? state.zeros : undefined;
|
||||
const array = codec.allocFloat32Array(length * 2, zeros);
|
||||
|
||||
for (let i=0, pos=0, point; i<length; i++) {
|
||||
point = points[i];
|
||||
array[pos++] = point.x;
|
||||
array[pos++] = point.y;
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
|
||||
function decodePointArray(array) {
|
||||
if (!array) return null;
|
||||
|
||||
const length = array.length;
|
||||
const points = new Array(length / 3);
|
||||
|
||||
for (let vid=0, pid=0; vid < length; ) {
|
||||
points[pid++] = base.newPoint(array[vid++], array[vid++], array[vid++]);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
function decodePointArray2D(array, z, fn) {
|
||||
if (!array) return null;
|
||||
|
||||
const length = array.length;
|
||||
const points = new Array(length / 2);
|
||||
|
||||
for (let vid=0, pid=0; vid < length; ) {
|
||||
points[pid++] = fn ?
|
||||
fn(array[vid++], array[vid++]) :
|
||||
base.newPoint(array[vid++], array[vid++], z);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
// ----- Widget Codec -----
|
||||
|
||||
kiri.Widget.prototype.encode = function(state) {
|
||||
const json = state._json_;
|
||||
const geo = this.getGeoVertices();
|
||||
const coded = {
|
||||
type: TYPE.WIDGET,
|
||||
id: this.id,
|
||||
ver: 1, // for better future encodings
|
||||
json: json, // safe for JSON (float32array mess)
|
||||
group: this.group.id,
|
||||
track: this.track,
|
||||
geo: json ? Array.from(geo) : geo
|
||||
};
|
||||
return coded;
|
||||
};
|
||||
|
||||
registerDecoder(TYPE.WIDGET, function(v, state) {
|
||||
const id = v.id,
|
||||
group = v.group || id,
|
||||
track = v.track || undefined,
|
||||
widget = kiri.newWidget(id, kiri.Widget.Groups.forid(group));
|
||||
|
||||
widget.loadVertices(v.json ? v.geo.toFloat32() : v.geo);
|
||||
widget.saved = Date.now();
|
||||
if (track && track.pos) {
|
||||
widget.track = track;
|
||||
widget.move(track.pos.x, track.pos.y, track.pos.z, true);
|
||||
}
|
||||
|
||||
return widget;
|
||||
});
|
||||
|
||||
// ----- Slice Codec -----
|
||||
|
||||
kiri.Slice.prototype.encode = function(state) {
|
||||
const rv = {
|
||||
type: TYPE.SLICE,
|
||||
z: this.z,
|
||||
index: this.index,
|
||||
layers: encode(this.layers, state)
|
||||
};
|
||||
// aggressively free memory
|
||||
if (freeMem) this.layers = undefined;
|
||||
return rv;
|
||||
};
|
||||
|
||||
registerDecoder(TYPE.SLICE, function(v, state) {
|
||||
let slice = kiri.newSlice(v.z, state.mesh ? state.mesh.newGroup() : null);
|
||||
|
||||
slice.index = v.index;
|
||||
slice.layers = decode(v.layers, state)
|
||||
|
||||
return slice;
|
||||
});
|
||||
|
||||
// ----- Slice.Top Codec -----
|
||||
|
||||
kiri.Top.prototype.encode = function(state) {
|
||||
let obj = {
|
||||
type: TYPE.TOP,
|
||||
poly: encode(this.poly, state)
|
||||
};
|
||||
|
||||
if (state.full) {
|
||||
// obj.gaps = encode(this.gaps, state);
|
||||
obj.last = encode(this.last, state);
|
||||
obj.shells = encode(this.shells, state);
|
||||
obj.fill_off = encode(this.fill_off, state);
|
||||
obj.fill_lines = encode(this.fill_lines, state);
|
||||
}
|
||||
|
||||
return obj;
|
||||
};
|
||||
|
||||
registerDecoder(TYPE.TOP, function(v, state) {
|
||||
let top = kiri.newTop(decode(v.poly, state));
|
||||
if (state.full) {
|
||||
// top.gaps = decode(v.gaps, state);
|
||||
top.last = decode(v.last, state);
|
||||
top.shells = decode(v.shells, state);
|
||||
top.fill_off = decode(v.fill_off, state);
|
||||
top.fill_lines = decode(v.fill_lines, state);
|
||||
}
|
||||
return top;
|
||||
});
|
||||
|
||||
// ----- Polygon Codec -----
|
||||
|
||||
base.Polygon.prototype.encode = function(state) {
|
||||
if (!state.poly) state.poly = {};
|
||||
|
||||
let cached = state.poly[this.id];
|
||||
if (cached) {
|
||||
return { type: TYPE.POLY, ref: this.id };
|
||||
}
|
||||
|
||||
state.poly[this.id] = this;
|
||||
|
||||
return {
|
||||
type: TYPE.POLY,
|
||||
id: this.id,
|
||||
array: encodePointArray(this.points, state, this.z),
|
||||
inner: encode(this.inner, state),
|
||||
parent: encode(this.parent, state),
|
||||
depth: this.depth,
|
||||
color: this.color,
|
||||
open: this.open
|
||||
};
|
||||
};
|
||||
|
||||
registerDecoder(TYPE.POLY, function(v, state) {
|
||||
if (!state.poly) state.poly = {};
|
||||
|
||||
// return polygon from cached reference
|
||||
if (v.ref) {
|
||||
return state.poly[v.ref];
|
||||
}
|
||||
|
||||
const array = v.array;
|
||||
const length = array.length;
|
||||
const poly = base.newPolygon();
|
||||
|
||||
for (let vid = 0; vid < length; ) {
|
||||
poly.push(base.newPoint(array[vid++], array[vid++], array[vid++]));
|
||||
}
|
||||
|
||||
state.poly[v.id] = poly;
|
||||
|
||||
poly.id = v.id;
|
||||
poly.open = v.open;
|
||||
poly.inner = decode(v.inner, state);
|
||||
poly.parent = decode(v.parent, state);
|
||||
poly.depth = v.depth;
|
||||
poly.color = v.color;
|
||||
|
||||
return poly;
|
||||
});
|
||||
|
||||
// ----- Layers Codec -----
|
||||
|
||||
function encodeLayerPolys(polys, state) {
|
||||
return polys.map(poly => {
|
||||
return {
|
||||
open: poly.open,
|
||||
color: poly.color,
|
||||
points: codec.encodePointArray(poly.points, state.zeros)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
kiri.Layers.prototype.encode = function(state) {
|
||||
let zeros = state.zeros;
|
||||
let enc = {
|
||||
type: TYPE.LAYERS,
|
||||
layers: Object.keys(this.layers),
|
||||
data: Object.values(this.layers).map(layer => {
|
||||
const paths = layer.paths;
|
||||
return {
|
||||
// defaults
|
||||
off: layer.off,
|
||||
color: layer.color,
|
||||
// 1D lines and polylines
|
||||
polys: encodeLayerPolys(layer.polys, state),
|
||||
lines: encodePointArray(layer.lines, state),
|
||||
// 2D lines and flat areas
|
||||
faces: codec.allocFloat32Array(layer.faces, zeros),
|
||||
norms: codec.allocFloat32Array(layer.norms, zeros),
|
||||
cface: layer.cface || codec.undef,
|
||||
// 3D lines
|
||||
paths: paths ? {
|
||||
z: paths.z,
|
||||
index: paths.index,
|
||||
faces: codec.allocFloat32Array(paths.faces, zeros),
|
||||
norms: codec.allocFloat32Array(paths.norms, zeros)
|
||||
} : undefined,
|
||||
cpath: layer.cpath || codec.undef,
|
||||
rotation: layer.rotation,
|
||||
position: layer.position
|
||||
}
|
||||
})
|
||||
};
|
||||
// aggressively free memory
|
||||
if (freeMem) this.init();
|
||||
return enc;
|
||||
};
|
||||
|
||||
registerDecoder(TYPE.LAYERS, function(v, state) {
|
||||
const render = new kiri.Layers();
|
||||
const { layers } = v;
|
||||
|
||||
for (let i=0; i<layers.length; i++) {
|
||||
const data = v.data[i];
|
||||
const paths = data.paths;
|
||||
const d = render.layers[layers[i]] = {
|
||||
off: data.off,
|
||||
color: data.color,
|
||||
polys: data.polys,
|
||||
lines: data.lines,
|
||||
faces: codec.allocFloat32Array(data.faces),
|
||||
norms: codec.allocFloat32Array(data.norms),
|
||||
cface: data.cface,
|
||||
paths: paths ? {
|
||||
z: paths.z,
|
||||
index: paths.index,
|
||||
faces: codec.allocFloat32Array(paths.faces),
|
||||
norms: codec.allocFloat32Array(paths.norms)
|
||||
} : undefined,
|
||||
cpath: data.cpath,
|
||||
rotation: data.rotation,
|
||||
position: data.position
|
||||
};
|
||||
// fixup null -> Infinity in material counts (JSON stringify sucks)
|
||||
if (d.cface) {
|
||||
d.cface.forEach(rec => {
|
||||
if (rec.count === null) {
|
||||
rec.count = Infinity;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (d.cpath) {
|
||||
d.cpath.forEach(rec => {
|
||||
if (rec.count === null) {
|
||||
rec.count = Infinity;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return render;
|
||||
});
|
||||
|
||||
});
|
||||
994
src.old/kiri/conf.js
Normal file
994
src.old/kiri/conf.js
Normal file
|
|
@ -0,0 +1,994 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: add.array
|
||||
// dep: data.local
|
||||
gapp.register("kiri.conf", (root, exports) => {
|
||||
|
||||
const { data } = root;
|
||||
const { local } = data;
|
||||
const { clone } = Object;
|
||||
const CVER = 410;
|
||||
|
||||
function genID() {
|
||||
while (true) {
|
||||
let k = Math.round(Math.random() * 9999999999).toString(36);
|
||||
if (k.length >= 4 && k.length <= 8) return k;
|
||||
}
|
||||
}
|
||||
|
||||
// add fields to o(bject) from d(efault) that are missing
|
||||
// remove fields from o(bject) that don't exist in d(efault)
|
||||
function fill_cull_once(obj, def, debug) {
|
||||
if (!obj) return;
|
||||
// handle renaming
|
||||
for (let k in obj) {
|
||||
if (obj.hasOwnProperty(k)) {
|
||||
let nam = renamed[k] || k;
|
||||
if (nam !== k) {
|
||||
// handle field renames
|
||||
obj[nam] = obj[k];
|
||||
if (debug) console.log({rename: k, to: nam});
|
||||
delete obj[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
// fill missing
|
||||
for (let k in def) {
|
||||
if (def.hasOwnProperty(k)) {
|
||||
let okv = obj[k];
|
||||
if ((okv === undefined || okv === null)) {
|
||||
// handle fill
|
||||
if (debug) console.log({fill: k, val: def[k]});
|
||||
if (typeof def[k] === 'object') {
|
||||
obj[k] = clone(def[k]);
|
||||
} else {
|
||||
obj[k] = def[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove invalid
|
||||
for (let k in obj) {
|
||||
if (!def.hasOwnProperty(k)) {
|
||||
if (debug) console.log({cull: k});
|
||||
delete obj[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fill_cull_many(map, def) {
|
||||
forValues(map, (obj) => { fill_cull_once(obj, def) });
|
||||
}
|
||||
|
||||
function objectMap(o, fn) {
|
||||
for (let [key, val] of Object.entries(o)) {
|
||||
o[key] = fn(val) || val;
|
||||
}
|
||||
}
|
||||
|
||||
function valueOf(val, dv) {
|
||||
if (typeof(val) === 'string' && Array.isArray(dv)) {
|
||||
val = [val];
|
||||
}
|
||||
return typeof(val) !== 'undefined' ? val : dv;
|
||||
}
|
||||
|
||||
function forValues(o, fn) {
|
||||
Object.values(o).forEach(v => fn(v));
|
||||
}
|
||||
|
||||
// convert default filter (from server) into device structure
|
||||
function device_from_code(code,mode) {
|
||||
// presence of internal field indicates already converted
|
||||
if (code.internal >= 0) return code;
|
||||
|
||||
// if (self.navigator) console.log({mode, convert: code});
|
||||
let cmd = code.cmd || {},
|
||||
set = code.settings || {},
|
||||
ext = code.extruders;
|
||||
|
||||
// currently causes unecessary fills and culls because
|
||||
// it's not mode and device type sensitive
|
||||
let device = {
|
||||
noclone: valueOf(code.no_clone, false),
|
||||
mode: mode || code.mode || '',
|
||||
internal: 0,
|
||||
imageScale: valueOf(set.image_scale, 0.75),
|
||||
imageAnchor: valueOf(set.image_anchor, 0),
|
||||
bedHeight: valueOf(set.bed_height, 2.5),
|
||||
bedWidth: valueOf(set.bed_width, 300),
|
||||
bedDepth: valueOf(set.bed_depth, 175),
|
||||
bedRound: valueOf(set.bed_circle, false),
|
||||
bedBelt: valueOf(set.bed_belt, false),
|
||||
resolutionX: valueOf(set.resolution_x, 1600),
|
||||
resolutionY: valueOf(set.resolution_y, 900),
|
||||
deviceZMax: valueOf(set.z_move_max, 0),
|
||||
gcodeTime: valueOf(set.time_factor, 1),
|
||||
maxHeight: valueOf(set.build_height, 150),
|
||||
originCenter: valueOf(set.origin_center, false),
|
||||
extrudeAbs: valueOf(set.extrude_abs, false),
|
||||
spindleMax: valueOf(set.spindle_max, 0),
|
||||
gcodeFan: valueOf(cmd.fan_power || code.fan_power, []),
|
||||
gcodeFeature: valueOf(cmd.feature || code.feature, []),
|
||||
gcodeTrack: valueOf(cmd.progress || code.progress, []),
|
||||
gcodeLayer: valueOf(cmd.layer || code.layer, []),
|
||||
gcodePre: valueOf(code.pre, []),
|
||||
gcodePost: valueOf(code.post, []),
|
||||
// post processor script of which only one exists
|
||||
// for XYZ.daVinci.Mini.w triggered in kiri.export
|
||||
// in the fdm driver to turn gcode into base64
|
||||
gcodeProc: valueOf(code.proc, ''),
|
||||
gcodeDwell: valueOf(code.dwell, []),
|
||||
gcodeSpindle: valueOf(code.spindle || cmd.spindle, []),
|
||||
gcodeChange: valueOf(code['tool-change'], []),
|
||||
gcodeFExt: valueOf(code['file-ext'], 'gcode'),
|
||||
gcodeSpace: valueOf(code['token-space'], true),
|
||||
gcodeStrip: valueOf(code['strip-comments'], false),
|
||||
gcodeLaserOn: valueOf(code['laser-on'], []),
|
||||
gcodeLaserOff: valueOf(code['laser-off'], []),
|
||||
extruders: []
|
||||
};
|
||||
|
||||
if (ext) {
|
||||
// synthesize extruders from new style settings
|
||||
ext.forEach(rec => {
|
||||
let e = clone(conf.defaults.fdm.d.extruders[0]);
|
||||
if (rec.nozzle) e.extNozzle = rec.nozzle;
|
||||
if (rec.filament) e.extFilament = rec.filament;
|
||||
if (rec.offset_x) e.extOffsetX = rec.offset_x;
|
||||
if (rec.offset_y) e.extOffsetY = rec.offset_y;
|
||||
device.extruders.push(e);
|
||||
});
|
||||
} else {
|
||||
// synthesize extruders from old style settings
|
||||
device.extruders = [ clone(conf.defaults.fdm.d.extruders[0]) ];
|
||||
device.extruders[0].extNozzle = valueOf(set.nozzle_size, 0.4);
|
||||
device.extruders[0].extFilament = valueOf(set.filament_diameter, 1.75);
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
// ensure settings structure is up-to-date
|
||||
function normalize(settings) {
|
||||
let defaults = conf.defaults,
|
||||
template = conf.template,
|
||||
mode = settings.mode.toLowerCase(),
|
||||
default_dev = defaults[mode].d,
|
||||
default_pro = defaults[mode].p;
|
||||
|
||||
// fixup old/new detail settings
|
||||
let detail = settings.controller.detail;
|
||||
settings.controller.detail = {
|
||||
"best": "100",
|
||||
"good": "75",
|
||||
"fair": "50",
|
||||
"poor": "25"
|
||||
}[detail] || detail;
|
||||
|
||||
fill_cull_once(settings, template);
|
||||
fill_cull_once(settings.device, default_dev);
|
||||
fill_cull_once(settings.process, default_pro);
|
||||
fill_cull_once(settings.cdev, template.cdev);
|
||||
fill_cull_once(settings.cproc, template.cproc);
|
||||
fill_cull_once(settings.sproc, template.sproc);
|
||||
fill_cull_once(settings.defaults, template.defaults);
|
||||
fill_cull_once(settings.cdev.FDM, defaults.fdm.d);
|
||||
fill_cull_once(settings.cdev.SLA, defaults.sla.d);
|
||||
fill_cull_once(settings.cdev.CAM, defaults.cam.d);
|
||||
fill_cull_once(settings.cdev.LASER, defaults.laser.d);
|
||||
fill_cull_many(settings.sproc.FDM, defaults.fdm.p);
|
||||
fill_cull_many(settings.sproc.SLA, defaults.sla.p);
|
||||
fill_cull_many(settings.sproc.CAM, defaults.cam.p);
|
||||
fill_cull_many(settings.sproc.LASER, defaults.laser.p);
|
||||
fill_cull_once(settings.controller, template.controller);
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
// auto field renaming on import
|
||||
const renamed = {
|
||||
roughingTool: "camRoughTool",
|
||||
roughingSpindle: "camRoughSpindle",
|
||||
roughingDown: "camRoughDown",
|
||||
roughingOver: "camRoughOver",
|
||||
roughingSpeed: "camRoughSpeed",
|
||||
roughingPlunge: "camRoughPlunge",
|
||||
roughingStock: "camRoughStock",
|
||||
roughingPocket: "camRoughVoid",
|
||||
roughingOn: "camRoughOn",
|
||||
finishingTool: "camOutlineTool",
|
||||
finishingSpindle: "camOutlineSpindle",
|
||||
finishingDown: "camOutlineDown",
|
||||
finishingOver: "camContourOver",
|
||||
finishingSpeed: "camOutlineSpeed",
|
||||
finishingPlunge: "camOutlinePlunge",
|
||||
finishingOn: "camOutlineOn",
|
||||
finishingXOn: "camContourXOn",
|
||||
finishingYOn: "camContourYOn",
|
||||
drillTool: "camDrillTool",
|
||||
drillSpindle: "camDrillSpindle",
|
||||
drillDownSpeed: "camDrillDownSpeed",
|
||||
drillDown: "camDrillDown",
|
||||
drillDwell: "camDrillDwell",
|
||||
drillLift: "camDrillLift",
|
||||
drillingOn: "camDrillingOn",
|
||||
camWideCutout: "camOutlineWide",
|
||||
outputClockwise: "camConventional"
|
||||
};
|
||||
|
||||
const conf = exports({
|
||||
// --------------- helper functions
|
||||
normalize,
|
||||
device_from_code,
|
||||
fill_cull_once,
|
||||
// --------------- device and process defaults
|
||||
defaults: {
|
||||
fdm:{
|
||||
// device defaults FDM:Device
|
||||
d:{
|
||||
new: true,
|
||||
mode: "",
|
||||
deviceName: "",
|
||||
imageURL: "",
|
||||
internal: 0,
|
||||
bedWidth: 300,
|
||||
bedDepth: 175,
|
||||
bedHeight: 2.5,
|
||||
bedRound: false,
|
||||
bedBelt: false,
|
||||
fwRetract: false,
|
||||
filamentSource: "direct",
|
||||
originCenter: false,
|
||||
deviceZMax: 0,
|
||||
maxHeight: 150,
|
||||
gcodeTime: 1,
|
||||
gcodeChange: ["T{tool}"],
|
||||
gcodePre: [],
|
||||
gcodePost: [],
|
||||
gcodeProc: "",
|
||||
gcodeFan: [],
|
||||
gcodeFeature: [],
|
||||
gcodeTrack: [],
|
||||
gcodeLayer: [],
|
||||
gcodeFExt: "",
|
||||
extruders:[{
|
||||
extFilament: 1.75,
|
||||
extNozzle: 0.4,
|
||||
extOffsetX: 0,
|
||||
extOffsetY: 0
|
||||
}],
|
||||
profiles: [],
|
||||
// other stored config info
|
||||
extras: {}
|
||||
},
|
||||
// process defaults FDM:Process
|
||||
p:{
|
||||
processName: "default",
|
||||
sliceAngle: 45,
|
||||
sliceHeight: 0.25,
|
||||
sliceShells: 3,
|
||||
sliceShellOrder: "in-out",
|
||||
sliceLayerStart: "last",
|
||||
sliceLayerStartX: 0,
|
||||
sliceLayerStartY: 0,
|
||||
sliceLineWidth: 0,
|
||||
sliceFillGrow: 0,
|
||||
sliceFillAngle: 45,
|
||||
sliceFillWidth: 1,
|
||||
sliceFillOverlap: 0.35,
|
||||
sliceFillSparse: 0.25,
|
||||
sliceFillRepeat: 1,
|
||||
sliceFillRate: 0,
|
||||
sliceSolidRate: 0,
|
||||
sliceFillType: "hex",
|
||||
sliceSupportDensity: 0.1,
|
||||
sliceSupportOffset: 1.0,
|
||||
sliceSupportGap: 1,
|
||||
sliceSupportSize: 5,
|
||||
sliceSupportArea: 0.1,
|
||||
sliceSupportSpan: 5,
|
||||
sliceSupportGrow: 0,
|
||||
sliceSupportExtra: 0,
|
||||
sliceSupportAngle: 50,
|
||||
sliceSupportNozzle: 0,
|
||||
sliceSupportEnable: false,
|
||||
sliceSupportOutline: true,
|
||||
sliceZInterleave: false,
|
||||
sliceSolidMinArea: 1,
|
||||
sliceBottomLayers: 3,
|
||||
sliceTopLayers: 3,
|
||||
firstSliceHeight: 0.25,
|
||||
firstLayerRate: 30,
|
||||
firstLayerFillRate: 35,
|
||||
firstLayerPrintMult: 1.0,
|
||||
firstLayerLineMult: 1.0,
|
||||
firstLayerYOffset: 0,
|
||||
firstLayerNozzleTemp: 0,
|
||||
firstLayerBedTemp: 0,
|
||||
firstLayerBrim: 0,
|
||||
firstLayerBrimIn: 0,
|
||||
firstLayerBrimTrig: 0,
|
||||
firstLayerBrimGap: 0,
|
||||
firstLayerBeltLead: 3,
|
||||
firstLayerBeltBump: 0,
|
||||
firstLayerFanSpeed: 0,
|
||||
firstLayerFlatten: 0,
|
||||
outputRaft: false,
|
||||
outputRaftSpacing: 0.2,
|
||||
outputDraftShield: false,
|
||||
outputTemp: 200,
|
||||
outputBedTemp: 60,
|
||||
outputFeedrate: 50,
|
||||
outputFinishrate: 50,
|
||||
outputSeekrate: 80,
|
||||
outputShellMult: 1.25,
|
||||
outputFillMult: 1.25,
|
||||
outputSparseMult: 1.25,
|
||||
outputFanLayer: 1,
|
||||
outputFanSpeed: 255,
|
||||
outputRetractDist: 1.5,
|
||||
outputRetractSpeed: 40,
|
||||
outputRetractWipe: 0,
|
||||
outputRetractDwell: 20,
|
||||
outputBrimCount: 2,
|
||||
outputBrimOffset: 2,
|
||||
outputShortPoly: 100.0,
|
||||
outputMinSpeed: 10.0,
|
||||
outputCoastDist: 0,
|
||||
outputPurgeTower: 0,
|
||||
outputBeltFirst: false,
|
||||
outputAvoidGaps: true,
|
||||
outputAlternating: false,
|
||||
outputLayerRetract: false,
|
||||
outputLoops: 0,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
sliceDetectThin: "off",
|
||||
sliceMinHeight: 0,
|
||||
sliceAdaptive: false,
|
||||
zHopDistance: 0.2,
|
||||
arcTolerance: 0,
|
||||
antiBacklash: 0,
|
||||
ranges: []
|
||||
}
|
||||
},
|
||||
sla:{
|
||||
// device defaults SLA:Device
|
||||
d:{
|
||||
new: true,
|
||||
mode: "",
|
||||
deviceName: "",
|
||||
imageURL: "",
|
||||
noclone: false,
|
||||
internal: 0,
|
||||
bedWidth: 150,
|
||||
bedDepth: 150,
|
||||
bedHeight: 1.5,
|
||||
maxHeight: 150,
|
||||
resolutionX: 1600,
|
||||
resolutionY: 900
|
||||
},
|
||||
// process defaults SLA:Process
|
||||
p:{
|
||||
processName: "default",
|
||||
slaSlice: 0.05,
|
||||
slaShell: 0.00,
|
||||
slaOpenTop: false,
|
||||
slaOpenBase: false,
|
||||
slaAntiAlias: 1,
|
||||
slaLayerOff: 0.1,
|
||||
slaLayerOn: 7,
|
||||
slaPeelDist: 6,
|
||||
slaPeelLiftRate: 1.5,
|
||||
slaPeelDropRate: 3,
|
||||
slaBaseLayers: 5,
|
||||
slaBaseOff: 0.1,
|
||||
slaBaseOn: 30,
|
||||
slaBasePeelDist: 6,
|
||||
slaBasePeelLiftRate: 1.5,
|
||||
slaFillDensity: 0,
|
||||
slaFillLine: 0.5,
|
||||
slaFirstOffset: 0,
|
||||
slaSupportLayers: 10,
|
||||
slaSupportDensity: 0.5,
|
||||
slaSupportSize: 0.6,
|
||||
slaSupportPoints: 4,
|
||||
slaSupportGap: 10,
|
||||
slaSupportEnable: false
|
||||
}
|
||||
},
|
||||
cam:{
|
||||
// device defaults CAM:Device
|
||||
d:{
|
||||
new: true,
|
||||
mode: "",
|
||||
deviceName: "",
|
||||
imageURL: "",
|
||||
internal: 0,
|
||||
bedWidth: 300,
|
||||
bedDepth: 175,
|
||||
bedHeight: 2.5,
|
||||
maxHeight: 300,
|
||||
useLaser: false,
|
||||
originCenter: false,
|
||||
spindleMax: 0,
|
||||
gcodePre: [],
|
||||
gcodePost: [],
|
||||
gcodeFExt: "",
|
||||
gcodeSpace: true,
|
||||
gcodeStrip: true,
|
||||
gcodeDwell: ["G4 P{time}"],
|
||||
gcodeChange: ["M6 T{tool}"],
|
||||
gcodeSpindle: ["M3 S{speed}"]
|
||||
},
|
||||
// process defaults CAM:Process
|
||||
p:{
|
||||
processName: "default",
|
||||
camLevelTool: 1000,
|
||||
camLevelSpindle: 1000,
|
||||
camLevelOver: 0.75,
|
||||
camLevelSpeed: 1000,
|
||||
camLevelDown: 0,
|
||||
camLevelOver: 0.5,
|
||||
camLevelStock: true,
|
||||
camRoughTool: 1000,
|
||||
camRoughSpindle: 1000,
|
||||
camRoughDown: 2,
|
||||
camRoughOver: 0.4,
|
||||
camRoughSpeed: 1000,
|
||||
camRoughPlunge: 250,
|
||||
camRoughStock: 0,
|
||||
camRoughStockZ: 0,
|
||||
camRoughAll: true,
|
||||
camRoughVoid: false,
|
||||
camRoughFlat: true,
|
||||
camRoughTop: true,
|
||||
camRoughIn: true,
|
||||
camRoughOn: true,
|
||||
camRoughOmitVoid: false,
|
||||
camOutlineTool: 1000,
|
||||
camOutlineSpindle: 1000,
|
||||
camOutlineTop: true,
|
||||
camOutlineDown: 3,
|
||||
camOutlineOver: 0.4,
|
||||
camOutlineOverCount: 1,
|
||||
camOutlineSpeed: 800,
|
||||
camOutlinePlunge: 250,
|
||||
camOutlineWide: false,
|
||||
camOutlineDogbone: false,
|
||||
camOutlineOmitThru: false,
|
||||
camOutlineOmitVoid: false,
|
||||
camOutlineOut: true,
|
||||
camOutlineIn: false,
|
||||
camOutlineOn: true,
|
||||
camContourTool: 1000,
|
||||
camContourSpindle: 1000,
|
||||
camContourOver: 0.5,
|
||||
camContourSpeed: 1000,
|
||||
camContourAngle: 85,
|
||||
camContourLeave: 0,
|
||||
camContourReduce: 2,
|
||||
camContourBottom: false,
|
||||
camContourCurves: false,
|
||||
camContourIn: false,
|
||||
camContourXOn: true,
|
||||
camContourYOn: true,
|
||||
camLatheTool: 1000,
|
||||
camLatheSpindle: 1000,
|
||||
camLatheOver: 0.1,
|
||||
camLatheAngle: 1,
|
||||
camLatheSpeed: 500,
|
||||
camLatheLinear: true,
|
||||
camTolerance: 0,
|
||||
camTraceTool: 1000,
|
||||
camTraceSpindle: 1000,
|
||||
camTraceType: "follow",
|
||||
camTraceOver: 0.5,
|
||||
camTraceDown: 0,
|
||||
camTraceThru: 0,
|
||||
camTraceSpeed: 250,
|
||||
camTracePlunge: 200,
|
||||
camTraceOffOver: 0,
|
||||
camTraceDogbone: false,
|
||||
camTraceMerge: false,
|
||||
camTraceLines: false,
|
||||
camTraceZTop: 0,
|
||||
camTraceZBottom: 0,
|
||||
camPocketSpindle: 1000,
|
||||
camPocketTool: 1000,
|
||||
camPocketOver: 0.25,
|
||||
camPocketDown: 1,
|
||||
camPocketSpeed: 250,
|
||||
camPocketPlunge: 200,
|
||||
camPocketExpand: 0,
|
||||
camPocketSmooth: 0,
|
||||
camPocketRefine: 20,
|
||||
camPocketFollow: 5,
|
||||
camPocketContour: false,
|
||||
camPocketEngrave: false,
|
||||
camPocketOutline: false,
|
||||
camPocketZTop: 0,
|
||||
camPocketZBottom: 0,
|
||||
camDrillTool: 1006,
|
||||
camDrillSpindle: 1000,
|
||||
camDrillDownSpeed: 250,
|
||||
camDrillDown: 5,
|
||||
camDrillDwell: 250,
|
||||
camDrillLift: 2,
|
||||
camDrillMark: false,
|
||||
camDrillFromStockTop: false,
|
||||
camDrillThru: 5,
|
||||
camDrillPrecision: 1,
|
||||
camDrillingOn: false,
|
||||
camRegisterSpeed: 1000,
|
||||
camRegisterThru: 5,
|
||||
camFlipAxis: "X",
|
||||
camFlipOther: "",
|
||||
camLaserEnable: ["M321"],
|
||||
camLaserDisable: ["M322"],
|
||||
camLaserOn: ["M3"],
|
||||
camLaserOff: ["M5"],
|
||||
camLaserSpeed: 100,
|
||||
camLaserPower: 1,
|
||||
camLaserAdaptive: false,
|
||||
camLaserAdaptMod: false,
|
||||
camLaserFlatten: false,
|
||||
camLaserFlatZ: 0,
|
||||
camLaserPowerMin: 0,
|
||||
camLaserPowerMax: 1,
|
||||
camLaserZMin: 0,
|
||||
camLaserZMax: 0,
|
||||
camTabsWidth: 5,
|
||||
camTabsHeight: 5,
|
||||
camTabsDepth: 5,
|
||||
camTabsMidline: false,
|
||||
camDepthFirst: false,
|
||||
camEaseDown: false,
|
||||
camEaseAngle: 10,
|
||||
camOriginTop: true,
|
||||
camZAnchor: "middle",
|
||||
camZOffset: 0,
|
||||
camZTop: 0,
|
||||
camZBottom: 0,
|
||||
camZClearance: 1,
|
||||
camZThru: 0,
|
||||
camFastFeed: 6000,
|
||||
camFastFeedZ: 300,
|
||||
camTolerance: 0,
|
||||
camFlatness: 0.001,
|
||||
camContourBridge: 0,
|
||||
camStockX: 5,
|
||||
camStockY: 5,
|
||||
camStockZ: 5,
|
||||
camStockOffset: true,
|
||||
camStockClipTo: false,
|
||||
camStockIndexed: false,
|
||||
camStockIndexGrid: true,
|
||||
camIndexAxis: 0,
|
||||
camIndexAbs: true,
|
||||
camConventional: false, // outputClockwise
|
||||
camOriginCenter: false,
|
||||
camOriginOffX: 0,
|
||||
camOriginOffY: 0,
|
||||
camOriginOffZ: 0,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
camExpertFast: false,
|
||||
camTrueShadow: false,
|
||||
camArcTolerance: 0.006,
|
||||
camArcResolution: 5,
|
||||
camForceZMax: false,
|
||||
camFirstZMax: false,
|
||||
camToolInit: true,
|
||||
camFullEngage: 0.8,
|
||||
ops: [], // current ops
|
||||
op2: [] // flip ops
|
||||
}
|
||||
},
|
||||
laser: {
|
||||
// device defaults Laser:Device
|
||||
d:{
|
||||
new: true,
|
||||
mode: "",
|
||||
deviceName: "",
|
||||
imageURL: "",
|
||||
internal: 0,
|
||||
bedWidth: 300,
|
||||
bedDepth: 200,
|
||||
bedHeight: 2.5,
|
||||
maxHeight: 100,
|
||||
laserMaxPower: 255,
|
||||
gcodePre: [],
|
||||
gcodePost: [],
|
||||
gcodeFExt: "",
|
||||
gcodeSpace: true,
|
||||
gcodeLaserOn: ["M106 S{power}"],
|
||||
gcodeLaserOff: ["M107"]
|
||||
},
|
||||
// process defaults Laser:Process
|
||||
p:{
|
||||
processName: "default",
|
||||
ctSliceKerf: 0.1,
|
||||
ctSliceHeight: 1,
|
||||
ctSliceHeightMin: 0,
|
||||
ctSliceSingle: true,
|
||||
ctOutTileSpacing: 1,
|
||||
ctOutPower: 100,
|
||||
ctOutSpeed: 1000,
|
||||
ctOutGroup: true,
|
||||
ctOutZColor: false,
|
||||
ctOutLayer: false,
|
||||
ctOutMark: false,
|
||||
ctOutStack: false,
|
||||
ctOutMerged: false,
|
||||
ctOriginCenter: true,
|
||||
ctOriginBounds: false,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
ctOutKnifeDepth: 1,
|
||||
ctOutKnifePasses: 1,
|
||||
ctOutKnifeTip: 2,
|
||||
ctOutInches: false,
|
||||
ctOutShaper: false
|
||||
}
|
||||
},
|
||||
drag: {
|
||||
// device defaults Drag:Device
|
||||
d:{
|
||||
new: true,
|
||||
mode: "",
|
||||
deviceName: "",
|
||||
imageURL: "",
|
||||
internal: 0,
|
||||
bedWidth: 300,
|
||||
bedDepth: 200,
|
||||
bedHeight: 2.5,
|
||||
maxHeight: 100,
|
||||
gcodePre: [],
|
||||
gcodePost: [],
|
||||
gcodeFExt: "",
|
||||
gcodeSpace: true,
|
||||
gcodeKnifeDn: [";; Z down"],
|
||||
gcodeKnifeUp: [";; Z up"]
|
||||
},
|
||||
// process defaults Drag:Process
|
||||
p:{
|
||||
processName: "default",
|
||||
ctSliceKerf: 0.1,
|
||||
ctSliceHeight: 1,
|
||||
ctSliceHeightMin: 0,
|
||||
ctSliceSingle: true,
|
||||
ctOutTileSpacing: 1,
|
||||
ctOutPower: 100,
|
||||
ctOutSpeed: 1000,
|
||||
ctOutGroup: true,
|
||||
ctOutZColor: false,
|
||||
ctOutLayer: false,
|
||||
ctOutMark: false,
|
||||
ctOutMerged: false,
|
||||
ctOriginCenter: true,
|
||||
ctOriginBounds: false,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
ctOutKnifeDepth: 1,
|
||||
ctOutKnifePasses: 1,
|
||||
ctOutKnifeTip: 2,
|
||||
ctOutStack: true,
|
||||
},
|
||||
},
|
||||
wjet: {
|
||||
// device defaults WaterJet:Device
|
||||
d:{
|
||||
new: true,
|
||||
mode: "",
|
||||
deviceName: "",
|
||||
imageURL: "",
|
||||
internal: 0,
|
||||
bedWidth: 300,
|
||||
bedDepth: 200,
|
||||
bedHeight: 2.5,
|
||||
maxHeight: 100,
|
||||
gcodePre: [],
|
||||
gcodePost: [],
|
||||
gcodeFExt: "",
|
||||
gcodeSpace: true,
|
||||
gcodeWaterOn: ["M106 S{power}"],
|
||||
gcodeWaterOff: ["M107"]
|
||||
},
|
||||
// process defaults WaterJet:Process
|
||||
p:{
|
||||
processName: "default",
|
||||
ctSliceKerf: 0.1,
|
||||
ctSliceHeight: 1,
|
||||
ctSliceHeightMin: 0,
|
||||
ctSliceSingle: true,
|
||||
ctOutTileSpacing: 1,
|
||||
ctOutPower: 100,
|
||||
ctOutSpeed: 1000,
|
||||
ctOutGroup: true,
|
||||
ctOutZColor: false,
|
||||
ctOutLayer: false,
|
||||
ctOutMark: false,
|
||||
ctOutMerged: false,
|
||||
ctOriginCenter: true,
|
||||
ctOriginBounds: false,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
ctOutKnifeDepth: 1,
|
||||
ctOutKnifePasses: 1,
|
||||
ctOutKnifeTip: 2,
|
||||
ctOutStack: false,
|
||||
},
|
||||
},
|
||||
wedm: {
|
||||
// device defaults WireEDM:Device
|
||||
d:{
|
||||
new: true,
|
||||
mode: "",
|
||||
deviceName: "",
|
||||
imageURL: "",
|
||||
internal: 0,
|
||||
bedWidth: 300,
|
||||
bedDepth: 200,
|
||||
bedHeight: 2.5,
|
||||
maxHeight: 100,
|
||||
gcodePre: [],
|
||||
gcodePost: [],
|
||||
gcodeFExt: "",
|
||||
gcodeSpace: true
|
||||
},
|
||||
// process defaults WireEDM:Process
|
||||
p:{
|
||||
processName: "default",
|
||||
ctSliceKerf: 0.1,
|
||||
ctSliceHeight: 1,
|
||||
ctSliceHeightMin: 0,
|
||||
ctSliceSingle: true,
|
||||
ctOmitInner: false,
|
||||
ctOutTileSpacing: 1,
|
||||
ctOutPower: 100,
|
||||
ctOutSpeed: 1000,
|
||||
ctOutGroup: true,
|
||||
ctOutZColor: false,
|
||||
ctOutLayer: false,
|
||||
ctOutMark: false,
|
||||
ctOutMerged: false,
|
||||
ctOriginCenter: false,
|
||||
ctOriginBounds: true,
|
||||
ctOriginOffX: 0,
|
||||
ctOriginOffY: 0,
|
||||
outputInvertX: false,
|
||||
outputInvertY: false,
|
||||
ctOutStack: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
// --------------- settings template
|
||||
template: {
|
||||
bounds: {},
|
||||
// CAM only
|
||||
origin: {},
|
||||
stock: {},
|
||||
tools:[
|
||||
{
|
||||
id: 1000,
|
||||
number: 1,
|
||||
type: "endmill",
|
||||
name: "end 1/4",
|
||||
metric: false,
|
||||
shaft_diam: 0.25,
|
||||
shaft_len: 1,
|
||||
flute_diam: 0.25,
|
||||
flute_len: 2,
|
||||
taper_tip: 0,
|
||||
},
|
||||
{
|
||||
id: 1001,
|
||||
number: 2,
|
||||
type: "endmill",
|
||||
name: "end 1/8",
|
||||
metric: false,
|
||||
shaft_diam: 0.125,
|
||||
shaft_len: 1,
|
||||
flute_diam: 0.125,
|
||||
flute_len: 1.5,
|
||||
taper_tip: 0,
|
||||
},
|
||||
{
|
||||
id: 1002,
|
||||
number: 3,
|
||||
type: "endmill",
|
||||
name: "end 1/16",
|
||||
metric: false,
|
||||
shaft_diam: 0.0625,
|
||||
shaft_len: 1,
|
||||
flute_diam: 0.0625,
|
||||
flute_len: 1.5,
|
||||
taper_tip: 0,
|
||||
},
|
||||
{
|
||||
id: 1003,
|
||||
number: 4,
|
||||
type: "tapermill",
|
||||
name: "vee 1/8",
|
||||
metric: true,
|
||||
shaft_diam: 0.125,
|
||||
shaft_len: 1,
|
||||
flute_diam: 0.125,
|
||||
flute_len: 1.5,
|
||||
taper_angle: 5.3,
|
||||
taper_tip: 0,
|
||||
},
|
||||
{
|
||||
id: 1004,
|
||||
number: 5,
|
||||
type: "ballmill",
|
||||
name: "ball 1/8",
|
||||
metric: false,
|
||||
shaft_diam: 0.125,
|
||||
shaft_len: 1,
|
||||
flute_diam: 0.125,
|
||||
flute_len: 1.5,
|
||||
taper_tip: 0,
|
||||
},
|
||||
{
|
||||
id: 1005,
|
||||
number: 6,
|
||||
type: "drill",
|
||||
name: "drill 1/8",
|
||||
metric: false,
|
||||
shaft_diam: 0.125,
|
||||
shaft_len: 1,
|
||||
flute_diam: 0.125,
|
||||
flute_len: 1.5,
|
||||
taper_tip: 0,
|
||||
},
|
||||
{
|
||||
id: 1006,
|
||||
number: 7,
|
||||
type: "drill",
|
||||
name: "drill 1/4",
|
||||
metric: false,
|
||||
shaft_diam: 0.25,
|
||||
shaft_len: 1,
|
||||
flute_diam: 0.25,
|
||||
flute_len: 2,
|
||||
taper_tip: 0,
|
||||
}
|
||||
],
|
||||
// currently selected device (current mode)
|
||||
device:{},
|
||||
// currently selected process (current mode)
|
||||
process:{},
|
||||
// current process (name of last used) by mode
|
||||
cproc:{
|
||||
FDM: "default",
|
||||
SLA: "default",
|
||||
CAM: "default",
|
||||
DRAG: "default",
|
||||
WJET: "default",
|
||||
WEDM: "default",
|
||||
LASER: "default",
|
||||
},
|
||||
// stored process (copy of last used) by mode
|
||||
sproc:{
|
||||
FDM: {},
|
||||
SLA: {},
|
||||
CAM: {},
|
||||
DRAG: {},
|
||||
WJET: {},
|
||||
WEDM: {},
|
||||
LASER: {},
|
||||
},
|
||||
// current device (name of last used) by mode
|
||||
filter:{
|
||||
FDM: "Any.Generic.Marlin",
|
||||
SLA: "Anycubic.Photon",
|
||||
CAM: "Any.Generic.Grbl",
|
||||
DRAG: "Any.Generic.DragKnife",
|
||||
WJET: "HydroBLADE",
|
||||
WEDM: "RackRobo.Betta.Wire.V1",
|
||||
LASER: "Any.Generic.Laser",
|
||||
},
|
||||
// current (last used) device by mode
|
||||
cdev: {
|
||||
FDM: null,
|
||||
SLA: null,
|
||||
CAM: null,
|
||||
LASER: null,
|
||||
DRAG: null,
|
||||
},
|
||||
// custom devices by name (all modes)
|
||||
devices:{},
|
||||
// map of device name to last process setting name
|
||||
devproc: {},
|
||||
// application ui and control preferences (Q menu)
|
||||
controller:{
|
||||
view: null,
|
||||
dark: false,
|
||||
shiny: false,
|
||||
drawer: false,
|
||||
scrolls: true,
|
||||
zoomSpeed: 1.0,
|
||||
lineType: "path",
|
||||
autoSave: true,
|
||||
antiAlias: true,
|
||||
reverseZoom: true,
|
||||
showOrigin: false,
|
||||
showRulers: true,
|
||||
showSpeeds: true,
|
||||
freeLayout: true,
|
||||
autoLayout: true,
|
||||
spaceRandoX: false,
|
||||
spaceLayout: 1,
|
||||
edgeangle: 20,
|
||||
units: "mm",
|
||||
exportOcto: false,
|
||||
exportGhost: false,
|
||||
exportLocal: false,
|
||||
exportThumb: false,
|
||||
exportPreview: false,
|
||||
detail: "50",
|
||||
animesh: "800",
|
||||
healMesh: false,
|
||||
threaded: true,
|
||||
assembly: false,
|
||||
ortho: false,
|
||||
devel: false
|
||||
},
|
||||
// default hidden ui groups
|
||||
hidden: {
|
||||
"fdm-base": true,
|
||||
"fdm-cool": true,
|
||||
"fdm-fill": true,
|
||||
"fdm-supp": true,
|
||||
"fdm-xprt": true
|
||||
},
|
||||
// label state preferences (slice/preview toggles)
|
||||
labels: {
|
||||
'CAM-3-arrows': false,
|
||||
'FDM-3-engage': false,
|
||||
'FDM-3-retract': false,
|
||||
'FDM-3-arrows': false,
|
||||
'FDM-3-move': false
|
||||
},
|
||||
// for passing temporary slice hints (topo currently)
|
||||
synth: {},
|
||||
// widget extra info for slicing (extruder mapping)
|
||||
widget: {},
|
||||
// legacy localStorage settings (like octo print)
|
||||
local: {
|
||||
'model.edges': false,
|
||||
'cam.anim.trans': true,
|
||||
'cam.anim.model': false,
|
||||
'cam.anim.stock': false,
|
||||
'cam.anim.speed': 3
|
||||
},
|
||||
mode: 'FDM',
|
||||
id: genID(),
|
||||
ver: CVER
|
||||
}
|
||||
});
|
||||
|
||||
const settings = conf.template;
|
||||
|
||||
// seed defaults. will get culled on save
|
||||
settings.sproc.FDM.default = clone(settings.process);
|
||||
settings.sproc.SLA.default = clone(settings.process);
|
||||
settings.sproc.CAM.default = clone(settings.process);
|
||||
settings.sproc.LASER.default = clone(settings.process);
|
||||
settings.sproc.DRAG.default = clone(settings.process);
|
||||
settings.sproc.WJET.default = clone(settings.process);
|
||||
settings.sproc.WEDM.default = clone(settings.process);
|
||||
settings.cdev.FDM = clone(settings.device);
|
||||
settings.cdev.SLA = clone(settings.device);
|
||||
settings.cdev.CAM = clone(settings.device);
|
||||
settings.cdev.LASER = clone(settings.device);
|
||||
settings.cdev.DRAG = clone(settings.device);
|
||||
settings.cdev.WJET = clone(settings.device);
|
||||
settings.cdev.WEDM = clone(settings.device);
|
||||
|
||||
});
|
||||
160
src.old/kiri/consts.js
Normal file
160
src.old/kiri/consts.js
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri.consts", (root, exports) => {
|
||||
|
||||
const COLOR = {
|
||||
wireframe: 0x444444,
|
||||
wireframe_opacity: 0.25,
|
||||
selected: [ 0xbbff00, 0xbbee00, 0xbbdd00, 0xbb9900 ],
|
||||
deselected: [ 0xffff00, 0xffdd00, 0xffbb00, 0xff9900 ],
|
||||
model_opacity: 1.0,
|
||||
preview_opacity: 0.0,
|
||||
slicing: 0xffaaaa,
|
||||
slicing_opacity: 0.5,
|
||||
sliced_opacity: 0.0,
|
||||
cam_preview: 0x888888,
|
||||
cam_preview_opacity_dark: 0.2,
|
||||
cam_preview_opacity: 0.1,
|
||||
cam_sliced_opacity: 0.2
|
||||
};
|
||||
|
||||
const LISTS = {
|
||||
shell: [
|
||||
{ name: "in-out" },
|
||||
{ name: "out-in" },
|
||||
{ name: "alternate" }
|
||||
],
|
||||
start: [
|
||||
{ name: "last" },
|
||||
{ name: "center" },
|
||||
{ name: "origin" },
|
||||
{ name: "random" },
|
||||
],
|
||||
infill: [
|
||||
{ name: "none" },
|
||||
{ name: "hex" },
|
||||
{ name: "grid" },
|
||||
// { name: "cubic" },
|
||||
{ name: "linear" },
|
||||
{ name: "triangle" },
|
||||
{ name: "gyroid" },
|
||||
{ name: "vase" }
|
||||
],
|
||||
units: [
|
||||
{ name: "mm" },
|
||||
{ name: "in" }
|
||||
],
|
||||
antialias: [
|
||||
{ name: "1", id: 1 },
|
||||
{ name: "2", id: 2 },
|
||||
{ name: "4", id: 4 },
|
||||
{ name: "8", id: 8 }
|
||||
],
|
||||
detail: [
|
||||
{ name: "100" },
|
||||
{ name: "75" },
|
||||
{ name: "50" },
|
||||
{ name: "25" },
|
||||
],
|
||||
linetype: [
|
||||
{ name: "path" },
|
||||
{ name: "flat" },
|
||||
{ name: "line" }
|
||||
],
|
||||
animesh: [
|
||||
{ name: "100" },
|
||||
{ name: "200" },
|
||||
{ name: "300" },
|
||||
{ name: "400" },
|
||||
{ name: "500" },
|
||||
{ name: "600" },
|
||||
{ name: "700" },
|
||||
{ name: "800" },
|
||||
{ name: "900" },
|
||||
{ name: "1000" },
|
||||
{ name: "1500" },
|
||||
{ name: "2000" },
|
||||
{ name: "2500" },
|
||||
{ name: "3000" },
|
||||
{ name: "4000" },
|
||||
],
|
||||
select: [
|
||||
{ name: "loops" },
|
||||
{ name: "lines" },
|
||||
// { name: "surface" }
|
||||
],
|
||||
trace: [
|
||||
{ name: "follow" },
|
||||
{ name: "clear" }
|
||||
],
|
||||
traceoff: [
|
||||
{ name: "none" },
|
||||
{ name: "inside" },
|
||||
{ name: "outside" }
|
||||
],
|
||||
zanchor: [
|
||||
{ name: "top" },
|
||||
{ name: "middle" },
|
||||
{ name: "bottom" }
|
||||
],
|
||||
xyaxis: [
|
||||
{ name: "X" },
|
||||
{ name: "Y" }
|
||||
],
|
||||
regaxis: [
|
||||
{ name: "X" },
|
||||
{ name: "Y" },
|
||||
{ name: "-" }
|
||||
],
|
||||
regpoints: [
|
||||
{ name: "2" },
|
||||
{ name: "3" }
|
||||
],
|
||||
thin: [
|
||||
{ name: "off" },
|
||||
{ name: "type 1" },
|
||||
{ name: "type 2" },
|
||||
{ name: "type 3" }
|
||||
]
|
||||
};
|
||||
|
||||
// primary device mode
|
||||
const MODES = {
|
||||
FDM: 1, // fused deposition modeling (also FFF)
|
||||
LASER: 2, // laser cutters (base for all 2d device types)
|
||||
CAM: 3, // 3 axis milling/machining
|
||||
SLA: 4, // cured resin printers
|
||||
DRAG: 5, // drag knife (a variation on laser / 2d)
|
||||
WJET: 6, // waterjet (a varation on laser / 2d)
|
||||
WEDM: 7 // wire-edm (a variation on laser / 2d)
|
||||
};
|
||||
|
||||
// view mode within device mode
|
||||
const VIEWS = {
|
||||
ARRANGE: 1,
|
||||
SLICE: 2,
|
||||
PREVIEW: 3,
|
||||
ANIMATE: 4
|
||||
};
|
||||
|
||||
// preview modes
|
||||
const PMODES = {
|
||||
SPEED: 1,
|
||||
TOOLS: 2
|
||||
};
|
||||
|
||||
const SEED = 'kiri-seed';
|
||||
|
||||
exports({
|
||||
PMODES,
|
||||
COLOR,
|
||||
LISTS,
|
||||
MODES,
|
||||
VIEWS,
|
||||
SEED,
|
||||
beltfact: Math.cos(Math.PI / 4)
|
||||
});
|
||||
|
||||
});
|
||||
32
src.old/kiri/conv.js
Normal file
32
src.old/kiri/conv.js
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// device legacy format converter
|
||||
|
||||
const arg = process.argv.slice(2);
|
||||
const mode = arg.shift();
|
||||
const umode = mode.toUpperCase();
|
||||
const lmode = mode.toLowerCase();
|
||||
|
||||
const fs = require('fs');
|
||||
const self = {};
|
||||
|
||||
eval( fs.readFileSync("src/main/gapp.js").toString() );
|
||||
const gapp = self.gapp;
|
||||
|
||||
eval( fs.readFileSync("src/add/array.js").toString() );
|
||||
eval( fs.readFileSync("src/data/local.js").toString() );
|
||||
eval( fs.readFileSync("src/kiri/conf.js").toString() );
|
||||
|
||||
gapp.main(undefined, undefined, root => {
|
||||
let kiri = root.kiri;
|
||||
let conf = kiri.conf;
|
||||
|
||||
for (let file of arg) {
|
||||
let code = JSON.parse(fs.readFileSync(file).toString());
|
||||
let devi = conf.device_from_code(code, umode);
|
||||
conf.fill_cull_once(devi, conf.defaults[lmode].d);
|
||||
delete devi.new;
|
||||
delete devi.internal;
|
||||
delete devi.deviceName;
|
||||
// console.log(JSON.stringify(code, undefined, 4));
|
||||
console.log(JSON.stringify([code, "-----", devi], undefined, 2));
|
||||
}
|
||||
});
|
||||
393
src.old/kiri/devices.js
Normal file
393
src.old/kiri/devices.js
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.api
|
||||
// dep: kiri.settings
|
||||
gapp.register("kiri.devices", (root, exports) => {
|
||||
|
||||
let { kiri } = root,
|
||||
{ api, conf } = kiri;
|
||||
|
||||
// extend API
|
||||
Object.assign(api.show, {
|
||||
devices: showDevices
|
||||
});
|
||||
|
||||
Object.assign(api.device, {
|
||||
clone: cloneDevice,
|
||||
code: currentDeviceCode,
|
||||
get: currentDeviceName,
|
||||
set: selectDevice,
|
||||
isBelt
|
||||
});
|
||||
|
||||
Object.assign(api.devices, {
|
||||
show: showDevices,
|
||||
select: selectDevice,
|
||||
refresh: updateDeviceList,
|
||||
update_laser_state: updateLaserState
|
||||
});
|
||||
|
||||
function isBelt() {
|
||||
return api.conf.get().device.bedBelt;
|
||||
}
|
||||
|
||||
function currentDeviceName() {
|
||||
return api.conf.get().filter[api.mode.get()];
|
||||
}
|
||||
|
||||
function currentDeviceCode() {
|
||||
return api.conf.get().devices[currentDeviceName()];
|
||||
}
|
||||
|
||||
function getModeDevices() {
|
||||
// devices are injected into self scope by
|
||||
// app.js generateDevices()
|
||||
return Object.keys(devices[api.mode.get_lower()]).sort();
|
||||
}
|
||||
|
||||
function showDevices() {
|
||||
api.settings.sync.get().then(_showDevices);
|
||||
}
|
||||
|
||||
function _showDevices() {
|
||||
updateDeviceList();
|
||||
api.modal.show('setup');
|
||||
}
|
||||
|
||||
function updateDeviceList() {
|
||||
renderDevices(getModeDevices());
|
||||
}
|
||||
|
||||
function updateDeviceName(newname) {
|
||||
let selected = api.device.get(),
|
||||
devs = api.conf.get().devices;
|
||||
if (newname !== selected) {
|
||||
devs[newname] = devs[selected];
|
||||
delete devs[selected];
|
||||
selectDevice(newname);
|
||||
updateDeviceList();
|
||||
}
|
||||
}
|
||||
|
||||
function putLocalDevice(devicename, obj) {
|
||||
api.conf.get().devices[devicename] = obj;
|
||||
api.conf.save();
|
||||
}
|
||||
|
||||
function removeLocalDevice(devicename) {
|
||||
delete api.conf.get().devices[devicename];
|
||||
api.conf.save();
|
||||
api.settings.sync.put();
|
||||
}
|
||||
|
||||
function isLocalDevice(devicename) {
|
||||
return api.conf.get().devices[devicename] ? true : false;
|
||||
}
|
||||
|
||||
function getSelectedDevice() {
|
||||
return api.device.get();
|
||||
}
|
||||
|
||||
function selectDevice(devicename) {
|
||||
if (isLocalDevice(devicename)) {
|
||||
setDeviceCode(api.conf.get().devices[devicename], devicename);
|
||||
} else {
|
||||
let code = devices[api.mode.get_lower()][devicename];
|
||||
if (code) {
|
||||
setDeviceCode(code, devicename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only for local filters
|
||||
function cloneDevice() {
|
||||
let name = `${getSelectedDevice().replace(/\./g,' ')}`;
|
||||
let code = api.clone(api.conf.get().device);
|
||||
code.mode = api.mode.get();
|
||||
if (name.toLowerCase().indexOf('my ') >= 0) {
|
||||
name = `${name} copy`;
|
||||
} else {
|
||||
name = `My ${name}`;
|
||||
}
|
||||
putLocalDevice(name, code);
|
||||
setDeviceCode(code, name);
|
||||
api.settings.sync.put();
|
||||
}
|
||||
|
||||
function updateLaserState() {
|
||||
const dev = api.conf.get().device;
|
||||
$('laser-on').style.display = dev.useLaser ? 'flex' : 'none';
|
||||
$('laser-off').style.display = dev.useLaser ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function setDeviceCode(code, devicename) {
|
||||
api.event.emit('device.select', devicename);
|
||||
try {
|
||||
if (typeof(code) === 'string') code = js2o(code) || {};
|
||||
|
||||
let mode = api.mode.get(),
|
||||
lmode = mode.toLowerCase(),
|
||||
current = api.conf.get(),
|
||||
local = isLocalDevice(devicename),
|
||||
dev = current.device = conf.device_from_code(code,mode),
|
||||
dproc = current.devproc[devicename], // last process name for this device
|
||||
newdev = dproc === undefined, // first time device is selected
|
||||
predev = current.filter[mode], // previous device selection
|
||||
chgdev = predev !== devicename; // device is changing
|
||||
|
||||
// fill missing device fields
|
||||
conf.fill_cull_once(dev, conf.defaults[lmode].d);
|
||||
|
||||
// first time device use, add any print profiles and set to default if present
|
||||
if (code.profiles) {
|
||||
for (let profile of code.profiles) {
|
||||
let profname = profile.processName;
|
||||
// if no saved profile by that name for this mode...
|
||||
if (!current.sproc[mode][profname]) {
|
||||
console.log('adding profile', profname, 'to', mode);
|
||||
current.sproc[mode][profname] = profile;
|
||||
}
|
||||
// if it's a new device, seed the new profile name as last profile
|
||||
if (newdev && !current.devproc[devicename]) {
|
||||
console.log('setting default profile for new device', devicename, 'to', profname);
|
||||
current.devproc[devicename] = dproc = profname;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dev.new = false;
|
||||
dev.deviceName = devicename;
|
||||
|
||||
let { platform, ui, uc } = api;
|
||||
let { space } = kiri;
|
||||
|
||||
ui.deviceBelt.checked = dev.bedBelt;
|
||||
ui.deviceRound.checked = dev.bedRound;
|
||||
ui.deviceOrigin.checked = dev.ctOriginCenter || dev.originCenter || dev.bedRound;
|
||||
ui.fwRetract.checked = dev.fwRetract;
|
||||
|
||||
// add extruder selection buttons
|
||||
if (dev.extruders) {
|
||||
let ext = api.lists.extruders = [];
|
||||
dev.internal = 0;
|
||||
for (let i=0; i<dev.extruders.length; i++) {
|
||||
ext.push({id:i, name:i});
|
||||
}
|
||||
}
|
||||
|
||||
// disable editing for non-local devices
|
||||
[
|
||||
// ui.deviceName,
|
||||
ui.gcodePre,
|
||||
ui.gcodePost,
|
||||
ui.bedDepth,
|
||||
ui.bedWidth,
|
||||
ui.maxHeight,
|
||||
ui.useLaser,
|
||||
ui.resolutionX,
|
||||
ui.resolutionY,
|
||||
ui.deviceOrigin,
|
||||
ui.deviceRound,
|
||||
ui.deviceBelt,
|
||||
ui.fwRetract,
|
||||
ui.deviceZMax,
|
||||
ui.gcodeTime,
|
||||
ui.gcodeFan,
|
||||
ui.gcodeFeature,
|
||||
ui.gcodeTrack,
|
||||
ui.gcodeLayer,
|
||||
ui.extFilament,
|
||||
ui.extNozzle,
|
||||
ui.spindleMax,
|
||||
ui.gcodeSpindle,
|
||||
ui.gcodeDwell,
|
||||
ui.gcodeChange,
|
||||
ui.gcodeFExt,
|
||||
ui.gcodeSpace,
|
||||
ui.gcodeStrip,
|
||||
ui.gcodeLaserOn,
|
||||
ui.gcodeLaserOff,
|
||||
ui.laserMaxPower,
|
||||
ui.extPrev,
|
||||
ui.extNext,
|
||||
ui.extAdd,
|
||||
ui.extDel,
|
||||
ui.extOffsetX,
|
||||
ui.extOffsetY
|
||||
].forEach(function(e) {
|
||||
e.disabled = !local;
|
||||
});
|
||||
|
||||
ui.deviceSave.disabled = !local;
|
||||
ui.deviceDelete.disabled = !local;
|
||||
ui.deviceRename.disabled = !local;
|
||||
ui.deviceExport.disabled = !local;
|
||||
ui.deviceAdd.style.display = mode === 'SLA' ? 'none' : '';
|
||||
|
||||
if (local) {
|
||||
ui.deviceAdd.innerText = "copy";
|
||||
ui.deviceDelete.style.display = '';
|
||||
ui.deviceRename.style.display = '';
|
||||
ui.deviceExport.style.display = '';
|
||||
} else {
|
||||
ui.deviceAdd.innerText = "customize";
|
||||
ui.deviceDelete.style.display = 'none';
|
||||
ui.deviceRename.style.display = 'none';
|
||||
ui.deviceExport.style.display = 'none';
|
||||
}
|
||||
ui.deviceAdd.disabled = dev.noclone;
|
||||
|
||||
api.conf.update_fields();
|
||||
space.platform.setBelt(isBelt());
|
||||
platform.update_size();
|
||||
platform.update_origin();
|
||||
platform.update();
|
||||
updateLaserState();
|
||||
|
||||
// store current device name for this mode
|
||||
current.filter[mode] = devicename;
|
||||
// cache device record for this mode (restored in setMode)
|
||||
current.cdev[mode] = dev;
|
||||
|
||||
if (dproc) {
|
||||
// restore last process associated with this device
|
||||
api.conf.load(null, dproc);
|
||||
} else {
|
||||
api.conf.update();
|
||||
}
|
||||
|
||||
api.conf.save();
|
||||
|
||||
if (isBelt()) {
|
||||
// space.view.setHome(dev.bedBelt ? Math.PI/2 : 0, Math.PI / 2.5);
|
||||
space.view.setHome(0, Math.PI / 2.5);
|
||||
} else {
|
||||
space.view.setHome(0);
|
||||
}
|
||||
// when changing devices, update focus on widgets
|
||||
if (chgdev) {
|
||||
setTimeout(api.space.set_focus, 0);
|
||||
}
|
||||
|
||||
uc.refresh(1);
|
||||
api.event.emit('device.selected', dev);
|
||||
} catch (e) {
|
||||
console.log({error:e, device:code, devicename});
|
||||
api.show.alert(`invalid or deprecated device: "${devicename}"`, 10);
|
||||
api.show.alert(`please select a new device`, 10);
|
||||
throw e;
|
||||
showDevices();
|
||||
}
|
||||
api.function.clear();
|
||||
api.event.settings();
|
||||
}
|
||||
|
||||
function renderDevices(devices) {
|
||||
let selected = api.device.get() || devices[0],
|
||||
features = api.feature,
|
||||
devs = api.conf.get().devices,
|
||||
dfilter = typeof(features.device_filter) === 'function' ? features.device_filter : undefined;
|
||||
|
||||
for (let local in devs) {
|
||||
if (!(devs.hasOwnProperty(local) && devs[local])) {
|
||||
continue;
|
||||
}
|
||||
let dev = devs[local],
|
||||
fdmCode = dev.cmd,
|
||||
fdmMode = (api.mode.get() === 'FDM');
|
||||
if (dev.mode ? (dev.mode === api.mode.get()) : (fdmCode ? fdmMode : !fdmMode)) {
|
||||
devices.push(local);
|
||||
}
|
||||
};
|
||||
|
||||
devices = devices.sort();
|
||||
|
||||
let { event, ui } = api;
|
||||
|
||||
event.emit('devices.render', devices);
|
||||
|
||||
ui.deviceSave.onclick = function() {
|
||||
event.emit('device.save');
|
||||
api.function.clear();
|
||||
api.conf.save();
|
||||
api.settings.sync.put();
|
||||
showDevices();
|
||||
api.modal.hide();
|
||||
};
|
||||
ui.deviceAdd.onclick = function() {
|
||||
api.function.clear();
|
||||
cloneDevice();
|
||||
showDevices();
|
||||
};
|
||||
ui.deviceDelete.onclick = function() {
|
||||
api.function.clear();
|
||||
removeLocalDevice(getSelectedDevice());
|
||||
selectDevice(getModeDevices()[0]);
|
||||
showDevices();
|
||||
};
|
||||
ui.deviceRename.onclick = function() {
|
||||
api.uc.prompt(`Rename "${selected}`, selected).then(newname => {
|
||||
if (newname) {
|
||||
updateDeviceName(newname);
|
||||
api.conf.save();
|
||||
api.settings.sync.put();
|
||||
showDevices();
|
||||
} else {
|
||||
showDevices();
|
||||
}
|
||||
});
|
||||
};
|
||||
ui.deviceExport.onclick = function(event) {
|
||||
const record = {
|
||||
version: kiri.version,
|
||||
device: selected,
|
||||
process: api.process.code(),
|
||||
profiles: event.altKey ? api.settings.prof() : undefined,
|
||||
code: devs[selected],
|
||||
time: Date.now()
|
||||
};
|
||||
let exp = api.util.b64enc(record);
|
||||
api.device.export(exp, selected, { event, record });
|
||||
};
|
||||
|
||||
let dedup = {};
|
||||
let list_cdev = [];
|
||||
let list_mdev = [];
|
||||
devices.forEach(function(device, index) {
|
||||
// prevent device from appearing twice
|
||||
// such as local name = standard device name
|
||||
if (dedup[device]) {
|
||||
return;
|
||||
}
|
||||
dedup[device] = device;
|
||||
let loc = isLocalDevice(device);
|
||||
if (dfilter && dfilter(device) === false) {
|
||||
return;
|
||||
}
|
||||
if (loc) {
|
||||
list_mdev.push(h.option(device));
|
||||
} else {
|
||||
list_cdev.push(h.option(device));
|
||||
}
|
||||
});
|
||||
|
||||
let dev_list = $('dev-list');
|
||||
h.bind(dev_list, [
|
||||
h.option({ _: '-- My Devices --', disabled: true }),
|
||||
...list_mdev,
|
||||
h.option({ _: '-- Stock Devices --', disabled: true }),
|
||||
...list_cdev
|
||||
]);
|
||||
let dev_opts = [...dev_list.options].map(o => o.innerText);
|
||||
dev_list.selectedIndex = dev_opts.indexOf(selected);
|
||||
dev_list.onchange = ev => {
|
||||
const seldev = dev_list.options[dev_list.selectedIndex];
|
||||
selectDevice(seldev.innerText);
|
||||
api.platform.layout();
|
||||
}
|
||||
selectDevice(selected);
|
||||
}
|
||||
|
||||
});
|
||||
179
src.old/kiri/do.js
Normal file
179
src.old/kiri/do.js
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
// dep: kiri.api
|
||||
// use: kiri.selection
|
||||
// use: moto.space
|
||||
gapp.register("kiri.do", [], (root, events) => {
|
||||
|
||||
const { kiri, moto } = root;
|
||||
const { api } = kiri;
|
||||
const { space } = moto;
|
||||
const { event } = api;
|
||||
|
||||
let stack = [];
|
||||
let stpos = 0;
|
||||
let moved = { x: 0, y: 0 };
|
||||
let msgid;
|
||||
|
||||
event.on("init-done", () => {
|
||||
$('undo').onclick = undo;
|
||||
$('redo').onclick = redo;
|
||||
});
|
||||
|
||||
let undo = api.doit.undo = function() {
|
||||
if (stpos) {
|
||||
action(stack[--stpos].undo);
|
||||
} else {
|
||||
message('nothing to undo');
|
||||
}
|
||||
};
|
||||
|
||||
let redo = api.doit.redo = function() {
|
||||
if (stpos < stack.length) {
|
||||
action(stack[stpos++].redo);
|
||||
} else {
|
||||
message('nothing to redo');
|
||||
}
|
||||
};
|
||||
|
||||
let clear = api.doit.clear = function() {
|
||||
stack = [];
|
||||
stpos = 0;
|
||||
moved = {x:0, y:0};
|
||||
updateButtons();
|
||||
};
|
||||
|
||||
function updateButtons() {
|
||||
let isArrange = api.view.get() === kiri.consts.VIEWS.ARRANGE;
|
||||
$('doit').style.display = isArrange && stack.length ? 'flex' : 'none';
|
||||
$('undo').disabled = stpos === 0;
|
||||
$('redo').disabled = stpos == stack.length;
|
||||
}
|
||||
|
||||
function message(txt) {
|
||||
if (msgid) {
|
||||
api.hide.alert(msgid);
|
||||
}
|
||||
msgid = api.show.alert(txt);
|
||||
}
|
||||
|
||||
function action(rec) {
|
||||
switch (rec.type) {
|
||||
case 'move':
|
||||
for (let w of rec.widgets) {
|
||||
w._move(rec.dist.x, rec.dist.y, 0);
|
||||
}
|
||||
break;
|
||||
case 'rotate':
|
||||
for (let w of rec.widgets) {
|
||||
w.rotate(rec.x, rec.y, rec.z);
|
||||
}
|
||||
break;
|
||||
case 'scale':
|
||||
for (let w of rec.widgets) {
|
||||
w._scale(rec.x, rec.y, rec.z);
|
||||
}
|
||||
api.selection.update_info();
|
||||
break;
|
||||
}
|
||||
space.update();
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
function pushActions(ur) {
|
||||
stack.length = stpos++;
|
||||
stack.push(ur);
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
event.on([
|
||||
'platform.layout',
|
||||
'widget.add',
|
||||
'widget.delete'
|
||||
], (widget) => {
|
||||
clear();
|
||||
});
|
||||
|
||||
event.on('view.set', mode => {
|
||||
updateButtons();
|
||||
});
|
||||
|
||||
// selection event (store position)
|
||||
event.on('selection.rotate', (rec) => {
|
||||
let type = "rotate";
|
||||
let widgets = api.selection.widgets(true);
|
||||
if (!widgets.length) {
|
||||
return;
|
||||
}
|
||||
let {x, y, z} = rec;
|
||||
if (!(x || y || z)) {
|
||||
return;
|
||||
}
|
||||
pushActions({
|
||||
redo: {
|
||||
type,
|
||||
widgets,
|
||||
x, y, z
|
||||
},
|
||||
undo: {
|
||||
type,
|
||||
widgets,
|
||||
x: -x, y: -y, z: -z
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
event.on('selection.scale', (rec) => {
|
||||
let [x, y, z] = rec;
|
||||
// ignore scale close to 1
|
||||
if (Math.abs(1 - (x * y * z)) < 0.0001) {
|
||||
return;
|
||||
}
|
||||
let widgets = api.selection.widgets(true);
|
||||
if (!widgets.length) {
|
||||
return;
|
||||
}
|
||||
let type = "scale";
|
||||
pushActions({
|
||||
redo: {
|
||||
type,
|
||||
widgets,
|
||||
x, y, z
|
||||
},
|
||||
undo: {
|
||||
type,
|
||||
widgets,
|
||||
x: 1/x, y: 1/y, z: 1/z
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// selection moved (accumulator)
|
||||
event.on('selection.drag', (delta) => {
|
||||
moved.x += delta.x;
|
||||
moved.y += delta.y;
|
||||
});
|
||||
|
||||
// move complete (store updated position)
|
||||
event.on('mouse.drag.done', () => {
|
||||
let widgets = api.selection.widgets(true);
|
||||
if (!widgets.length) {
|
||||
return;
|
||||
}
|
||||
let type = "move";
|
||||
pushActions({
|
||||
redo: {
|
||||
type,
|
||||
widgets,
|
||||
dist: moved
|
||||
},
|
||||
undo: {
|
||||
type,
|
||||
widgets,
|
||||
dist: {x: -moved.x, y: -moved.y}
|
||||
}
|
||||
});
|
||||
moved = {x:0, y:0};
|
||||
});
|
||||
|
||||
});
|
||||
746
src.old/kiri/export.js
Normal file
746
src.old/kiri/export.js
Normal file
|
|
@ -0,0 +1,746 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: ext.md5
|
||||
// dep: geo.base
|
||||
// dep: data.local
|
||||
// dep: kiri.consts
|
||||
// dep: kiri.api
|
||||
// dep: kiri.main
|
||||
gapp.register("kiri.export", [], (root, exports) => {
|
||||
|
||||
const { base, data, kiri } = root;
|
||||
const { api, consts } = kiri;
|
||||
const { local } = data;
|
||||
const { util } = base;
|
||||
const { stats, ui } = api;
|
||||
const { MODES } = consts;
|
||||
|
||||
kiri.export = exportFile;
|
||||
|
||||
let printSeq = parseInt(local['kiri-print-seq'] || local['print-seq'] || "0") + 1;
|
||||
|
||||
function localGet(key) {
|
||||
return api.local.get(key);
|
||||
}
|
||||
|
||||
function localSet(key, val) {
|
||||
return api.local.set(key, val);
|
||||
}
|
||||
|
||||
function exportFile(options) {
|
||||
let mode = api.mode.get();
|
||||
let names = api.widgets.all().map(w => w.meta ? w.meta.file : undefined)
|
||||
.filter(v => v)
|
||||
.map(v => v.replace(/ /g,'-'))
|
||||
.map(v => v.substring(0,20))
|
||||
.map(v => v.split('.')[0]);
|
||||
api.event.emit('export', mode);
|
||||
switch (mode) {
|
||||
case 'WEDM':
|
||||
case 'WJET':
|
||||
case 'DRAG':
|
||||
case 'LASER': return callExportLaser(options, names);
|
||||
case 'FDM': return callExport(options, mode, names);
|
||||
case 'CAM': return callExport(options, mode, names);
|
||||
case 'SLA': return callExportSLA(options, names);
|
||||
}
|
||||
}
|
||||
|
||||
function callExport(callback, mode, names) {
|
||||
let alert = api.feature.work_alerts ? api.show.alert("Exporting") : null;
|
||||
let gcode = [];
|
||||
let section = [];
|
||||
let sections = { };
|
||||
kiri.client.export(api.conf.get(), (line) => {
|
||||
if (typeof line !== 'string') {
|
||||
if (line.section) {
|
||||
sections[line.section] = section = [];
|
||||
}
|
||||
return;
|
||||
}
|
||||
gcode.push(line);
|
||||
section.push(line);
|
||||
}, (output, error) => {
|
||||
api.hide.alert(alert);
|
||||
if (error) {
|
||||
api.show.alert(error, 5);
|
||||
} else if (callback) {
|
||||
callback(gcode.join('\r\n'), output);
|
||||
} else {
|
||||
exportGCodeDialog(gcode, mode === 'CAM' ? sections : undefined, output, names);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function callExportLaser(options, names) {
|
||||
kiri.client.export(api.conf.get(), (line) => {
|
||||
// engine export uses lines
|
||||
// console.log({unexpected_line: line});
|
||||
}, (output, error) => {
|
||||
// UI export uses output
|
||||
if (error) {
|
||||
api.show.alert(error, 5);
|
||||
} else {
|
||||
exportLaserDialog(output, names);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function callExportSLA(options, names) {
|
||||
kiri.client.export(api.conf.get(), (line) => {
|
||||
api.show.progress(line.progress, "exporting");
|
||||
}, (output, error) => {
|
||||
api.show.progress(0);
|
||||
if (error) {
|
||||
api.show.alert(error, 5);
|
||||
} else {
|
||||
kiri.driver.SLA.printDownload(output, api, names);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exportLaserDialog(data, names) {
|
||||
localSet('kiri-print-seq', printSeq++);
|
||||
|
||||
const fileroot = names[0] || "laser";
|
||||
const filename = `${fileroot}-${(printSeq.toString().padStart(3,"0"))}`;
|
||||
const settings = api.conf.get();
|
||||
const driver = kiri.driver.LASER;
|
||||
|
||||
function download_svg() {
|
||||
api.util.download(
|
||||
driver.exportSVG(settings, data),
|
||||
$('print-filename-laser').value + ".svg"
|
||||
);
|
||||
}
|
||||
|
||||
function download_dxf() {
|
||||
api.util.download(
|
||||
driver.exportDXF(settings, data),
|
||||
$('print-filename-laser').value + ".dxf"
|
||||
);
|
||||
}
|
||||
|
||||
function download_gcode() {
|
||||
api.util.download(
|
||||
driver.exportGCode(settings, data),
|
||||
$('print-filename-laser').value + ".gcode"
|
||||
);
|
||||
}
|
||||
|
||||
api.modal.show('xlaser');
|
||||
|
||||
let segments = 0;
|
||||
data.forEach(layer => { segments += layer.length });
|
||||
|
||||
$('print-filename-laser').value = filename;
|
||||
$('print-lines').value = util.comma(segments);
|
||||
$('print-svg').onclick = download_svg;
|
||||
$('print-dxf').onclick = download_dxf;
|
||||
$('print-lg').onclick = download_gcode;
|
||||
}
|
||||
|
||||
function bindField(field, varname) {
|
||||
$(field).onblur = function() {
|
||||
console.log('save', field, 'to', varname);
|
||||
localSet(varname, $(field).value.trim());
|
||||
};
|
||||
}
|
||||
|
||||
function exportGCodeDialog(gcode, sections, info, names) {
|
||||
localSet('kiri-print-seq', printSeq++);
|
||||
|
||||
let settings = api.conf.get(),
|
||||
MODE = api.mode.get_id(),
|
||||
name = names[0] || (MODE === MODES.CAM ? "cnc" : "print"),
|
||||
pre = `${name}-${(printSeq.toString().padStart(3,"0"))}`,
|
||||
filename = pre,// + (new Date().getTime().toString(36)),
|
||||
fileext = settings.device.gcodeFExt || "gcode",
|
||||
codeproc = settings.device.gcodeProc,
|
||||
octo_host,
|
||||
octo_apik,
|
||||
grid_host,
|
||||
grid_apik,
|
||||
grid_target,
|
||||
grid_targets = {},
|
||||
grid_local,
|
||||
grid_uuid;
|
||||
|
||||
// join gcode array into a string
|
||||
gcode = gcode.join('\r\n') + '\r\n';
|
||||
|
||||
// run gcode post processor function (when supplied and valid)
|
||||
if (codeproc && self[codeproc]) {
|
||||
gcode = self[codeproc](gcode);
|
||||
}
|
||||
|
||||
function getBlob() {
|
||||
return new Blob(
|
||||
[gcode],
|
||||
{type:"application/octet-stream"});
|
||||
}
|
||||
|
||||
function sendto_octoprint() {
|
||||
if (!(octo_host && octo_apik)) return;
|
||||
|
||||
let form = new FormData(),
|
||||
ajax = new XMLHttpRequest(),
|
||||
host = octo_host.value.toLowerCase(),
|
||||
apik = octo_apik.value;
|
||||
|
||||
if (host.indexOf("http") !== 0) {
|
||||
api.show.alert("host missing protocol (http:// or https://)");
|
||||
return;
|
||||
}
|
||||
if (api.const.SECURE && !api.util.isSecure(host)) {
|
||||
api.show.alert("host must begin with 'https' on a secure site");
|
||||
return;
|
||||
}
|
||||
|
||||
localSet('octo-host', host.trim());
|
||||
localSet('octo-apik', apik.trim());
|
||||
|
||||
filename = $('print-filename').value;
|
||||
form.append("file", getBlob(), filename+"."+fileext);
|
||||
ajax.onreadystatechange = function() {
|
||||
if (ajax.readyState === 4) {
|
||||
let status = ajax.status;
|
||||
stats.add(`ua_${api.mode.get_lower()}_print_octo_${status}`);
|
||||
if (status >= 200 && status < 300) {
|
||||
api.modal.hide();
|
||||
} else {
|
||||
api.show.alert("octoprint error\nstatus: "+status+"\nmessage: "+ajax.responseText);
|
||||
}
|
||||
api.show.progress(0);
|
||||
}
|
||||
api.show.progress(0);
|
||||
};
|
||||
ajax.upload.addEventListener('progress', function(evt) {
|
||||
api.show.progress(evt.loaded/evt.total, "sending");
|
||||
});
|
||||
ajax.open("POST", host+"/api/files/local");
|
||||
if (apik) {
|
||||
ajax.setRequestHeader("X-Api-Key", apik);
|
||||
}
|
||||
ajax.send(form);
|
||||
}
|
||||
|
||||
function gridhost_tracker(host,key) {
|
||||
ajax(host+"/api/check?key="+key, function(data) {
|
||||
data = js2o(data);
|
||||
console.log(data);
|
||||
if (!(data.done || data.error)) {
|
||||
setTimeout(function() { gridhost_tracker(host,key) }, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function gridlocal_probe(ev, devs) {
|
||||
if (ev && ev.code !== 'Enter') return;
|
||||
|
||||
if (!devs && api.probe.local(gridlocal_probe)) return;
|
||||
|
||||
grid_local = devs;
|
||||
|
||||
let gdev = localGet('grid-local');
|
||||
let gloc = $('grid-local');
|
||||
let html = [];
|
||||
for (let uuid in devs) {
|
||||
gdev = gdev || uuid;
|
||||
let dev = devs[uuid];
|
||||
let sel = uuid === gdev ? ' selected' : '';
|
||||
html.push(`<option id="gl-${uuid}" value="${uuid}" ${sel}>${dev.stat.device.name}</option>`);
|
||||
}
|
||||
|
||||
if (html.length) {
|
||||
gloc.innerHTML = html.join('\n');
|
||||
gloc.onchange = (ev) => {
|
||||
localSet('grid-local', gloc.options[gloc.selectedIndex].value);
|
||||
};
|
||||
$('send-to-gridhead').style.display = 'flex';
|
||||
$('send-to-gridspool').style.display = 'flex';
|
||||
} else {
|
||||
$('send-to-gridhead').style.display = '';
|
||||
$('send-to-gridspool').style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function sendto_gridlocal() {
|
||||
let uuid = $('grid-local').value;
|
||||
let dev = grid_local[uuid];
|
||||
if (dev) {
|
||||
let file = $('print-filename').value;
|
||||
fetch(
|
||||
`${api.probe.live}/api/grid_send?uuid=${uuid}&file=${encodeURIComponent(file + "." + fileext)}`,
|
||||
{method: "POST", body: gcode}
|
||||
)
|
||||
.then(t => t.text())
|
||||
.then(t => {
|
||||
stats.add(`ua_${api.mode.get_lower()}_print_local_ok`);
|
||||
console.log({grid_spool_said: t});
|
||||
})
|
||||
.catch(e => {
|
||||
stats.add(`ua_${api.mode.get_lower()}_print_local_err`);
|
||||
console.log({grid_local_spool_error: e});
|
||||
})
|
||||
.finally(() => {
|
||||
api.modal.hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function admin_gridlocal() {
|
||||
let dev = grid_local[$('grid-local').value];
|
||||
if (dev && dev.stat && dev.stat.device) {
|
||||
let dsd = dev.stat.device;
|
||||
window.open(`http://${dsd.addr[0]}:${dsd.port || 4080}`, "_grid_admin");
|
||||
}
|
||||
}
|
||||
|
||||
function gridhost_probe(ev, set_host) {
|
||||
if (ev && ev.code !== 'Enter') return;
|
||||
if (!(grid_host && grid_apik)) return;
|
||||
|
||||
if (set_host) grid_host.value = set_host;
|
||||
|
||||
let xhtr = new XMLHttpRequest(),
|
||||
host = grid_host.value,
|
||||
apik = grid_apik.value,
|
||||
target = grid_target.value;
|
||||
|
||||
if (!apik) $('gpapik').style.display = 'none';
|
||||
|
||||
if (!host && api.probe.grid(gridhost_probe)) return;
|
||||
|
||||
if (!host) return;
|
||||
|
||||
xhtr.onreadystatechange = function() {
|
||||
if (xhtr.readyState === 4) {
|
||||
if (xhtr.status >= 200 && xhtr.status < 300) {
|
||||
localSet('grid-host', host);
|
||||
localSet('grid-apik', apik);
|
||||
let res = JSON.parse(xhtr.responseText);
|
||||
let sel = false;
|
||||
let match = false;
|
||||
let first = null;
|
||||
let html = [];
|
||||
grid_targets = {};
|
||||
for (let key in res) {
|
||||
first = first || key;
|
||||
if (!localGet('grid-target')) {
|
||||
localSet('grid-target', key);
|
||||
sel = true;
|
||||
} else {
|
||||
sel = localGet('grid-target') === key;
|
||||
}
|
||||
match = match || sel;
|
||||
grid_targets[html.length] = key;
|
||||
html.push(
|
||||
"<option id='gpo-'" + key + " value='" +key + "'" +
|
||||
(sel ? " selected" : "") +
|
||||
">" +
|
||||
(res[key].comment || key) +
|
||||
"</option>"
|
||||
);
|
||||
}
|
||||
if (!match) {
|
||||
localSet('grid-target', first);
|
||||
}
|
||||
grid_target.innerHTML = html.join('\n');
|
||||
} else if (xhtr.status === 401) {
|
||||
$('gpapik').style.display = '';
|
||||
} else {
|
||||
local.removeItem('grid-host');
|
||||
local.removeItem('grid-apik');
|
||||
console.log("invalid grid:host url");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhtr.open("GET", host + "/api/active?key=" + apik);
|
||||
xhtr.send();
|
||||
}
|
||||
|
||||
function sendto_gridhost() {
|
||||
if (!(grid_host && grid_apik)) return;
|
||||
|
||||
let xhtr = new XMLHttpRequest(),
|
||||
host = grid_host.value,
|
||||
apik = grid_apik.value,
|
||||
target = localGet('grid-target') || '';
|
||||
|
||||
if (target === '') {
|
||||
api.show.alert('invalid or missing target');
|
||||
return;
|
||||
}
|
||||
if (host.indexOf("http") !== 0) {
|
||||
api.show.alert("host missing protocol (http:// or https://)");
|
||||
return;
|
||||
}
|
||||
if (host.indexOf("://") < 0) {
|
||||
api.show.alert("host:port malformed");
|
||||
return;
|
||||
}
|
||||
if (api.const.SECURE && !api.util.isSecure(host)) {
|
||||
api.show.alert("host must begin with 'https' on a secure site");
|
||||
return;
|
||||
}
|
||||
|
||||
localSet('grid-host', host.trim());
|
||||
localSet('grid-apik', apik.trim());
|
||||
|
||||
xhtr.onreadystatechange = function() {
|
||||
if (xhtr.readyState === 4) {
|
||||
let status = xhtr.status;
|
||||
stats.add(`ua_${api.mode.get_lower()}_print_grid_${status}`);
|
||||
if (status >= 200 && status < 300) {
|
||||
let json = js2o(xhtr.responseText);
|
||||
gridhost_tracker(host,json.key);
|
||||
api.ajax(host+"/api/wait?key="+json.key, function(data) {
|
||||
data = js2o(data);
|
||||
console.log(data);
|
||||
api.show.alert("print to "+target+": "+data.status, 600);
|
||||
});
|
||||
} else {
|
||||
api.show.alert("grid:host error\nstatus: "+status+"\nmessage: "+xhtr.responseText, 10000);
|
||||
}
|
||||
api.show.progress(0);
|
||||
}
|
||||
};
|
||||
xhtr.upload.addEventListener('progress', function(evt) {
|
||||
api.show.progress(evt.loaded/evt.total, "sending");
|
||||
});
|
||||
filename = $('print-filename').value;
|
||||
xhtr.open("POST",
|
||||
host + "/api/print?" +
|
||||
"filename=" + filename +
|
||||
"&target=" + target +
|
||||
"&key=" + apik +
|
||||
"&time=" + Math.round(info.time) +
|
||||
"&length=" + Math.round(info.distance) +
|
||||
"&image=" + filename
|
||||
);
|
||||
xhtr.setRequestHeader("Content-Type", "text/plain");
|
||||
let snapshot = api.view.snapshot;
|
||||
xhtr.send(snapshot ? [gcode,snapshot].join("\0") : gcode);
|
||||
api.modal.hide();
|
||||
}
|
||||
|
||||
function download() {
|
||||
filename = $('print-filename').value;
|
||||
api.util.download(gcode, filename + "." + fileext);
|
||||
// saveAs(getBlob(), filename + "." + fileext);
|
||||
}
|
||||
|
||||
function pad(v) {
|
||||
v = v.toString();
|
||||
return v.length < 2 ? '0' + v : v;
|
||||
}
|
||||
|
||||
function calcWeight() {
|
||||
try {
|
||||
let density = $('print-density');
|
||||
info.weight = (
|
||||
(Math.PI * util.sqr(
|
||||
info.settings.device.extruders[0].extFilament / 2
|
||||
)) *
|
||||
info.distance *
|
||||
(parseFloat(density.value) || 1.25) /
|
||||
1000
|
||||
).round(2);
|
||||
$('print-weight').value = info.weight.toFixed(2);
|
||||
density.onkeyup = (ev) => {
|
||||
if (ev.key === 'Enter') calcWeight();
|
||||
};
|
||||
} catch (e) { }
|
||||
}
|
||||
|
||||
function calcTime() {
|
||||
let floor = Math.floor,
|
||||
time = floor(info.time),
|
||||
hours = floor(time / 3600),
|
||||
newtime = time - hours * 3600,
|
||||
mins = floor(newtime / 60),
|
||||
secs = newtime - mins * 60;
|
||||
|
||||
$('output-time').value = [pad(hours),pad(mins),pad(secs)].join(':');
|
||||
}
|
||||
|
||||
api.modal.show('xany');
|
||||
let set = api.conf.get();
|
||||
let fdm = MODE === MODES.FDM;
|
||||
let octo = set.controller.exportOcto && MODE !== MODES.CAM;
|
||||
let ghost = set.controller.exportGhost;
|
||||
let local = set.controller.exportLocal;
|
||||
let preview = set.controller.exportPreview;
|
||||
$('code-preview-head').style.display = preview ? '' : 'none';
|
||||
$('code-preview').style.display = preview ? '' : 'none';
|
||||
$('print-download').onclick = download;
|
||||
$('print-filament').style.display = fdm ? '' : 'none';
|
||||
$('print-filename').value = filename;
|
||||
$('print-filesize').value = util.comma(info.bytes);
|
||||
$('print-filament').value = Math.round(info.distance);
|
||||
if (set.controller.devel) {
|
||||
$('code-preview-textarea').style.height = "30em";
|
||||
}
|
||||
calcTime();
|
||||
if (fdm) {
|
||||
calcWeight();
|
||||
}
|
||||
|
||||
// persist fields when changed
|
||||
bindField('octo-host', 'octo-host');
|
||||
bindField('octo-apik', 'octo-apik');
|
||||
bindField('grid-host', 'grid-host');
|
||||
bindField('grid-apik', 'grid-apik');
|
||||
|
||||
// in cam mode, show zip file option
|
||||
let downloadZip = $('print-zip');
|
||||
downloadZip.style.display = sections ? 'flex' : 'none';
|
||||
downloadZip.onclick = function() {
|
||||
let files = [];
|
||||
for (let [ name, data ] of Object.entries(sections)) {
|
||||
if (name.indexOf('op-') === 0) {
|
||||
let head = sections.header || [];
|
||||
let foot = sections.footer || [];
|
||||
files.push({
|
||||
name: `${name}.${fileext}`,
|
||||
data: [ ...head, ...data, ...foot ].join('\r\n')
|
||||
})
|
||||
}
|
||||
}
|
||||
kiri.client.zip(files, progress => {
|
||||
api.show.progress(progress.percent/100, "generating zip files");
|
||||
}, output => {
|
||||
api.show.progress(0);
|
||||
api.util.download(output, `${$('print-filename').value}.zip`);
|
||||
})
|
||||
};
|
||||
|
||||
// in fdm mode, show 3mf file option
|
||||
let nozzle0 = set.device?.extruders?.[0]?.extNozzle || 0.4;
|
||||
let download3MF = $('print-3mf');
|
||||
download3MF.style.display = fdm ? 'flex' : 'none';
|
||||
download3MF.onclick = function() {
|
||||
gen3mf(zip => api.util.download(zip, `${$('print-filename').value}.3mf`));
|
||||
};
|
||||
|
||||
// present bambu print options when selected device is bambu
|
||||
if (api.bambu) {
|
||||
api.bambu.prep_export(gen3mf, gcode, info, settings);
|
||||
}
|
||||
|
||||
// let wids = api.widgets.all();
|
||||
// let bnds = settings.bounds;
|
||||
// console.log({ wids, bnds });
|
||||
|
||||
function gen3mf(then, ptype = 'unknown', ams = [0]) {
|
||||
let now = new Date();
|
||||
let ymd = [
|
||||
now.getFullYear(),
|
||||
(now.getMonth() + 1).toString().padStart(2,0),
|
||||
now.getDate().toString().padStart(2,0),
|
||||
].join('-');
|
||||
let files = [{
|
||||
name: `[Content_Types].xml`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">',
|
||||
' <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>',
|
||||
' <Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>',
|
||||
' <Default Extension="gcode" ContentType="application/octet-stream"/>',
|
||||
'</Types>'
|
||||
].join('\n')
|
||||
},{
|
||||
name: `_rels/.rels`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
|
||||
' <Relationship Target="/3D/3dmodel.model" Id="rel-1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>',
|
||||
'</Relationships>'
|
||||
].join('\n')
|
||||
},{
|
||||
name: `3D/3dmodel.model`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<model unit="millimeter" xml:lang="en-US" xmlns="http://schemas.microsoft.com/3dmanufacturing/core/2015/02" xmlns:BambuStudio="http://schemas.bambulab.com/package/2021">',
|
||||
' <metadata name="Application">Kiri:Moto</metadata>',
|
||||
' <metadata name="Copyright"></metadata>',
|
||||
` <metadata name="CreationDate">${ymd}</metadata>`,
|
||||
' <metadata name="Description"></metadata>',
|
||||
' <metadata name="Designer"></metadata>',
|
||||
' <metadata name="DesignerCover"></metadata>',
|
||||
' <metadata name="License"></metadata>',
|
||||
` <metadata name="ModificationDate">${ymd}</metadata>`,
|
||||
' <metadata name="Origin"></metadata>',
|
||||
' <metadata name="Title"></metadata>',
|
||||
' <resources>',
|
||||
' </resources>',
|
||||
' <build/>',
|
||||
'</model>'
|
||||
].join('\n')
|
||||
},{
|
||||
name: `Metadata/_rels/model_settings.config.rels`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">',
|
||||
' <Relationship Target="/Metadata/plate_1.gcode" Id="rel-1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/gcode"/>',
|
||||
'</Relationships>'
|
||||
].join('\n')
|
||||
// },{
|
||||
// name: `Metadata/plate_1.json`,
|
||||
// data: JSON.stringify({
|
||||
// "bbox_all": [ 100, 100, 200, 200 ],
|
||||
// "bbox_objects": (info.labels || []).map(label => {
|
||||
// return {
|
||||
// area: 600,
|
||||
// bbox: [ 100, 100, 200, 200 ],
|
||||
// id: label,
|
||||
// layer_height: 0.2,
|
||||
// name: "Object"
|
||||
// }
|
||||
// }),
|
||||
// "bed_type": "textured_plate",
|
||||
// "filament_colors": ["#FFFFFF"],
|
||||
// "filament_ids": ams,
|
||||
// "first_extruder": ams[0],
|
||||
// "is_seq_print": false,
|
||||
// "nozzle_diameter": 0.6,
|
||||
// "version": 2
|
||||
// })
|
||||
},{
|
||||
name: `Metadata/model_settings.config`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<config>',
|
||||
' <plate>',
|
||||
' <metadata key="plater_id" value="1"/>',
|
||||
' <metadata key="plater_name" value=""/>',
|
||||
' <metadata key="locked" value="false"/>',
|
||||
' <metadata key="gcode_file" value="Metadata/plate_1.gcode"/>',
|
||||
' <metadata key="thumbnail_file" value="Metadata/plate_1.png"/>',
|
||||
' <metadata key="thumbnail_no_light_file" value="Metadata/plate_no_light_1.png"/>',
|
||||
' <metadata key="top_file" value="Metadata/top_1.png"/>',
|
||||
' <metadata key="pick_file" value="Metadata/pick_1.png"/>',
|
||||
' </plate>',
|
||||
'</config>'
|
||||
].join('\n')
|
||||
},{
|
||||
name: `Metadata/slice_info.config`,
|
||||
data: [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<config>',
|
||||
' <header>',
|
||||
' <header_item key="X-BBL-Client-Type" value="slicer"/>',
|
||||
' <header_item key="X-BBL-Client-Version" value="01.10.01.50"/>',
|
||||
' </header>',
|
||||
' <plate>',
|
||||
' <metadata key="index" value="1"/>',
|
||||
// setting this value allows for the printer to error out if
|
||||
// the gcode / 3mf was intended for a different target type.
|
||||
// leaving it blank bypasses the check
|
||||
// ` <metadata key="printer_model_id" value="${ptype}"/>`,
|
||||
` <metadata key="nozzle_diameters" value="${nozzle0}"/>`,
|
||||
' <metadata key="timelapse_type" value="0"/>',
|
||||
` <metadata key="prediction" value="${Math.round(info.time)}"/>`,
|
||||
` <metadata key="weight" value="${info.weight}"/>`,
|
||||
' <metadata key="outside" value="false"/>',
|
||||
' <metadata key="support_used" value="false"/>',
|
||||
' <metadata key="label_object_enabled" value="false"/>',
|
||||
// (info.labels || []).map(label =>
|
||||
// ` <object identify_id="${label}" name="Object" skipped="false" />`),
|
||||
// ' <filament id="1" tray_info_idx="GFL96" type="PLA" color="#FFFFFF" used_m="0.17" used_g="0.50" />',
|
||||
// ' <warning msg="bed_temperature_too_high_than_filament" level="1" error_code ="1000C001" />',
|
||||
' </plate>',
|
||||
'</config>'
|
||||
].filter(v => v).flat().join('\n')
|
||||
},{
|
||||
name: `Metadata/project_settings.config`,
|
||||
data: JSON.stringify({},undefined,4)
|
||||
},{
|
||||
name: `Metadata/plate_1.gcode`,
|
||||
data: gcode
|
||||
},{
|
||||
name: `Metadata/plate_1.gcode.md5`,
|
||||
data: ext.md5.hash(gcode)
|
||||
},{
|
||||
name: `Metadata/plate_1_small.png`,
|
||||
data: api.view.bambu.s128.png
|
||||
},{
|
||||
name: `Metadata/plate_no_light_1.png`,
|
||||
data: api.view.bambu.s512.png
|
||||
},{
|
||||
name: `Metadata/plate_1.png`,
|
||||
data: api.view.bambu.s512.png
|
||||
},{
|
||||
name: `Metadata/pick_1.png`,
|
||||
data: api.view.bambu.s512.png
|
||||
},{
|
||||
name: `Metadata/top_1.png`,
|
||||
data: api.view.bambu.s512.png
|
||||
}];
|
||||
kiri.client.zip(files, progress => {
|
||||
api.show.progress(progress.percent/100, "generating 3mf");
|
||||
}, output => {
|
||||
api.show.progress(0);
|
||||
then(output);
|
||||
})
|
||||
};
|
||||
|
||||
// octoprint setup
|
||||
$('send-to-octohead').style.display = octo ? '' : 'none';
|
||||
$('send-to-octoprint').style.display = octo ? '' : 'none';
|
||||
if (octo) try {
|
||||
$('print-octoprint').onclick = sendto_octoprint;
|
||||
octo_host = $('octo-host');
|
||||
octo_apik = $('octo-apik');
|
||||
if (MODE === MODES.CAM) {
|
||||
$('send-to-octohead').style.display = 'none';
|
||||
$('send-to-octoprint').style.display = 'none';
|
||||
} else {
|
||||
$('send-to-octohead').style.display = '';
|
||||
$('send-to-octoprint').style.display = '';
|
||||
}
|
||||
// hide octoprint when hard-coded in the url
|
||||
if (api.const.OCTO) {
|
||||
$('ophost').style.display = 'none';
|
||||
$('opapik').style.display = 'none';
|
||||
$('oph1nt').style.display = 'none';
|
||||
$('send-to-gridhost').style.display = 'none';
|
||||
}
|
||||
octo_host.value = localGet('octo-host') || '';
|
||||
octo_apik.value = localGet('octo-apik') || '';
|
||||
} catch (e) { console.log(e) }
|
||||
|
||||
// grid:host setup
|
||||
$('send-to-hosthead').style.display = ghost ? '' : 'none';
|
||||
$('send-to-gridhost').style.display = ghost ? '' : 'none';
|
||||
if (ghost) try {
|
||||
$('print-gridhost').onclick = sendto_gridhost;
|
||||
$('grid-host').onkeyup = gridhost_probe;
|
||||
$('grid-apik').onkeyup = gridhost_probe;
|
||||
grid_host = $('grid-host');
|
||||
grid_apik = $('grid-apik');
|
||||
grid_target = $('grid-target');
|
||||
grid_target.onchange = function(ev) {
|
||||
localSet('grid-target', grid_targets[grid_target.selectedIndex]);
|
||||
};
|
||||
grid_host.value = localGet('grid-host') || '';
|
||||
grid_apik.value = localGet('grid-apik') || '';
|
||||
gridhost_probe();
|
||||
} catch (e) { console.log(e) }
|
||||
|
||||
// grid:local setup
|
||||
if (local) try {
|
||||
gridlocal_probe();
|
||||
$('print-gridlocal').onclick = sendto_gridlocal;
|
||||
$('admin-gridlocal').onclick = admin_gridlocal;
|
||||
} catch (e) { console.log(e) }
|
||||
|
||||
// preview of the generated GCODE (first 64k max)
|
||||
if (preview && gcode) $('code-preview-textarea').value = gcode.substring(0,65535);
|
||||
}
|
||||
|
||||
});
|
||||
183
src.old/kiri/files.js
Normal file
183
src.old/kiri/files.js
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: main.kiri
|
||||
// use: data.index
|
||||
gapp.register("kiri.files", [], (root, exports) => {
|
||||
|
||||
const { kiri } = self;
|
||||
|
||||
class Files {
|
||||
constructor(indexdb) {
|
||||
let store = this;
|
||||
this.db = indexdb;
|
||||
this.files = {};
|
||||
this.listeners = [];
|
||||
this.deferredHandler = null;
|
||||
this.concurrent = 0;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
get index() {
|
||||
return this.db;
|
||||
}
|
||||
|
||||
refresh() {
|
||||
let store = this;
|
||||
store.db.get('files', function(files) {
|
||||
if (files) {
|
||||
store.files = files;
|
||||
notifyFileListeners(store);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
wipe() {
|
||||
let key, files = this.files;
|
||||
for (key in files) {
|
||||
if (files.hasOwnProperty(key)) this.deleteFile(key);
|
||||
}
|
||||
};
|
||||
|
||||
fileList() {
|
||||
return this.files;
|
||||
};
|
||||
|
||||
addFileListener(listener) {
|
||||
if (!this.listeners.contains(listener)) {
|
||||
this.listeners.push(listener);
|
||||
listener(this.files);
|
||||
}
|
||||
};
|
||||
|
||||
removeFileListener(listener) {
|
||||
this.listeners.remove(listener);
|
||||
};
|
||||
|
||||
setDeferredHandler(handler) {
|
||||
this.deferredHandler = handler;
|
||||
};
|
||||
|
||||
putDeferred(name, mark) {
|
||||
// triggers refresh callback
|
||||
this.files[name] = {
|
||||
deferred: mark
|
||||
};
|
||||
saveFileList(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @param {Float32Array} vertices
|
||||
* @param {Function} [callback]
|
||||
*/
|
||||
putFile(name, vertices, callback) {
|
||||
console.log({ putFile: name, vertices });
|
||||
|
||||
let mark = Date.now();
|
||||
let store = this;
|
||||
let pdb = store.db.promise();
|
||||
store.concurrent++;
|
||||
|
||||
function ondone(error) {
|
||||
if (callback) callback(error);
|
||||
if (--store.concurrent === 0) {
|
||||
notifyFileListeners(store);
|
||||
}
|
||||
}
|
||||
|
||||
pdb.put(`file-${name}`, vertices)
|
||||
.then(() => {
|
||||
store.files[name] = {
|
||||
vertices: vertices.length / 3,
|
||||
updated: new Date().getTime()
|
||||
};
|
||||
return pdb.put('files', store.files);
|
||||
})
|
||||
.then(ondone)
|
||||
.catch(ondone);
|
||||
};
|
||||
|
||||
rename(name, newname, callback) {
|
||||
if (!this.files[name]) return callback({error: 'no such file'});
|
||||
if (!newname || newname == name) return callback({error: 'invalid new name'});
|
||||
let done = 0;
|
||||
let error = [];
|
||||
let store = this;
|
||||
function complete(ok, err) {
|
||||
if (err) error.push(err);
|
||||
if (++done === 1) {
|
||||
store.files[newname] = store.files[name];
|
||||
delete store.files[name];
|
||||
saveFileList(store);
|
||||
store.db.remove(`file-${name}`);
|
||||
callback(error.length ? {error} : {});
|
||||
}
|
||||
}
|
||||
store.db.get(`file-${name}`, (vertices) => {
|
||||
if (!vertices) return complete(false, 'no raw file');
|
||||
store.db.put(`file-${newname}`, vertices, complete);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @param {Function} callback
|
||||
*/
|
||||
getFile(name, callback) {
|
||||
let store = this,
|
||||
rec = store.files[name];
|
||||
if (rec && rec.deferred) {
|
||||
if (store.deferredHandler) return store.deferredHandler(rec.deferred, name, callback);
|
||||
return callback();
|
||||
}
|
||||
this.db.get('file-'+name, function(vertices) {
|
||||
callback(vertices);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {String} name
|
||||
* @param {Function} callback
|
||||
*/
|
||||
deleteFile(name, callback) {
|
||||
let store = this;
|
||||
if (store.files[name]) {
|
||||
delete store.files[name];
|
||||
store.db.remove('file-'+name, function(ok) {
|
||||
saveFileList(store);
|
||||
if (callback) callback(ok);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (callback) callback(false);
|
||||
};
|
||||
|
||||
deleteFilter(fn, from, to) {
|
||||
this.db.keys(keys => {
|
||||
for (let key of keys) {
|
||||
if (fn(key)) {
|
||||
this.db.remove(key);
|
||||
}
|
||||
}
|
||||
}, from, to);
|
||||
}
|
||||
}
|
||||
|
||||
function saveFileList(store) {
|
||||
store.db.put('files', store.files);
|
||||
notifyFileListeners(store);
|
||||
}
|
||||
|
||||
function notifyFileListeners(store) {
|
||||
for (let i=0; i<store.listeners.length; i++) {
|
||||
store.listeners[i](store.files);
|
||||
}
|
||||
}
|
||||
|
||||
kiri.openFiles = function(indexdb) {
|
||||
return new Files(indexdb);
|
||||
};
|
||||
|
||||
});
|
||||
130
src.old/kiri/frame.js
Normal file
130
src.old/kiri/frame.js
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// use: load.stl
|
||||
// use: load.svg
|
||||
// use: kiri.api
|
||||
// use: kiri.platform
|
||||
// use: kiri.settings
|
||||
// use: kiri.widget
|
||||
gapp.register("kiri.frame", [], (root, exports) => {
|
||||
|
||||
// add frame message api listener
|
||||
window.addEventListener('message', msg => {
|
||||
const { load, kiri, moto } = self;
|
||||
const { api, newWidget } = kiri;
|
||||
const { conf, event, feature, platform, settings, show } = api;
|
||||
|
||||
if (!feature.frame) return;
|
||||
|
||||
const { origin, source, target, data } = msg;
|
||||
|
||||
if (source.window === target.window) return;
|
||||
|
||||
const send = source.window.postMessage;
|
||||
|
||||
if (data.mode) {
|
||||
api.mode.set(data.mode.toUpperCase());
|
||||
}
|
||||
|
||||
if (data.view) {
|
||||
api.view.set(VIEWS[data.view.toUpperCase()]);
|
||||
}
|
||||
|
||||
if (data.function) {
|
||||
const cb = data.callback ? (output) => {
|
||||
send({event:`${data.function}.done`, data: output});
|
||||
} : undefined;
|
||||
api.function[data.function.toLowerCase()](cb);
|
||||
}
|
||||
|
||||
if (data.event) {
|
||||
event.on(data.event, (evd) => {
|
||||
send({event: data.event, data: evd});
|
||||
});
|
||||
}
|
||||
|
||||
if (data.emit) {
|
||||
event.emit(data.emit, data.message)
|
||||
}
|
||||
|
||||
if (data.get) switch (data.get) {
|
||||
case "mode": send({mode: settings.mode()}); break;
|
||||
case "device": send({device: settings.dev()}); break;
|
||||
case "process": send({process: settings.proc()}); break;
|
||||
default: send({all: settings}); break;
|
||||
|
||||
}
|
||||
|
||||
if (data.features) {
|
||||
Object.assign(feature, data.features);
|
||||
api.show.controls(api.feature.controls);
|
||||
}
|
||||
|
||||
if (data.device) {
|
||||
Object.assign(settings.dev(), data.device);
|
||||
conf.save();
|
||||
}
|
||||
|
||||
if (data.process){
|
||||
Object.assign(settings.proc(), data.process);
|
||||
conf.save();
|
||||
}
|
||||
|
||||
if (data.controller){
|
||||
let ctrl = settings.ctrl();
|
||||
Object.assign(ctrl, data.controller);
|
||||
api.event.emit("set.threaded", ctrl.threaded);
|
||||
conf.save();
|
||||
}
|
||||
|
||||
if (data.parse) {
|
||||
let bin = data.parse;
|
||||
let widget;
|
||||
switch ((data.type || 'stl').toLowerCase()) {
|
||||
case 'stl':
|
||||
if (!bin.buffer) bin = new Float32Array(bin).buffer;
|
||||
new load.STL().parse(bin, vertices => {
|
||||
platform.add(widget = newWidget().loadVertices(vertices));
|
||||
send({event: "parsed", data: [ widget.id ]});
|
||||
});
|
||||
break;
|
||||
case 'obj':
|
||||
// todo
|
||||
break;
|
||||
case '3mf':
|
||||
// todo
|
||||
break;
|
||||
case 'svg':
|
||||
let wid = [];
|
||||
for (let svg of load.SVG.parse(bin)) {
|
||||
if (!(svg && svg.length)) continue;
|
||||
platform.add(widget = newWidget().loadVertices(svg.toFloat32()));
|
||||
wid.push(widget.id);
|
||||
}
|
||||
send({event: "parsed", data: wid});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.load) {
|
||||
platform.load(data.load, (verts, widget) => {
|
||||
send({event: "loaded", data: [ widget.id ]});
|
||||
})
|
||||
};
|
||||
|
||||
if (data.clear) {
|
||||
platform.clear();
|
||||
}
|
||||
|
||||
if (data.alert) {
|
||||
show.alert(data.alert, data.time);
|
||||
}
|
||||
|
||||
if (data.progress >= 0) {
|
||||
show.progress(data.progress, data.message);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
457
src.old/kiri/function.js
Normal file
457
src.old/kiri/function.js
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.api
|
||||
// dep: kiri.client
|
||||
// dep: kiri.export
|
||||
// dep: moto.space
|
||||
// use: kiri-mode.cam.client
|
||||
// dep: kiri-mode.fdm.client
|
||||
// dep: kiri-mode.sla.client
|
||||
// dep: kiri-mode.laser.driver
|
||||
// dep: kiri-mode.drag.driver
|
||||
// dep: kiri-mode.wjet.driver
|
||||
// dep: kiri-mode.wedm.driver
|
||||
gapp.register("kiri.function", (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const { api, client, consts, utils } = kiri;
|
||||
const { space } = moto;
|
||||
const { COLOR, PMODES } = consts;
|
||||
|
||||
let complete = {};
|
||||
|
||||
function prepareSlices(callback, scale = 1, offset = 0) {
|
||||
const { conf, event, feature, hide, mode, view, platform, show } = api;
|
||||
const { stacks } = kiri;
|
||||
|
||||
if (view.is_arrange()) {
|
||||
// in arrange mode, create a screenshot at the start slicing
|
||||
// this can be used later by exports and rendered on some devices
|
||||
let snap = space.screenshot();
|
||||
view.snapshot = snap.substring(snap.indexOf(",") + 1);
|
||||
client.snap(space.screenshot2({ width: 640 }));
|
||||
let bambu = view.bambu = { };
|
||||
space.screenshot3({ width: 512, out(png) { bambu.s512 = png } });
|
||||
space.screenshot3({ width: 128, out(png) { bambu.s128 = png } });
|
||||
}
|
||||
|
||||
if (mode.is_sla() && !callback) {
|
||||
// in SLA mode, slice and preview are the same thing
|
||||
callback = preparePreview;
|
||||
}
|
||||
|
||||
const widgets = api.widgets.all();
|
||||
const settings = conf.get();
|
||||
const { device, process, controller } = settings;
|
||||
const isBelt = device.bedBelt || false;
|
||||
const mark = Date.now();
|
||||
|
||||
// force layout in belt mode when widget exceeds bed length
|
||||
// which has the side-effect (intended) of increasing the bed size
|
||||
if (widgets.length && isBelt) {
|
||||
let doLayout = false;
|
||||
for (let w of widgets) {
|
||||
let bb = w.getBoundingBox();
|
||||
let yspan = bb.max.y - bb.min.y;
|
||||
if (yspan > device.bedDepth) {
|
||||
doLayout = true;
|
||||
}
|
||||
}
|
||||
if (doLayout) {
|
||||
platform.layout();
|
||||
}
|
||||
}
|
||||
|
||||
functions.clear_progress();
|
||||
platform.deselect();
|
||||
hide.slider();
|
||||
view.set_slice();
|
||||
conf.save();
|
||||
|
||||
// allow client.js to generate support widgets
|
||||
event.emit('slice.begin', settings.mode);
|
||||
|
||||
// refresh widget list because 'slice.begin' may generate support widgets
|
||||
const slicing = api.widgets.all()
|
||||
.filter(w => !w.track.ignore && !w.meta.disabled);
|
||||
|
||||
if (slicing.length === 0) {
|
||||
return api.show.alert('nothing to slice');
|
||||
}
|
||||
|
||||
const totalv = slicing.map(w => w.getVertices().count).reduce((a,v) => a + v);
|
||||
const defvert = totalv / slicing.length;
|
||||
const track = {};
|
||||
|
||||
let totalProgress;
|
||||
|
||||
api.widgets.opacity(COLOR.slicing_opacity);
|
||||
|
||||
let segtimes = {},
|
||||
segNumber = 0,
|
||||
errored = false,
|
||||
startTime = Date.now(),
|
||||
toSlice = slicing.slice(),
|
||||
camOrLaser = mode.is_cam() || mode.is_laser(),
|
||||
extruders = {},
|
||||
lastMsg;
|
||||
|
||||
for (let widget of toSlice) {
|
||||
widget.belt = null;
|
||||
widget.stats.progress = 0;
|
||||
widget.setColor(COLOR.slicing);
|
||||
let { extruder } = widget.anno;
|
||||
if (extruder >= 0) {
|
||||
extruders[extruder] = extruder;
|
||||
}
|
||||
}
|
||||
|
||||
// in multi-material belt mode, the anchor needs to be extended
|
||||
// to allow room for the purge tower to be built. calculate here
|
||||
extruders = Object.values(extruders);
|
||||
if (isBelt && extruders.length > 1 && process.outputPurgeTower) {
|
||||
process.beltAnchor = Math.max(
|
||||
process.firstLayerBeltLead,
|
||||
Math.sqrt(process.outputPurgeTower) * extruders.length * (1/Math.sqrt(2)));
|
||||
} else if (isBelt) {
|
||||
process.beltAnchor = process.firstLayerBeltLead;
|
||||
}
|
||||
|
||||
stacks.clear(); // clear rendered stacks
|
||||
client.clear(); // clear worker cache
|
||||
client.sync(); // send fresh widget data to worker
|
||||
|
||||
if (isBelt) {
|
||||
// belt op required to rotate meshes for slicing
|
||||
client.rotate(settings);
|
||||
}
|
||||
|
||||
function sliceNext() {
|
||||
if (toSlice.length) {
|
||||
// while more widgets to slice
|
||||
sliceWidget(toSlice.shift())
|
||||
} else {
|
||||
// once all slicing done, run sliceAll() once for all widgets
|
||||
client.sliceAll(settings, sliceDone);
|
||||
}
|
||||
}
|
||||
|
||||
function sliceWidget(widget) {
|
||||
// weight each widget progress % by their # vertices
|
||||
let factor = (widget.getVertices().count / defvert);
|
||||
|
||||
widget.slice(settings, (sliced, error) => {
|
||||
let mark = Date.now();
|
||||
// update UI info
|
||||
if (sliced) {
|
||||
// update segment time
|
||||
if (lastMsg) {
|
||||
segtimes[`${widget.id}_${segNumber++}_${lastMsg}`] = mark - startTime;
|
||||
}
|
||||
event.emit('slice', settings.mode);
|
||||
}
|
||||
// handle slicing errors
|
||||
if (error && !errored) {
|
||||
errored = true;
|
||||
view.set_arrange();
|
||||
show.alert(error, 5);
|
||||
show.progress(0);
|
||||
client.restart();
|
||||
event.emit('slice.error', error);
|
||||
setTimeout(view.set_arrange, 1);
|
||||
}
|
||||
if (errored) {
|
||||
// terminate slicing
|
||||
sliceDone();
|
||||
} else {
|
||||
// start next widget slice
|
||||
sliceNext();
|
||||
}
|
||||
}, (update, msg, alert) => {
|
||||
if (alert) {
|
||||
api.show.alert(alert);
|
||||
}
|
||||
if (msg && msg !== lastMsg) {
|
||||
let mark = Date.now();
|
||||
if (lastMsg) {
|
||||
segtimes[`${widget.id}_${segNumber++}_${lastMsg}`] = mark - startTime;
|
||||
}
|
||||
lastMsg = msg;
|
||||
startTime = mark;
|
||||
}
|
||||
// on update
|
||||
if (update >= 0) {
|
||||
track[widget.id] = (update || 0) * factor;
|
||||
totalProgress = 0;
|
||||
for (let w of slicing) {
|
||||
totalProgress += (track[w.id] || 0);
|
||||
}
|
||||
show.progress(offset + (totalProgress / slicing.length) * scale, msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sliceDone() {
|
||||
let alert = null;
|
||||
if (scale === 1 && feature.work_alerts && slicing.length) {
|
||||
alert = show.alert("Rendering");
|
||||
};
|
||||
for (let widget of slicing) {
|
||||
// on done
|
||||
segtimes[`${widget.id}_${segNumber++}_draw`] = widget.render(widget.stack);
|
||||
// rotate stack for belt beds
|
||||
if (widget.belt) {
|
||||
widget.stack.obj.rotate(widget.belt);
|
||||
}
|
||||
if (scale === 1) {
|
||||
// clear wireframe
|
||||
widget.setWireframe(false, COLOR.wireframe, COLOR.wireframe_opacity);
|
||||
widget.setOpacity(camOrLaser ? COLOR.cam_sliced_opacity : COLOR.sliced_opacity);
|
||||
widget.setColor(COLOR.deselected);
|
||||
api.hide.alert(alert);
|
||||
}
|
||||
}
|
||||
view.update_slider_max(true);
|
||||
show.layer(-1, 0);
|
||||
if (scale === 1) {
|
||||
view.update_stack_labels();
|
||||
}
|
||||
// if (!isBelt && controller.lineType === 'line' && !process.xray && !controller.devel) {
|
||||
// $('render-ghost').onclick();
|
||||
// }
|
||||
if (scale === 1) {
|
||||
show.progress(0);
|
||||
}
|
||||
// mark slicing complete for prep/preview
|
||||
complete.slice = true;
|
||||
event.emit('slice.end', settings.mode);
|
||||
// print stats
|
||||
segtimes.total = Date.now() - mark;
|
||||
console.log(segtimes);
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
if (isBelt) {
|
||||
// required to render supports properly (in belt mode)
|
||||
// because this updates widget top z which is affected by rotation
|
||||
api.platform.update_top_z();
|
||||
}
|
||||
// refresh / repaint workspace
|
||||
space.refresh();
|
||||
}
|
||||
|
||||
// kick off slicing chain
|
||||
sliceNext();
|
||||
}
|
||||
|
||||
function preparePreview(callback, scale = 1, offset = 0) {
|
||||
const { conf, event, feature, hide, mode, view, platform, show } = api;
|
||||
const { stacks } = kiri;
|
||||
|
||||
const widgets = api.widgets.all();
|
||||
const settings = conf.get();
|
||||
const { device, process, controller } = settings;
|
||||
|
||||
if (complete.preview === feature.pmode) {
|
||||
if (device.extruders && device.extruders.length > 1) {
|
||||
if (++feature.pmode > 2) {
|
||||
feature.pmode = 1;
|
||||
}
|
||||
} else {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!complete.slice) {
|
||||
settings.render = false;
|
||||
prepareSlices(() => { preparePreview(callback, 0.25, 0.75) }, 0.75);
|
||||
return;
|
||||
}
|
||||
|
||||
hide.slider(true);
|
||||
|
||||
const isCam = mode.is_cam(), pMode = settings.mode;
|
||||
const isDark = api.space.is_dark();
|
||||
const isBelt = device.bedBelt || false;
|
||||
|
||||
view.set_preview();
|
||||
conf.save();
|
||||
event.emit('preview.begin', pMode);
|
||||
|
||||
if (isCam) {
|
||||
api.widgets.opacity(isDark ? COLOR.cam_preview_opacity_dark : COLOR.cam_preview_opacity);
|
||||
api.widgets.each(widget => {
|
||||
widget.setColor(COLOR.cam_preview);
|
||||
});
|
||||
} else if (offset === 0) {
|
||||
api.widgets.opacity(COLOR.preview_opacity);
|
||||
}
|
||||
|
||||
let mark = Date.now(),
|
||||
segNumber = 0,
|
||||
segtimes = {},
|
||||
startTime,
|
||||
lastMsg,
|
||||
output = [];
|
||||
|
||||
// pass preview mode to worker
|
||||
settings.pmode = feature.pmode;
|
||||
settings.render = true;
|
||||
|
||||
client.prepare(settings, (progress, message, layer) => {
|
||||
if (layer) {
|
||||
output.push(kiri.codec.decode(layer));
|
||||
}
|
||||
if (message && message !== lastMsg) {
|
||||
const mark = Date.now();
|
||||
if (lastMsg) {
|
||||
segtimes[`${segNumber++}_${lastMsg}`] = mark - startTime;
|
||||
}
|
||||
lastMsg = message;
|
||||
startTime = mark;
|
||||
}
|
||||
show.progress(offset + progress * scale, message);
|
||||
}, (reply, maxSpeed, minSpeed) => {
|
||||
// handle worker errors
|
||||
if (reply && reply.error) {
|
||||
show.alert(reply.error, 5);
|
||||
view.set_arrange();
|
||||
show.progress(0);
|
||||
space.update();
|
||||
event.emit('preview.error', reply.error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastMsg) {
|
||||
segtimes[`${segNumber++}_${lastMsg}`] = Date.now() - startTime;
|
||||
}
|
||||
|
||||
show.progress(0);
|
||||
if (!isCam) {
|
||||
api.widgets.opacity(0);
|
||||
}
|
||||
|
||||
if (output.length) {
|
||||
let alert = feature.work_alerts ? show.alert("Rendering") : null;
|
||||
startTime = Date.now();
|
||||
stacks.clear();
|
||||
const stack = stacks.create('print', space.world)
|
||||
const view = stack.obj.view;
|
||||
output.forEach(layer => {
|
||||
stack.add(layer);
|
||||
});
|
||||
event.emit('preview.view', view);
|
||||
// rotate stack for belt beds
|
||||
if (isBelt && widgets[0].belt) {
|
||||
let ri = widgets[0].belt;
|
||||
ri.dz = 0;
|
||||
ri.dy = settings.device.bedDepth / 2;
|
||||
stack.obj.rotate(widgets[0].belt);
|
||||
}
|
||||
api.hide.alert(alert);
|
||||
segtimes[`${segNumber}_draw`] = Date.now() - startTime;
|
||||
}
|
||||
|
||||
// print stats
|
||||
segtimes.total = Date.now() - mark;
|
||||
console.log(segtimes);
|
||||
|
||||
event.emit('print', pMode);
|
||||
event.emit('preview.end', pMode);
|
||||
|
||||
space.update();
|
||||
view.update_slider_max(true);
|
||||
show.layer(-1, 0);
|
||||
if (feature.pmode === PMODES.SPEED) {
|
||||
view.update_speeds(maxSpeed, minSpeed);
|
||||
} else {
|
||||
view.update_speeds();
|
||||
}
|
||||
view.update_stack_labels();
|
||||
|
||||
let { controller, process } = settings;
|
||||
// if (!isBelt && controller.lineType === 'line' && !process.xray && !controller.devel) {
|
||||
// $('render-ghost').onclick();
|
||||
// }
|
||||
|
||||
// mark preview complete for export
|
||||
complete.preview = feature.pmode;
|
||||
|
||||
if (typeof(callback) === 'function') {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function prepareAnimation() {
|
||||
if (!window.SharedArrayBuffer) {
|
||||
api.alerts.show("The security context of this");
|
||||
api.alerts.show("window prevents animations.");
|
||||
api.alerts.show("Try a Chromium-based browser");
|
||||
return;
|
||||
}
|
||||
api.event.emit("function.animate", {mode: api.conf.get().mode});
|
||||
}
|
||||
|
||||
function prepareExport() {
|
||||
const settings = api.conf.get();
|
||||
const argsave = arguments;
|
||||
if (!complete.preview) {
|
||||
preparePreview(() => { prepareExport(...argsave) });
|
||||
return;
|
||||
}
|
||||
api.event.emit("function.export", {mode: settings.mode});
|
||||
complete.export = true;
|
||||
kiri.export(...argsave);
|
||||
}
|
||||
|
||||
function cancelWorker() {
|
||||
if (client.isBusy()) {
|
||||
client.restart();
|
||||
}
|
||||
}
|
||||
|
||||
function parseCode(code, type) {
|
||||
const { conf, event, show, widgets, view } = api;
|
||||
const { stacks } = kiri;
|
||||
const settings = conf.get();
|
||||
|
||||
event.emit("code.load", {code, type});
|
||||
view.set_preview();
|
||||
widgets.opacity(0);
|
||||
|
||||
client.parse({code, type, settings}, progress => {
|
||||
show.progress(progress, "parsing");
|
||||
}, (layers, maxSpeed, minSpeed) => {
|
||||
show.progress(0);
|
||||
stacks.clear();
|
||||
const stack = stacks.create('parse', space.world);
|
||||
layers.forEach(layer => stack.add(layer));
|
||||
view.update_slider_max(true);
|
||||
view.update_speeds(maxSpeed, minSpeed);
|
||||
show.slices();
|
||||
view.update_stack_labels();
|
||||
space.update();
|
||||
event.emit("code.loaded", {code, type});
|
||||
});
|
||||
}
|
||||
|
||||
// extend API (api.function)
|
||||
const functions = Object.assign(api.function, {
|
||||
slice: prepareSlices,
|
||||
print: preparePreview,
|
||||
prepare: preparePreview,
|
||||
animate: prepareAnimation,
|
||||
export: prepareExport,
|
||||
cancel: cancelWorker,
|
||||
parse: parseCode,
|
||||
clear: client.clear,
|
||||
clear_progress() { complete = {} }
|
||||
});
|
||||
|
||||
});
|
||||
2169
src.old/kiri/init.js
Normal file
2169
src.old/kiri/init.js
Normal file
File diff suppressed because it is too large
Load diff
1
src.old/kiri/lang-en.js
Symbolic link
1
src.old/kiri/lang-en.js
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../web/kiri/lang/en.js
|
||||
81
src.old/kiri/lang.js
Normal file
81
src.old/kiri/lang.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri.lang", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
const LANG = kiri.lang = { current: {} };
|
||||
const KDFL = 'en-us';
|
||||
|
||||
let lset = navigator.language.toLocaleLowerCase();
|
||||
|
||||
LANG.map = function(key) {
|
||||
if (!key) {
|
||||
return KDFL;
|
||||
}
|
||||
let tok = key.split('-');
|
||||
switch (tok[0]) {
|
||||
case 'da': return 'da-dk';
|
||||
case 'de': return 'de-de';
|
||||
case 'en': return 'en-us';
|
||||
case 'es': return 'es-es';
|
||||
case 'fr': return 'fr-fr';
|
||||
case 'pl': return 'pl-pl';
|
||||
case 'pt': return 'pt-pt';
|
||||
case 'zh': return 'zh';
|
||||
}
|
||||
return KDFL;
|
||||
};
|
||||
|
||||
LANG.get = function() {
|
||||
return lset;
|
||||
};
|
||||
|
||||
LANG.set = function() {
|
||||
let map, key, keys = [...arguments];
|
||||
// provide default if none given
|
||||
if (keys.length === 0) {
|
||||
keys = [lset, lset.split('-')[0], 'en'];
|
||||
}
|
||||
for (let i=0; i<keys.length; i++)
|
||||
{
|
||||
key = keys[i]
|
||||
if (!(map = LANG[key])) {
|
||||
continue;
|
||||
}
|
||||
lset = key;
|
||||
let missing = [];
|
||||
let invalid = [];
|
||||
// default to EN values when missing
|
||||
Object.keys(LANG.en).forEach(key => {
|
||||
if (!map[key]) {
|
||||
map[key] = LANG.en[key];
|
||||
missing.push(key);
|
||||
}
|
||||
});
|
||||
// update current map from chosen map
|
||||
Object.keys(map).forEach(key => {
|
||||
if (LANG.en[key]) {
|
||||
let val = map[key];
|
||||
// turn arrays into newline separated strings
|
||||
if (Array.isArray(val)) {
|
||||
val = val.join('\n');
|
||||
}
|
||||
LANG.current[key] = val;
|
||||
} else {
|
||||
invalid.push(key);
|
||||
}
|
||||
});
|
||||
if (missing.length) {
|
||||
console.log(`language map "${key}" missing keys [${missing.length}]: ${missing.join(', ')}`);
|
||||
}
|
||||
if (invalid.length) {
|
||||
console.log(`language map "${key}" invalid keys [${invalid.length}]: ${invalid.join(', ')}`);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
});
|
||||
283
src.old/kiri/layers.js
Normal file
283
src.old/kiri/layers.js
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* provides the `output` abstraction for turning poly lines (open and closed)
|
||||
* into primordial geometries (faces, lines, color ranges). used inside of
|
||||
* workers as part of slicing and preview output.
|
||||
*/
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.paths
|
||||
// dep: geo.polygon
|
||||
// dep: geo.polygons
|
||||
gapp.register("kiri.layers", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { polygons, newPolygon } = base;
|
||||
|
||||
const POLY = base.polygons;
|
||||
|
||||
class Layers {
|
||||
constructor() {
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.layers = {};
|
||||
this.profiles = {};
|
||||
this.stats = {
|
||||
contour: 0,
|
||||
flat_line: 0,
|
||||
flat_poly: 0,
|
||||
line_poly: 0,
|
||||
line: 0
|
||||
};
|
||||
}
|
||||
|
||||
getLayer(layer) {
|
||||
return this.layers[layer];
|
||||
}
|
||||
|
||||
// in radians
|
||||
setRotation(x = 0, y = 0, z = 0) {
|
||||
this.rotation = { x, y, z };
|
||||
return this;
|
||||
}
|
||||
|
||||
setPosition(x = 0, y = 0, z = 0) {
|
||||
this.position = { x, y, z };
|
||||
return this;
|
||||
}
|
||||
|
||||
setLayer(layer, colors, off) {
|
||||
let layers = this.layers;
|
||||
if (typeof(colors) === 'number') {
|
||||
colors = {
|
||||
line: colors,
|
||||
face: colors,
|
||||
opacity: 1
|
||||
};
|
||||
}
|
||||
this.current = layers[layer] = layers[layer] || {
|
||||
rotation: this.rotation,
|
||||
position: this.position,
|
||||
off: off === true,
|
||||
lines: [], // basic line segments
|
||||
polys: [], // colors are an attribute on polygons
|
||||
faces: [], // triangles for areas and flats
|
||||
norms: undefined, // flats vertex normals
|
||||
cface: undefined, // flats face color indices
|
||||
paths: undefined, // 3d extrusion / tube paths (used to be an array, now merged - FIX)
|
||||
cpath: undefined, // path colors indices
|
||||
color: colors || {
|
||||
fat: 0, // render polys & lines with thickness
|
||||
line: 0,
|
||||
face: 0,
|
||||
opacity: 1
|
||||
},
|
||||
};
|
||||
return this;
|
||||
}
|
||||
|
||||
// add a line segment (two points)
|
||||
addLine(p1, p2) {
|
||||
this.current.lines.push(p1, p2);
|
||||
return this;
|
||||
}
|
||||
|
||||
// add an array of line segments
|
||||
addLines(lines, options) {
|
||||
if (options) {
|
||||
// the open option encodes lines as open polygons
|
||||
options.open = true;
|
||||
const polys = [];
|
||||
for (let i=0; i<lines.length-1; i += 2) {
|
||||
polys.push(new base.Polygon()
|
||||
.append(lines[i])
|
||||
.append(lines[i+1])
|
||||
.setOpen());
|
||||
}
|
||||
return this.addPolys(polys, options);
|
||||
}
|
||||
for (let i=0; i<lines.length-1; i += 2) {
|
||||
this.addLine(lines[i], lines[i+1]);
|
||||
this.stats.line++;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// an open or closed polygon
|
||||
addPoly(poly, options) {
|
||||
return this.addPolys([poly], options);
|
||||
}
|
||||
|
||||
// a polygon rendered as a webgl line
|
||||
addPolys(polys, options) {
|
||||
if (polys.length === 0) {
|
||||
return this;
|
||||
}
|
||||
if (options && options.clean) {
|
||||
polys = polys.map(p => p.clean(true));
|
||||
}
|
||||
if (options && options.flat) {
|
||||
return this.addFlats(polys, options);
|
||||
}
|
||||
if (options && !options.thin) {
|
||||
return this.addPaths(polys, options);
|
||||
}
|
||||
polys = flat(polys);
|
||||
if (options) {
|
||||
for (let p of polys) {
|
||||
if (options.z !== undefined) {
|
||||
p.setZ(options.z);
|
||||
}
|
||||
switch (typeof(options.color)) {
|
||||
case 'number': p.color = options.color; break;
|
||||
case 'object': p.color = options.color.line; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.current.polys.appendAll(polys);
|
||||
this.stats.line_poly += polys.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
// add an enclosed 3D polygon earcut into faces
|
||||
// used for FDM solids, bridges, flats debug, CAM hole generation & SLA slice visualization
|
||||
addAreas(polys, options) {
|
||||
const faces = this.current.faces;
|
||||
polys = Array.isArray(polys) ? polys : [ polys ];
|
||||
for (let poly of polys) {
|
||||
for (let ep of poly.earcut()) {
|
||||
for (let p of ep.points) {
|
||||
faces.push(p.x, p.y, p.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (options && options.outline) {
|
||||
this.addPolys(polys.clone(true));
|
||||
}
|
||||
}
|
||||
|
||||
// add a 2D polyline path (usually FDM extrusion paths)
|
||||
addFlats(polys, options) {
|
||||
const opts = options || {};
|
||||
const offset = opts.offset || 1;
|
||||
polys = flat(polys);
|
||||
if (!polys.length) {
|
||||
return;
|
||||
}
|
||||
const cur = this.current, faces = cur.faces;
|
||||
const norms = [];
|
||||
for (let poly of polys) {
|
||||
const faceidx = faces.length / 3;
|
||||
const path = poly.toPath2D(offset * 0.95);
|
||||
const { left, right, normals } = path;
|
||||
for (let p of path.faces) {
|
||||
faces.push(p.x, p.y, p.z);
|
||||
}
|
||||
norms.push(normals);
|
||||
if (opts.outline) {
|
||||
let p1 = newPolygon().addPoints(left).setOpenValue(poly.open);
|
||||
let p2 = newPolygon().addPoints(right).setOpenValue(poly.open);
|
||||
if (poly.z) {
|
||||
p1.setZ(poly.z);
|
||||
p2.setZ(poly.z);
|
||||
}
|
||||
this.addPolys(p1);
|
||||
this.addPolys(p2);
|
||||
}
|
||||
const color = opts.color ?
|
||||
(typeof(opts.color) === 'number' ? { line: opts.color, face: opts.color } : opts.color) :
|
||||
cur.color;
|
||||
if (!cur.cface) {
|
||||
cur.cface = [ Object.assign({ start: faceidx, count: Infinity }, color) ];
|
||||
} else {
|
||||
// rewrite last color count if color or opacity have changed
|
||||
const pc = cur.cface[cur.cface.length - 1];
|
||||
if (pc.face !== color.face || pc.opacity !== color.opacity) {
|
||||
pc.count = faceidx;
|
||||
cur.cface.push(Object.assign({ start: faceidx, count: Infinity }, color));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (cur.norms) {
|
||||
cur.norms.appendAll(norms.flat());
|
||||
} else {
|
||||
cur.norms = norms.flat();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// add 3D polyline path (FDM extrusion paths)
|
||||
addPaths(polys, options) {
|
||||
const opts = options || {};
|
||||
const height = opts.height || 1;
|
||||
const offset = opts.offset || 1;
|
||||
|
||||
polys = flat(polys);
|
||||
if (!polys.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let poly of polys) {
|
||||
if (poly.length < (poly.open ? 2 : 3)) {
|
||||
continue;
|
||||
}
|
||||
const z = opts.z || poly.getZ();
|
||||
const { faces, normals } = poly.toPath3D(offset, height, 0);
|
||||
const cur = this.current;
|
||||
const one = cur.paths;
|
||||
// happens with very short lines being omitted
|
||||
if (!(faces && normals)) {
|
||||
continue;
|
||||
}
|
||||
if (one) {
|
||||
// for some reason, incremental merge is faster than all at the end
|
||||
const add = one.faces.length / 3;
|
||||
const feces = new Float32Array(one.faces.length + faces.length);
|
||||
const indln = one.faces.length / 3;
|
||||
feces.set(one.faces);
|
||||
feces.set(faces, one.faces.length);
|
||||
one.faces = feces;
|
||||
// allow changing colors
|
||||
if (opts.color) {
|
||||
if (!cur.cpath) {
|
||||
cur.cpath = [ Object.assign({ start: 0, count: indln - 1 }, cur.color) ];
|
||||
}
|
||||
// rewrite last color count if color or opacity have changed
|
||||
const pc = cur.cpath[cur.cpath.length - 1];
|
||||
if (pc.face !== opts.color.face || pc.opacity !== opts.color.opacity) {
|
||||
pc.count = indln;
|
||||
cur.cpath.push(Object.assign({ start: indln, count: Infinity }, opts.color));
|
||||
}
|
||||
}
|
||||
if (normals) {
|
||||
one.norms.appendAll(normals);
|
||||
}
|
||||
} else {
|
||||
cur.paths = { faces, z, norms: normals };
|
||||
if (opts.color) {
|
||||
cur.cpath = [ Object.assign({ start: 0, count: Infinity }, opts.color) ];
|
||||
}
|
||||
}
|
||||
this.stats.contour++;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
function flat(polys) {
|
||||
if (Array.isArray(polys)) {
|
||||
return POLY.flatten(polys.clone(true), [], true);
|
||||
} else {
|
||||
return POLY.flatten([polys.clone(true)], [], true);
|
||||
}
|
||||
}
|
||||
|
||||
kiri.Layers = Layers;
|
||||
|
||||
});
|
||||
984
src.old/kiri/main.js
Normal file
984
src.old/kiri/main.js
Normal file
|
|
@ -0,0 +1,984 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: main.kiri
|
||||
// dep: moto.license
|
||||
// dep: geo.base
|
||||
// dep: kiri.ui
|
||||
// dep: kiri.api
|
||||
// dep: kiri.conf
|
||||
// dep: kiri.lang
|
||||
// dep: kiri.utils
|
||||
// dep: kiri.files
|
||||
// dep: kiri.consts
|
||||
// dep: kiri.widget
|
||||
// dep: kiri.stats
|
||||
// dep: kiri.stacks
|
||||
// dep: kiri.devices
|
||||
// dep: kiri.function
|
||||
// dep: kiri.platform
|
||||
// dep: kiri.selection
|
||||
// dep: kiri.settings
|
||||
// use: kiri.alerts
|
||||
// use: kiri.files
|
||||
// use: kiri.frame
|
||||
// use: moto.ajax
|
||||
gapp.register("kiri.main", (root, exports) => {
|
||||
|
||||
let { data, kiri, moto, noop } = root,
|
||||
{ api, consts, lang, Widget, newWidget, utils, stats } = kiri,
|
||||
{ parseOpt, encodeOpt, o2js, js2o, ls2o } = utils,
|
||||
{ platform, selection, settings } = api,
|
||||
{ COLOR, MODES, VIEWS } = consts,
|
||||
LANG = lang.current,
|
||||
WIN = self.window,
|
||||
DOC = self.document,
|
||||
LOC = self.location,
|
||||
SETUP = parseOpt(LOC.search.substring(1)),
|
||||
SECURE = isSecure(LOC.protocol),
|
||||
LOCAL = self.debug && !SETUP.remote,
|
||||
EVENT = kiri.broker = gapp.broker,
|
||||
SDB = data.local,
|
||||
SPACE = kiri.space = moto.space,
|
||||
FILES = kiri.catalog = kiri.openFiles(new data.Index(SETUP.d ? SETUP.d[0] : 'kiri')),
|
||||
clone = Object.clone,
|
||||
UI = {},
|
||||
UC = kiri.ui.prefix('kiri').inputAction(api.conf.update),
|
||||
MODE = MODES.FDM,
|
||||
STACKS = kiri.stacks,
|
||||
DRIVER = undefined,
|
||||
viewMode = VIEWS.ARRANGE,
|
||||
autoSaveTimer = null,
|
||||
busy = 0,
|
||||
{ assign } = Object;
|
||||
|
||||
// extend API
|
||||
assign(api, {
|
||||
ui: UI = assign(api.ui, UI),
|
||||
uc: UC = assign(api.uc, UC),
|
||||
stats,
|
||||
catalog: FILES,
|
||||
busy: assign(api.busy, {
|
||||
val() { return busy },
|
||||
inc() { kiri.api.event.emit("busy", ++busy) },
|
||||
dec() { kiri.api.event.emit("busy", --busy) }
|
||||
}),
|
||||
color: COLOR,
|
||||
const: assign(api.const, {
|
||||
LANG,
|
||||
LOCAL,
|
||||
SETUP,
|
||||
SECURE,
|
||||
STACKS,
|
||||
}),
|
||||
dialog: assign(api.dialog, {
|
||||
show: showModal,
|
||||
hide: hideModal,
|
||||
update_process_list: updateProcessList
|
||||
}),
|
||||
help: assign(api.help, {
|
||||
show: showHelp,
|
||||
file: showHelpFile
|
||||
}),
|
||||
event: assign(api.event, {
|
||||
on(t,l) { return EVENT.on(t,l) },
|
||||
emit(t,m,o) { return EVENT.publish(t,m,o) },
|
||||
bind(t,m,o) { return EVENT.bind(t,m,o) },
|
||||
alerts(clr) { api.alerts.update(clr) },
|
||||
import: loadFile,
|
||||
settings: triggerSettingsEvent
|
||||
}),
|
||||
group: assign(api.group, {
|
||||
merge: groupMerge,
|
||||
split: groupSplit,
|
||||
}),
|
||||
hide: assign(api.hide, {
|
||||
alert(rec, recs) { api.alerts.hide(...arguments) },
|
||||
import: noop,
|
||||
slider: hideSlider
|
||||
}),
|
||||
image: assign(api.image, {
|
||||
dialog: loadImageDialog,
|
||||
convert: loadImageConvert
|
||||
}),
|
||||
language: kiri.lang,
|
||||
modal: assign(api.modal, {
|
||||
show: showModal,
|
||||
hide: hideModal,
|
||||
visible: modalShowing
|
||||
}),
|
||||
mode: assign(api.mode, {
|
||||
get_id() { return MODE },
|
||||
get_lower: getModeLower,
|
||||
get: getMode,
|
||||
set: setMode,
|
||||
switch: switchMode,
|
||||
set_expert: noop,
|
||||
is_fdm() { return MODE === MODES.FDM },
|
||||
is_cam() { return MODE === MODES.CAM },
|
||||
is_sla() { return MODE === MODES.SLA },
|
||||
is_drag() { return MODE === MODES.DRAG },
|
||||
is_wedm() { return MODE === MODES.WEDM },
|
||||
is_wjet() { return MODE === MODES.WJET },
|
||||
is_laser() { return MODE === MODES.LASER },
|
||||
is_2d() { return false ||
|
||||
api.mode.is_drag() ||
|
||||
api.mode.is_wedm() ||
|
||||
api.mode.is_wjet() ||
|
||||
api.mode.is_laser()
|
||||
}
|
||||
}),
|
||||
probe: assign(api.probe, {
|
||||
live: "https://live.grid.space",
|
||||
grid: noop,
|
||||
local: noop
|
||||
}),
|
||||
process: assign(api.process, {
|
||||
code: currentProcessCode,
|
||||
get: currentProcessName
|
||||
}),
|
||||
show: assign(api.show, {
|
||||
alert() { return api.alerts.show(...arguments) },
|
||||
progress: setProgress,
|
||||
controls: setControlsVisible,
|
||||
slices: showSlices,
|
||||
layer: setVisibleLayer,
|
||||
local: showLocal,
|
||||
import: function() { UI.import.style.display = '' }
|
||||
}),
|
||||
space: assign(api.space, {
|
||||
reload,
|
||||
auto_save,
|
||||
restore: restoreWorkspace,
|
||||
clear: clearWorkspace,
|
||||
save: saveWorkspace,
|
||||
set_focus: setFocus,
|
||||
update: SPACE.update,
|
||||
is_dark() { return settings.ctrl().dark }
|
||||
}),
|
||||
util: assign(api.util, {
|
||||
isSecure,
|
||||
download: downloadBlob,
|
||||
ui2rec() { api.conf.update_from(...arguments) },
|
||||
rec2ui() { api.conf.update_fields(...arguments) },
|
||||
b64enc(obj) { return base64js.fromByteArray(new TextEncoder().encode(JSON.stringify(obj))) },
|
||||
b64dec(obj) { return JSON.parse(new TextDecoder().decode(base64js.toByteArray(obj))) }
|
||||
}),
|
||||
view: assign(api.view, {
|
||||
get() { return viewMode },
|
||||
set() { setViewMode(...arguments) },
|
||||
set_arrange() { api.view.set(VIEWS.ARRANGE) },
|
||||
set_slice() { api.view.set(VIEWS.SLICE) },
|
||||
set_preview() { api.view.set(VIEWS.PREVIEW) },
|
||||
set_animate() { api.view.set(VIEWS.ANIMATE) },
|
||||
is_arrange() { return viewMode === VIEWS.ARRANGE },
|
||||
is_slice() { return viewMode === VIEWS.SLICE },
|
||||
is_preview() { return viewMode === VIEWS.PREVIEW },
|
||||
is_animate() { return viewMode === VIEWS.ANIMATE },
|
||||
update_stack_labels: updateStackLabelState,
|
||||
update_slider_max: updateSliderMax,
|
||||
update_slider: updateSlider,
|
||||
update_speeds: updateSpeeds,
|
||||
hide_slices: hideSlices,
|
||||
snapshot: null,
|
||||
edges: setEdges,
|
||||
unit_scale: unitScale,
|
||||
wireframe: setWireframe,
|
||||
}),
|
||||
work: kiri.client
|
||||
});
|
||||
|
||||
// add show() to catalog for API
|
||||
FILES.show = showCatalog;
|
||||
|
||||
// patch broker for api backward compatibility
|
||||
EVENT.on = (topic, listener) => {
|
||||
EVENT.subscribe(topic, listener);
|
||||
return EVENT;
|
||||
};
|
||||
|
||||
function updateStackLabelState() {
|
||||
const settings = api.conf.get();
|
||||
const { stacks } = kiri;
|
||||
// match label checkboxes to preference
|
||||
for (let label of stacks.getLabels()) {
|
||||
let check = `${settings.mode}-${api.view.get()}-${label}`;
|
||||
stacks.setVisible(label, settings.labels[check] !== false);
|
||||
}
|
||||
}
|
||||
|
||||
function setFocus(sel, point) {
|
||||
if (point) {
|
||||
SPACE.platform.setCenter(point.x, point.z, point.y);
|
||||
SPACE.view.setFocus(new THREE.Vector3(point.x, point.y, point.z));
|
||||
return;
|
||||
}
|
||||
if (sel === undefined) {
|
||||
sel = api.widgets.all();
|
||||
} else if (!Array.isArray) {
|
||||
sel = [ sel ];
|
||||
} else if (sel.length === 0) {
|
||||
sel = api.widgets.all();
|
||||
}
|
||||
let pos = { x:0, y:0, z:0 };
|
||||
for (let widget of sel) {
|
||||
pos.x += widget.track.pos.x;
|
||||
pos.y += widget.track.pos.y;
|
||||
pos.z += widget.track.pos.z;
|
||||
}
|
||||
if (sel.length) {
|
||||
pos.x /= sel.length;
|
||||
pos.y /= sel.length;
|
||||
pos.z /= sel.length;
|
||||
}
|
||||
let cam_index = api.conf.get().process.camStockIndexed || false;
|
||||
let focus_z = cam_index ? 0 : platform.top_z() / 2;
|
||||
SPACE.platform.setCenter(pos.x, -pos.y, focus_z);
|
||||
SPACE.view.setFocus(new THREE.Vector3(pos.x, focus_z, -pos.y));
|
||||
}
|
||||
|
||||
function reload() {
|
||||
api.event.emit('reload');
|
||||
do_reload(100);
|
||||
}
|
||||
|
||||
function do_reload(time) {
|
||||
// allow time for async saves to complete and busy to to to zero
|
||||
setTimeout(() => {
|
||||
if (busy === 0) {
|
||||
LOC.reload();
|
||||
} else {
|
||||
console.log(`reload deferred on busy=${busy}`);
|
||||
do_reload(250);
|
||||
}
|
||||
}, time || 100);
|
||||
}
|
||||
|
||||
function auto_save() {
|
||||
if (!settings.ctrl().autoSave) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(autoSaveTimer);
|
||||
autoSaveTimer = setTimeout(() => {
|
||||
api.space.save(true);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// update version and init count
|
||||
let inits = parseInt(SDB.getItem('kiri-init') || stats.get('init') || 0) + 1;
|
||||
SDB.setItem('kiri-init', inits);
|
||||
stats.set('init', inits);
|
||||
stats.set('kiri', kiri.version || gapp.version);
|
||||
|
||||
// remove version from url, preserve other settings
|
||||
WIN.history.replaceState({},'','/kiri/' + encodeOpt(SETUP) + LOC.hash);
|
||||
|
||||
/** ******************************************************************
|
||||
* Utility Functions
|
||||
******************************************************************* */
|
||||
|
||||
function unitScale() {
|
||||
return api.mode.is_cam() && settings.ctrl().units === 'in' ? 25.4 : 1;
|
||||
}
|
||||
|
||||
function triggerSettingsEvent() {
|
||||
api.event.emit('settings', settings.get());
|
||||
}
|
||||
|
||||
function isSecure(proto) {
|
||||
return proto.toLowerCase().indexOf("https") === 0;
|
||||
}
|
||||
|
||||
function setProgress(value = 0, msg) {
|
||||
value = (value * 100).round(4);
|
||||
UI.progress.width = value+'%';
|
||||
if (self.debug) {
|
||||
// console.log(msg, value.round(2));
|
||||
UI.prostatus.style.display = 'flex';
|
||||
if (msg) {
|
||||
UI.prostatus.innerHTML = msg;
|
||||
} else {
|
||||
UI.prostatus.innerHTML = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function bound(v,min,max) {
|
||||
return Math.max(min,Math.min(max,v));
|
||||
}
|
||||
|
||||
function updateSpeeds(maxSpeed, minSpeed) {
|
||||
const { ui } = api;
|
||||
ui.speeds.style.display =
|
||||
maxSpeed &&
|
||||
settings.mode !== 'SLA' &&
|
||||
settings.mode !== 'LASER' &&
|
||||
viewMode === VIEWS.PREVIEW &&
|
||||
ui.showSpeeds.checked ? 'block' : '';
|
||||
if (maxSpeed) {
|
||||
const colors = [];
|
||||
for (let i = 0; i <= maxSpeed; i += maxSpeed / 20) {
|
||||
colors.push(Math.round(Math.max(i, 1)));
|
||||
}
|
||||
kiri.client.colors(colors, maxSpeed, speedColors => {
|
||||
const list = [];
|
||||
Object.keys(speedColors).map(v => parseInt(v)).sort((a, b) => b - a).forEach(speed => {
|
||||
const color = speedColors[speed];
|
||||
const hex = color.toString(16).padStart(6, 0);
|
||||
const r = (color >> 16) & 0xff;
|
||||
const g = (color >> 8) & 0xff;
|
||||
const b = (color >> 0) & 0xff;
|
||||
const style = `background-color:#${hex}`;
|
||||
list.push(`<label style="${style}">${speed}</label>`);
|
||||
});
|
||||
ui.speedbar.innerHTML = list.join('');
|
||||
});
|
||||
api.event.emit('preview.speeds', {
|
||||
min: minSpeed,
|
||||
max: maxSpeed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateSlider() {
|
||||
api.event.emit("slider.set", {
|
||||
start: (api.var.layer_lo / api.var.layer_max),
|
||||
end: (api.var.layer_hi / api.var.layer_max)
|
||||
});
|
||||
api.conf.update_fields_from_range();
|
||||
}
|
||||
|
||||
function setVisibleLayer(h, l) {
|
||||
h = h >= 0 ? h : api.var.layer_hi;
|
||||
l = l >= 0 ? l : api.var.layer_lo;
|
||||
api.var.layer_hi = bound(h, 0, api.var.layer_max);
|
||||
api.var.layer_lo = bound(l, 0, h);
|
||||
api.event.emit("slider.label");
|
||||
updateSlider();
|
||||
showSlices();
|
||||
}
|
||||
|
||||
function setWireframe(bool, color, opacity) {
|
||||
api.widgets.each(function(w) { w.setWireframe(bool, color, opacity) });
|
||||
SPACE.update();
|
||||
}
|
||||
|
||||
function setEdges(bool) {
|
||||
if (bool && bool.toggle) {
|
||||
api.local.toggle('model.edges');
|
||||
} else {
|
||||
api.local.set('model.edges', bool);
|
||||
}
|
||||
bool = api.local.getBoolean('model.edges');
|
||||
api.widgets.each(w => w.setEdges(bool));
|
||||
SPACE.update();
|
||||
}
|
||||
|
||||
function updateSliderMax(set) {
|
||||
let max = STACKS.getRange().tallest - 1;
|
||||
api.var.layer_max = UI.sliderMax.innerText = max;
|
||||
if (set || max < api.var.layer_hi) {
|
||||
api.var.layer_hi = api.var.layer_max;
|
||||
api.event.emit("slider.label");
|
||||
updateSlider();
|
||||
}
|
||||
}
|
||||
|
||||
function hideSlices() {
|
||||
STACKS.clear();
|
||||
api.widgets.opacity(COLOR.model_opacity);
|
||||
api.widgets.each(function(widget) {
|
||||
widget.setWireframe(false);
|
||||
});
|
||||
}
|
||||
|
||||
function setWidgetVisibility(bool) {
|
||||
api.widgets.each(w => {
|
||||
if (bool) {
|
||||
w.show();
|
||||
} else {
|
||||
w.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* hide or show slice-layers and their sub-elements
|
||||
*
|
||||
* @param {number} [layer]
|
||||
*/
|
||||
function showSlices(layer) {
|
||||
if (viewMode === VIEWS.ARRANGE) {
|
||||
return;
|
||||
}
|
||||
|
||||
showSlider();
|
||||
|
||||
if (typeof(layer) === 'string' || typeof(layer) === 'number') {
|
||||
layer = parseInt(layer);
|
||||
} else {
|
||||
layer = api.var.layer_hi;
|
||||
}
|
||||
|
||||
layer = bound(layer, 0, api.var.layer_max);
|
||||
if (layer < api.var.layer_lo) api.var.layer_lo = layer;
|
||||
api.var.layer_hi = layer;
|
||||
api.event.emit("slider.label");
|
||||
|
||||
updateSlider();
|
||||
STACKS.setRange(api.var.layer_lo, api.var.layer_hi);
|
||||
|
||||
SPACE.update();
|
||||
}
|
||||
|
||||
function showSlider() {
|
||||
UI.layers.style.display = 'flex';
|
||||
UI.slider.style.display = 'flex';
|
||||
}
|
||||
|
||||
function hideSlider() {
|
||||
UI.layers.style.display = 'none';
|
||||
UI.slider.style.display = 'none';
|
||||
UI.speeds.style.display = 'none';
|
||||
}
|
||||
|
||||
function loadImageDialog(image, name, force) {
|
||||
if (!force && image.byteLength > 2500000) {
|
||||
return UC.confirm("Large images may fail to import<br>Consider resizing under 1000 x 1000<br>Proceed with import?").then(ok => {
|
||||
if (ok) {
|
||||
loadImageDialog(image, name, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
const opt = {pre: [
|
||||
"<div class='f-col a-center'>",
|
||||
" <h3>Image Conversion</h3>",
|
||||
" <p class='t-just' style='width:300px;line-height:1.5em'>",
|
||||
" This will create a 3D model from a 2D PNG image. Photos must",
|
||||
" be blurred to be usable. Values from 0=off to 50=high are suggested.",
|
||||
" Higher values incur more processing time.",
|
||||
" </p>",
|
||||
" <div class='f-row t-right'><table>",
|
||||
" <tr><th>blur value</th><td><input id='png-blur' value='0' size='3'></td>",
|
||||
" <th> invert image</th><td><input id='png-inv' type='checkbox'></td></tr>",
|
||||
" <tr><th>base size</th><td><input id='png-base' value='0' size='3'></td>",
|
||||
" <th> invert alpha</th><td><input id='alpha-inv' type='checkbox'></td></tr>",
|
||||
" <tr><th>border size</th><td><input id='png-border' value='0' size='3'></td>",
|
||||
" <th></th><td></td></tr>",
|
||||
" </table></div>",
|
||||
"</div>"
|
||||
]};
|
||||
UC.confirm(undefined, {convert:true, cancel:false}, undefined, opt).then((ok) => {
|
||||
if (ok) {
|
||||
loadImage(image, {
|
||||
file: name,
|
||||
blur: parseInt($('png-blur').value) || 0,
|
||||
base: parseInt($('png-base').value) || 0,
|
||||
border: parseInt($('png-border').value) || 0,
|
||||
inv_image: $('png-inv').checked,
|
||||
inv_alpha: $('alpha-inv').checked
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function loadImage(image, opt = {}) {
|
||||
const info = assign({settings: settings.get(), png:image}, opt);
|
||||
kiri.client.image2mesh(info, progress => {
|
||||
api.show.progress(progress, "converting");
|
||||
}, vertices => {
|
||||
api.show.progress(0);
|
||||
const widget = newWidget().loadVertices(vertices);
|
||||
widget.meta.file = opt.file;
|
||||
platform.add(widget);
|
||||
});
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Selection Functions
|
||||
******************************************************************* */
|
||||
|
||||
function groupMerge() {
|
||||
Widget.Groups.merge(selection.widgets(true));
|
||||
}
|
||||
|
||||
function groupSplit() {
|
||||
Widget.Groups.split(selection.widgets(false));
|
||||
}
|
||||
|
||||
/** ******************************************************************
|
||||
* Settings Functions
|
||||
******************************************************************* */
|
||||
|
||||
// convert any image type to png
|
||||
function loadImageConvert(res, name) {
|
||||
let url = URL.createObjectURL(new Blob([res]));
|
||||
|
||||
$('mod-any').innerHTML = `<img id="xsrc" src="${url}"><canvas id="xdst"></canvas>`;
|
||||
|
||||
let img = $('xsrc');
|
||||
let can = $('xdst');
|
||||
|
||||
img.onload = () => {
|
||||
can.width = img.width;
|
||||
can.height = img.height;
|
||||
let ctx = can.getContext('2d');
|
||||
ctx.drawImage(img, 0, 0);
|
||||
fetch(can.toDataURL()).then(r => r.arrayBuffer()).then(data => {
|
||||
loadImageDialog(data, name);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function loadFile(ev) {
|
||||
// use modern Filesystem api when available
|
||||
if (false && window.showOpenFilePicker) {
|
||||
window.showOpenFilePicker().then(files => {
|
||||
return Promise.all(files.map(fh => fh.getFile()))
|
||||
}).then(files => {
|
||||
if (files.length) {
|
||||
api.platform.load_files(files);
|
||||
}
|
||||
}).catch(e => { /* ignore cancel */ });
|
||||
return;
|
||||
}
|
||||
api.ui.load.click();
|
||||
}
|
||||
|
||||
function saveWorkspace(quiet) {
|
||||
api.conf.save();
|
||||
const newWidgets = [];
|
||||
const oldWidgets = js2o(SDB.getItem('ws-widgets'), []);
|
||||
api.widgets.each(function(widget) {
|
||||
if (widget.synth) return;
|
||||
newWidgets.push(widget.id);
|
||||
oldWidgets.remove(widget.id);
|
||||
widget.saveState();
|
||||
let ann = api.widgets.annotate(widget.id);
|
||||
ann.file = widget.meta.file;
|
||||
ann.url = widget.meta.url;
|
||||
});
|
||||
SDB.setItem('ws-widgets', o2js(newWidgets));
|
||||
oldWidgets.forEach(wid => {
|
||||
Widget.deleteFromState(wid);
|
||||
});
|
||||
// eliminate dangling saved widgets
|
||||
FILES.deleteFilter(key => newWidgets.indexOf(key.substring(8)) < 0, "ws-save-", "ws-savf");
|
||||
if (!quiet) {
|
||||
api.show.alert("workspace saved", 1);
|
||||
}
|
||||
}
|
||||
|
||||
function restoreWorkspace(ondone, skip_widget_load) {
|
||||
let newset = api.conf.restore(false),
|
||||
camera = newset.controller.view,
|
||||
toload = ls2o('ws-widgets',[]),
|
||||
loaded = 0,
|
||||
position = true;
|
||||
|
||||
api.conf.update_fields();
|
||||
platform.update_size();
|
||||
|
||||
SPACE.view.reset();
|
||||
if (camera) {
|
||||
SPACE.view.load(camera);
|
||||
} else {
|
||||
SPACE.view.home();
|
||||
}
|
||||
|
||||
if (skip_widget_load) {
|
||||
if (ondone) {
|
||||
ondone();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// remove any widgets from platform
|
||||
api.widgets.each(function(widget) {
|
||||
platform.delete(widget);
|
||||
});
|
||||
|
||||
// load any widget by name that was saved to the workspace
|
||||
toload.forEach(function(widgetid) {
|
||||
Widget.loadFromState(widgetid, function(widget) {
|
||||
if (widget) {
|
||||
platform.add(widget, 0, position, true);
|
||||
let ann = api.widgets.annotate(widgetid);
|
||||
widget.meta.file = ann.file;
|
||||
widget.meta.url = ann.url;
|
||||
}
|
||||
if (++loaded === toload.length) {
|
||||
platform.deselect();
|
||||
if (ondone) {
|
||||
ondone();
|
||||
setTimeout(() => {
|
||||
platform.update_bounds();
|
||||
SPACE.update();
|
||||
}, 1);
|
||||
}
|
||||
}
|
||||
}, position);
|
||||
});
|
||||
|
||||
return toload.length > 0;
|
||||
}
|
||||
|
||||
function clearWorkspace() {
|
||||
// free up worker cache/mem
|
||||
kiri.client.clear();
|
||||
platform.select_all();
|
||||
platform.delete(selection.meshes());
|
||||
}
|
||||
|
||||
function modalShowing() {
|
||||
return UI.modal.style.display === 'flex';
|
||||
}
|
||||
|
||||
function showModal(which) {
|
||||
let mod = UI.modal,
|
||||
style = mod.style,
|
||||
visible = modalShowing(),
|
||||
info = { pct: 0 };
|
||||
|
||||
// hide all modals befroe showing another
|
||||
Object.keys(UI.modals).forEach(name => {
|
||||
UI.modals[name].style.display = name === which ? 'flex' : '';
|
||||
});
|
||||
|
||||
function ondone() {
|
||||
api.event.emit('modal.show', which);
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
return ondone();
|
||||
}
|
||||
|
||||
style.height = '0';
|
||||
style.display = 'flex';
|
||||
|
||||
new TWEEN.Tween(info).
|
||||
easing(TWEEN.Easing.Quadratic.InOut).
|
||||
to({ pct: 100 }, 100).
|
||||
onUpdate(() => { style.height = `${info.pct}%` }).
|
||||
onComplete(ondone).
|
||||
start();
|
||||
}
|
||||
|
||||
function hideModal() {
|
||||
if (!modalShowing()) {
|
||||
return;
|
||||
}
|
||||
let mod = UI.modal, style = mod.style, info={pct:100};
|
||||
new TWEEN.Tween(info).
|
||||
easing(TWEEN.Easing.Quadratic.InOut).
|
||||
to({pct:0}, 100).
|
||||
onUpdate(() => { style.height = `${info.pct}%` }).
|
||||
onComplete(() => {
|
||||
style.display = '';
|
||||
api.event.emit('modal.hide');
|
||||
}).
|
||||
start();
|
||||
}
|
||||
|
||||
function showCatalog() {
|
||||
showModal("files");
|
||||
}
|
||||
|
||||
function editSettings(e) {
|
||||
let current = settings.get(),
|
||||
mode = getMode(),
|
||||
name = e.target.getAttribute("name"),
|
||||
load = current.sproc[mode][name],
|
||||
loadstr = JSON.stringify(load,null,4).split('\n');
|
||||
UC.prompt(`settings for "${name}"`, loadstr).then(edit => {
|
||||
if (edit) {
|
||||
try {
|
||||
current.sproc[mode][name] = JSON.parse(edit);
|
||||
if (name === settings.proc().processName) {
|
||||
api.conf.load(null, name);
|
||||
}
|
||||
api.conf.save();
|
||||
api.settings.sync.put();
|
||||
} catch (e) {
|
||||
console.log({ malformed_settings: e });
|
||||
UC.alert('malformed settings object');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function exportSettings(e) {
|
||||
let current = settings.get(),
|
||||
mode = getMode(),
|
||||
name = e.target.getAttribute("name"),
|
||||
data = api.util.b64enc({
|
||||
process: current.sproc[mode][name],
|
||||
version: kiri.version,
|
||||
moto: moto.id,
|
||||
time: Date.now(),
|
||||
mode,
|
||||
name
|
||||
});
|
||||
UC.prompt("Export Process Filename", name).then(name => {
|
||||
if (name) {
|
||||
api.util.download(data, `${name}.km`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteSettings(e) {
|
||||
let current = settings.get();
|
||||
let name = e.target.getAttribute("del");
|
||||
delete current.sproc[getMode()][name];
|
||||
api.settings.sync.put();
|
||||
updateProcessList();
|
||||
api.conf.save();
|
||||
triggerSettingsEvent();
|
||||
}
|
||||
|
||||
function updateProcessList() {
|
||||
let current = settings.get();
|
||||
let list = [], s = current, sp = s.sproc[getMode()] || {}, table = UI.settingsList;
|
||||
table.innerHTML = '';
|
||||
for (let k in sp) {
|
||||
if (sp.hasOwnProperty(k)) list.push(k);
|
||||
}
|
||||
list.filter(n => n !=='default').sort().forEach(function(sk) {
|
||||
let row = DOC.createElement('div'),
|
||||
load = DOC.createElement('button'),
|
||||
edit = DOC.createElement('button'),
|
||||
xprt = DOC.createElement('button'),
|
||||
del = DOC.createElement('button'),
|
||||
name = sk;
|
||||
|
||||
load.setAttribute('load', sk);
|
||||
load.onclick = (ev) => {
|
||||
api.conf.load(undefined, sk);
|
||||
updateProcessList();
|
||||
hideModal();
|
||||
}
|
||||
load.appendChild(DOC.createTextNode(sk));
|
||||
if (sk == settings.proc().processName) {
|
||||
load.setAttribute('class', 'selected')
|
||||
}
|
||||
UI.settingsName.value = settings.proc().processName;
|
||||
|
||||
del.setAttribute('del', sk);
|
||||
del.setAttribute('title', "remove '"+sk+"'");
|
||||
del.innerHTML = '<i class="far fa-trash-alt"></i>';
|
||||
del.onclick = deleteSettings;
|
||||
|
||||
edit.setAttribute('name', sk);
|
||||
edit.setAttribute('title', 'edit');
|
||||
edit.innerHTML = '<i class="far fa-edit"></i>';
|
||||
edit.onclick = editSettings;
|
||||
|
||||
xprt.setAttribute('name', sk);
|
||||
xprt.setAttribute('title', 'export');
|
||||
xprt.innerHTML = '<i class="fas fa-download"></i>';
|
||||
xprt.onclick = exportSettings;
|
||||
|
||||
row.setAttribute("class", "flow-row");
|
||||
row.appendChild(edit);
|
||||
row.appendChild(load);
|
||||
row.appendChild(xprt);
|
||||
row.appendChild(del);
|
||||
table.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function showHelp() {
|
||||
showHelpFile(`local`,() => {});
|
||||
}
|
||||
|
||||
function showHelpFile(local,then) {
|
||||
if (!local) {
|
||||
WIN.open("//docs.grid.space/", "_help");
|
||||
return;
|
||||
}
|
||||
$('kiri-version').innerHTML = `${LANG.version} ${kiri.version}`;
|
||||
showModal('help');
|
||||
api.event.emit('help.show', local);
|
||||
}
|
||||
|
||||
function showLocal() {
|
||||
showModal('local');
|
||||
api.probe.local((err,data) => {
|
||||
let devc = 0;
|
||||
let bind = [];
|
||||
let html = ['<table>'];
|
||||
html.push(`<thead><tr><th>device</th><th>type</th><th>status</th><th></th></tr></thead>`);
|
||||
html.push(`<tbody>`);
|
||||
let recs = [];
|
||||
for (let k in data) {
|
||||
recs.push(data[k].stat);
|
||||
}
|
||||
recs.sort((a,b) => {
|
||||
return a.device.name < b.device.name ? -1 : 1;
|
||||
});
|
||||
for (let r of recs) {
|
||||
bind.push({uuid: r.device.uuid, host: r.device.addr[0], port: r.device.port});
|
||||
html.push(`<tr>`);
|
||||
html.push(`<td>${r.device.name}</td>`);
|
||||
html.push(`<td>${r.device.mode}</td>`);
|
||||
html.push(`<td>${r.state}</td>`);
|
||||
html.push(`<td><button id="${r.device.uuid}">admin</button></td>`);
|
||||
html.push(`</tr>`);
|
||||
devc++;
|
||||
}
|
||||
html.push(`</tbody>`);
|
||||
html.push(`</table>`);
|
||||
if (devc) {
|
||||
$('mod-local').innerHTML = html.join('');
|
||||
} else {
|
||||
$('mod-local').innerHTML = `<br><b>no local devices</b>`;
|
||||
}
|
||||
bind.forEach(rec => {
|
||||
$(rec.uuid).onclick = () => {
|
||||
window.open(`http://${rec.host}:${rec.port||4080}/`);
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function setViewMode(mode) {
|
||||
const isCAM = settings.mode() === 'CAM';
|
||||
viewMode = mode;
|
||||
platform.deselect();
|
||||
selection.update_info();
|
||||
// clear any bounds forced by, for example, WireEDM previews
|
||||
api.platform.set_bounds();
|
||||
// disable clear in non-arrange modes
|
||||
['view-arrange','act-slice','act-preview','act-animate'].forEach(el => {
|
||||
$(el).classList.remove('selected')
|
||||
});
|
||||
$('render-tools').classList.add('hide');
|
||||
switch (mode) {
|
||||
case VIEWS.ARRANGE:
|
||||
$('view-arrange').classList.add('selected');
|
||||
$('render-tools').classList.remove('hide');
|
||||
api.function.clear_progress();
|
||||
kiri.client.clear();
|
||||
STACKS.clear();
|
||||
hideSlider();
|
||||
updateSpeeds();
|
||||
setVisibleLayer();
|
||||
setWidgetVisibility(true);
|
||||
api.widgets.opacity(1);
|
||||
api.view.edges(api.local.getBoolean('model.edges'));
|
||||
break;
|
||||
case VIEWS.SLICE:
|
||||
$('act-slice').classList.add('selected');
|
||||
updateSpeeds();
|
||||
updateSliderMax();
|
||||
setWidgetVisibility(true);
|
||||
!isCAM && api.view.edges(false);
|
||||
break;
|
||||
case VIEWS.PREVIEW:
|
||||
$('act-preview').classList.add('selected');
|
||||
setWidgetVisibility(true);
|
||||
!isCAM && api.view.edges(false);
|
||||
break;
|
||||
case VIEWS.ANIMATE:
|
||||
$('act-animate').classList.add('selected');
|
||||
!isCAM && api.view.edges(false);
|
||||
break;
|
||||
default:
|
||||
console.log("invalid view mode: "+mode);
|
||||
return;
|
||||
}
|
||||
api.event.emit('view.set', mode);
|
||||
DOC.activeElement.blur();
|
||||
}
|
||||
|
||||
function getMode() {
|
||||
return settings.mode();
|
||||
}
|
||||
|
||||
function getModeLower() {
|
||||
return getMode().toLowerCase();
|
||||
}
|
||||
|
||||
function switchMode(mode) {
|
||||
setMode(mode, null, platform.update_size);
|
||||
}
|
||||
|
||||
function setMode(mode, lock, then) {
|
||||
if (!MODES[mode]) {
|
||||
console.log("invalid mode: "+mode);
|
||||
mode = 'FDM';
|
||||
}
|
||||
const current = settings.get();
|
||||
// change mode constants
|
||||
current.mode = mode;
|
||||
MODE = MODES[mode];
|
||||
DRIVER = kiri.driver[mode];
|
||||
kiri.driver.current = DRIVER;
|
||||
// gcode edit area for any non-SLA mode
|
||||
api.uc.setVisible($('gcode-edit'), mode !== 'SLA');
|
||||
// highlight selected mode menu item
|
||||
["FDM","CAM","SLA","LASER","DRAG","WJET","WEDM"].forEach(sm => {
|
||||
const cl = $(`mode-${sm.toLowerCase()}`).classList;
|
||||
if (sm === mode) {
|
||||
cl.add('selected');
|
||||
} else {
|
||||
cl.remove('selected');
|
||||
}
|
||||
});
|
||||
// restore cached device profile for this mode
|
||||
if (current.cdev[mode]) {
|
||||
current.device = clone(current.cdev[mode]);
|
||||
api.event.emit('device.select', api.device.get());
|
||||
}
|
||||
// hide/show
|
||||
api.uc.setVisible($('set-tools'), mode === 'CAM');
|
||||
// updates right-hand menu by enabling/disabling fields
|
||||
setViewMode(VIEWS.ARRANGE);
|
||||
UC.setMode(MODE);
|
||||
// sanitize and persist settings
|
||||
api.conf.load();
|
||||
api.conf.save();
|
||||
// other housekeeping
|
||||
triggerSettingsEvent();
|
||||
platform.update_selected();
|
||||
selection.update_bounds(api.widgets.all());
|
||||
api.conf.update_fields();
|
||||
// because device dialog, if showing, needs to be updated
|
||||
if (modalShowing()) {
|
||||
api.show.devices();
|
||||
}
|
||||
api.space.restore(null, true);
|
||||
api.event.emit("mode.set", mode);
|
||||
if (then) {
|
||||
then();
|
||||
}
|
||||
}
|
||||
|
||||
function currentProcessName() {
|
||||
return settings.get().cproc[getMode()];
|
||||
}
|
||||
|
||||
function currentProcessCode() {
|
||||
return settings.get().sproc[getMode()][currentProcessName()];
|
||||
}
|
||||
|
||||
function setControlsVisible(show) {
|
||||
// TODO fix
|
||||
// $('mid-left').style.display = show ? 'flex' : 'none';
|
||||
// $('mid-right').style.display = show ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
function downloadBlob(data, filename) {
|
||||
let url = WIN.URL.createObjectURL(new Blob([data], {type: "octet/stream"}));
|
||||
$('mod-any').innerHTML = `<a id="_dexport_" href="${url}" download="${filename}">x</a>`;
|
||||
$('_dexport_').click();
|
||||
}
|
||||
|
||||
// prevent safari from exiting full screen mode
|
||||
DOC.onkeydown = function (evt) { if (evt.keyCode == 27) evt.preventDefault() }
|
||||
|
||||
// upon restore, seed presets
|
||||
api.event.emit('preset', api.conf.dbo());
|
||||
|
||||
});
|
||||
156
src.old/kiri/pack.js
Normal file
156
src.old/kiri/pack.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* adapted from
|
||||
* http://codeincomplete.com/posts/2011/5/7/bin_packing/
|
||||
*/
|
||||
|
||||
gapp.register("kiri.pack", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
|
||||
class Packer {
|
||||
constructor(w, h, spacing, opt = {}) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
this.spacing = spacing;
|
||||
this.opt = opt;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
const { w, h, spacing, opt } = this;
|
||||
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.opt = opt;
|
||||
this.pad = this.spacing / 2;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return { w: this.w, h: this. h };
|
||||
}
|
||||
|
||||
resize(w, h) {
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
return this;
|
||||
}
|
||||
|
||||
rescale(wm, hm) {
|
||||
this.w *= wm;
|
||||
this.h *= hm;
|
||||
return this;
|
||||
}
|
||||
|
||||
pack(blocks, onretry) {
|
||||
for (;;) {
|
||||
let simple = this.opt.simple;
|
||||
this.fit(blocks, simple);
|
||||
if (!this.packed && onretry && onretry(this)) {
|
||||
this.init();
|
||||
continue;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
// array of blocks/tiles with {w,h} properties
|
||||
fit(blocks, simple) {
|
||||
if (simple) {
|
||||
return this.#fit_simple(blocks);
|
||||
} else {
|
||||
return this.#fit_split(blocks);
|
||||
}
|
||||
}
|
||||
|
||||
// array of blocks/tiles with {w,h} properties
|
||||
#fit_simple(blocks) {
|
||||
let x = 0, y = 0, mh = 0, maxx = 0, maxy = 0;
|
||||
let spacing = this.spacing;
|
||||
for (let block of blocks) {
|
||||
block.fit = { x, y };
|
||||
this.max.w = Math.max(this.max.w, x + block.w);
|
||||
this.max.h = Math.max(this.max.h, y + block.h);
|
||||
maxx = Math.max(maxx, x);
|
||||
maxy = Math.max(maxy, y);
|
||||
mh = Math.max(mh, block.h);
|
||||
x += block.w;
|
||||
if (x > this.root.w) {
|
||||
x = 0;
|
||||
y = y + mh + spacing;
|
||||
mh = 0;
|
||||
} else {
|
||||
x += spacing;
|
||||
}
|
||||
if (y > this.root.h) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
if (this.opt.invx) {
|
||||
for (let block of blocks) {
|
||||
block.fit.x = maxx - block.fit.x;
|
||||
}
|
||||
}
|
||||
if (this.opt.invy) {
|
||||
for (let block of blocks) {
|
||||
block.fit.y = maxy - block.fit.y;
|
||||
}
|
||||
}
|
||||
this.packed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
// array of blocks/tiles with {w,h} properties
|
||||
#fit_split(blocks) {
|
||||
let node, w, h;
|
||||
for (let block of blocks) {
|
||||
w = block.w;
|
||||
h = block.h;
|
||||
if (node = this.#findNode(this.root, w, h)) {
|
||||
block.fit = this.#splitNode(node, w, h);
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
this.packed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
#findNode(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(node, w, h) {
|
||||
let spacing = this.spacing;
|
||||
node.used = true;
|
||||
node.down = {
|
||||
x: node.x,
|
||||
y: node.y + h + spacing,
|
||||
w: node.w,
|
||||
h: node.h - h
|
||||
};
|
||||
node.right = {
|
||||
x: node.x + w + spacing,
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
kiri.Pack = Packer;
|
||||
|
||||
});
|
||||
955
src.old/kiri/platform.js
Normal file
955
src.old/kiri/platform.js
Normal file
|
|
@ -0,0 +1,955 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: moto.space
|
||||
// dep: moto.webui
|
||||
// dep: kiri.api
|
||||
// dep: kiri.consts
|
||||
// dep: kiri.utils
|
||||
// dep: kiri.widget
|
||||
// dep: load.stl
|
||||
// dep: load.obj
|
||||
// dep: load.3mf
|
||||
// dep: load.gbr
|
||||
// use: kiri-mode.cam.tool
|
||||
gapp.register("kiri.platform", [], (root, exports) => {
|
||||
|
||||
const { base, kiri, moto, load } = root;
|
||||
const { api, consts, driver, utils, newWidget, Widget } = kiri;
|
||||
const { ajax, js2o } = utils;
|
||||
const { space } = moto;
|
||||
const { util } = base;
|
||||
const { COLOR, MODES } = consts;
|
||||
|
||||
const V0 = new THREE.Vector3(0,0,0);
|
||||
|
||||
let setbounds = undefined;
|
||||
let grouping = false;
|
||||
let topZ = 0;
|
||||
|
||||
function current() {
|
||||
return api.conf.get();
|
||||
}
|
||||
|
||||
function get_mode() {
|
||||
return api.mode.get_id();
|
||||
}
|
||||
|
||||
function platformUpdateOrigin(update_bounds = true) {
|
||||
if (update_bounds) {
|
||||
platform.update_bounds();
|
||||
}
|
||||
|
||||
const settings = current();
|
||||
const { device, process, controller, stock, bounds } = settings;
|
||||
const { FDM, CAM, SLA, LASER, DRAG, WJET, WEDM } = MODES;
|
||||
const TWOD = [ LASER, DRAG, WJET, WEDM ];
|
||||
const MODE = get_mode();
|
||||
|
||||
let ruler = controller.showRulers;
|
||||
let stockCenter = stock.center || { x: 0, y: 0, z: 0 };
|
||||
let isIndexed = process.camStockIndexed;
|
||||
let isBelt = device.bedBelt;
|
||||
let origin = settings.origin = { x: 0, y: 0, z: 0 };
|
||||
let center = MODE === MODES.FDM ? device.originCenter || device.bedRound :
|
||||
MODE === MODES.SLA ? false :
|
||||
MODE === MODES.CAM ? process.camOriginCenter :
|
||||
device.originCenter || process.ctOriginCenter;
|
||||
|
||||
if (MODE === MODES.CAM && process.camOriginTop) {
|
||||
origin.z = stock.z + 0.01;
|
||||
}
|
||||
|
||||
if (!center) {
|
||||
if (MODE === MODES.CAM) {
|
||||
origin.x = (-stock.x / 2) + stockCenter.x;
|
||||
origin.y = (stock.y / 2) - stockCenter.y;
|
||||
} else {
|
||||
if (TWOD.contains(MODE) && process.ctOriginBounds) {
|
||||
let b = bounds;
|
||||
origin.x = b.min.x,
|
||||
origin.y = -b.min.y
|
||||
} else {
|
||||
origin.x = -device.bedWidth / 2;
|
||||
origin.y = device.bedDepth / 2;
|
||||
}
|
||||
}
|
||||
} else if (MODE === MODES.CAM) {
|
||||
origin.x = stockCenter.x;
|
||||
origin.y = -stockCenter.y;
|
||||
} else if (TWOD.contains(MODE) && process.ctOriginBounds ) {
|
||||
let b = bounds,
|
||||
mx = bounds.min.x,
|
||||
my = bounds.min.y,
|
||||
xd = b.max.x - mx,
|
||||
yd = b.max.y - my;
|
||||
origin.x += (mx + xd / 2);
|
||||
origin.y -= (my + yd / 2);
|
||||
} else if (isBelt) {
|
||||
origin.y = device.bedDepth / 2;
|
||||
}
|
||||
|
||||
// CNC origin offsets
|
||||
if (MODE === MODES.CAM) {
|
||||
origin.x += process.camOriginOffX;
|
||||
origin.y -= process.camOriginOffY;
|
||||
origin.z += process.camOriginOffZ;
|
||||
if (isIndexed) {
|
||||
origin.y = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// WireEDM origin offsets
|
||||
if (MODE === MODES.WEDM && process.ctOriginBounds) {
|
||||
origin.x -= process.ctOriginOffX;
|
||||
origin.y += process.ctOriginOffY;
|
||||
}
|
||||
space.platform.setRulers(ruler, ruler, 1 / api.view.unit_scale(), 'X', isBelt ? 'Z' : 'Y');
|
||||
|
||||
let { x, y, z } = origin;
|
||||
let oz = process.camStockIndexed ? z / 2 : z;
|
||||
if (controller.showOrigin && MODE !== MODES.SLA) {
|
||||
space.platform.setOrigin(x, y, oz, true);
|
||||
} else {
|
||||
space.platform.setOrigin(x, y, oz, false);
|
||||
}
|
||||
}
|
||||
|
||||
function platformUpdateSize(updateDark = true) {
|
||||
const { process, device, controller } = current();
|
||||
const { showRulers, units } = controller;
|
||||
|
||||
space.platform.setRound(device.bedRound);
|
||||
space.platform.setSize(
|
||||
parseInt(device.bedWidth || 300),
|
||||
parseInt(device.bedDepth || 300),
|
||||
parseFloat(device.bedHeight || 5),
|
||||
parseFloat(device.maxHeight || 100)
|
||||
);
|
||||
|
||||
const ruler = showRulers,
|
||||
unitMM = units === 'mm',
|
||||
gridMajor = unitMM ? 25 : 25.4,
|
||||
gridMinor = unitMM ? 5 : 25.4 / 10;
|
||||
|
||||
if (updateDark) {
|
||||
if (controller.dark) {
|
||||
space.platform.set({ light: 0.08 });
|
||||
space.platform.setFont({rulerColor:'#888888'});
|
||||
space.platform.setGrid(gridMajor, gridMinor, 0x666666, 0x333333);
|
||||
space.platform.opacity(0.05);
|
||||
space.sky.set({ color: 0, ambient: { intensity: 0.6 } });
|
||||
document.body.classList.add('dark');
|
||||
} else {
|
||||
space.platform.set({ light: 0.08 });
|
||||
space.platform.setFont({rulerColor:'#333333'});
|
||||
space.platform.setGrid(gridMajor, gridMinor, 0x999999, 0xcccccc);
|
||||
space.platform.opacity(0.2);
|
||||
space.sky.set({ color: 0xffffff, ambient: { intensity: 1.1 } });
|
||||
document.body.classList.remove('dark');
|
||||
}
|
||||
space.platform.setSize();
|
||||
}
|
||||
|
||||
space.platform.setRulers(ruler, ruler, 1 / api.view.unit_scale(), 'X', device.bedBelt ? 'Z' : 'Y');
|
||||
platform.update_origin();
|
||||
}
|
||||
|
||||
function platformUpdateMidZ() {
|
||||
topZ = 0;
|
||||
api.widgets.each(widget => {
|
||||
topZ = Math.max(topZ, widget.mesh.getBoundingBox().max.z);
|
||||
});
|
||||
space.platform.setMaxZ(topZ);
|
||||
}
|
||||
|
||||
function platformUpdateTopZ() {
|
||||
const { process, stock } = current();
|
||||
const MODE = get_mode();
|
||||
api.widgets.each(widget => {
|
||||
if (MODE === MODES.CAM) {
|
||||
const bounds = widget.getBoundingBox();
|
||||
const wzmax = bounds.max.z;
|
||||
const zdelta = process.camZOffset || 0;
|
||||
switch (process.camZAnchor) {
|
||||
case 'top':
|
||||
widget.setTopZ(stock.z - zdelta);
|
||||
break;
|
||||
case 'middle':
|
||||
if (widget.isIndexed) {
|
||||
widget.setTopZ(zdelta);
|
||||
} else {
|
||||
widget.setTopZ(stock.z - (stock.z - wzmax) / 2 + zdelta);
|
||||
}
|
||||
break;
|
||||
case 'bottom':
|
||||
widget.setTopZ(wzmax + zdelta);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
widget.setTopZ(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function platformUpdateStock() {
|
||||
const settings = current();
|
||||
const { bounds, process, mode } = settings;
|
||||
const { camStockX, camStockY, camStockZ, camStockOffset, camStockIndexed } = process;
|
||||
if (mode === 'CAM') {
|
||||
let stock = settings.stock = {
|
||||
x: camStockX,
|
||||
y: camStockY,
|
||||
z: camStockZ
|
||||
};
|
||||
// drop back to offset mode if any stock dimension is 0
|
||||
if (camStockOffset || (stock.x * stock.y * stock.z === 0)) {
|
||||
stock.x += bounds.max.x - bounds.min.x;
|
||||
stock.y += bounds.max.y - bounds.min.y;
|
||||
stock.z += bounds.max.z - bounds.min.z;
|
||||
}
|
||||
stock.center = {
|
||||
x: (bounds.max.x + bounds.min.x) / 2,
|
||||
y: (bounds.max.y + bounds.min.y) / 2,
|
||||
z: camStockIndexed ? 0 : stock.z / 2
|
||||
};
|
||||
} else {
|
||||
settings.stock = {};
|
||||
}
|
||||
}
|
||||
|
||||
function platformSetBounds(bounds) {
|
||||
setbounds = bounds;
|
||||
return platformUpdateBounds();
|
||||
}
|
||||
|
||||
function platformUpdateBounds() {
|
||||
const bounds = setbounds || new THREE.Box3();
|
||||
if (!setbounds)
|
||||
api.widgets.each(widget => {
|
||||
let wp = widget.track.pos;
|
||||
let wb = widget.getBoundingBox().clone();
|
||||
wb.min.x += wp.x;
|
||||
wb.max.x += wp.x;
|
||||
wb.min.y += wp.y;
|
||||
wb.max.y += wp.y;
|
||||
bounds.union(wb);
|
||||
});
|
||||
if (bounds.isEmpty()) {
|
||||
bounds.set(V0, V0);
|
||||
}
|
||||
current().bounds = bounds;
|
||||
platformUpdateStock();
|
||||
platformUpdateTopZ();
|
||||
platformUpdateMidZ();
|
||||
platformUpdateOrigin(false);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
function platformSelectedCount() {
|
||||
return api.view.is_arrange() ? api.selection.count() : 0;
|
||||
}
|
||||
|
||||
function platformUpdateSelected() {
|
||||
const settings = current();
|
||||
|
||||
const { device } = settings;
|
||||
const { extruders } = device;
|
||||
const { selection, ui } = api;
|
||||
|
||||
if (extruders) {
|
||||
for (let i = 0; i < extruders.length; i++) {
|
||||
let b = $(`sel-ext-${i}`);
|
||||
if (b) b.classList.remove('pop-sel');
|
||||
}
|
||||
selection.for_widgets(w => {
|
||||
w.setColor(COLOR.selected);
|
||||
let ext = api.widgets.annotate(w.id).extruder || 0;
|
||||
let b = $(`sel-ext-${ext}`);
|
||||
if (b) b.classList.add('pop-sel');
|
||||
w.saveState();
|
||||
}, true);
|
||||
} else {
|
||||
selection.for_widgets(w => {
|
||||
w.setColor(COLOR.selected);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
function platformSelect(widget, shift, recurse = true) {
|
||||
const { event, selection, view } = api;
|
||||
|
||||
if (!view.is_arrange()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// apply select to entire group
|
||||
if (recurse && widget && widget.group.length > 1) {
|
||||
for (let w of widget.group) {
|
||||
platformSelect(w, true, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (selection.contains(widget)) {
|
||||
if (shift) {
|
||||
platform.deselect(widget, recurse)
|
||||
} else if (selection.count() > 1) {
|
||||
platform.deselect(undefined, recurse);
|
||||
platform.select(widget, false, recurse);
|
||||
}
|
||||
} else {
|
||||
// prevent selection in slice view
|
||||
if (!widget.isVisible()) {
|
||||
return;
|
||||
}
|
||||
if (!shift) {
|
||||
platform.deselect(undefined, recurse);
|
||||
}
|
||||
selection.add(widget);
|
||||
event.emit('widget.select', widget);
|
||||
widget.setColor(COLOR.selected);
|
||||
$(`ws-${widget.id}`)?.classList.add('selected');
|
||||
selection.update_info();
|
||||
}
|
||||
|
||||
platform.update_selected();
|
||||
space.update();
|
||||
}
|
||||
|
||||
function platformDeselect(widget, recurse = true) {
|
||||
const { selection, view } = api;
|
||||
|
||||
if (!view.is_arrange()) {
|
||||
// don't de-select and re-color widgets in,
|
||||
// for example, sliced or preview modes
|
||||
return;
|
||||
}
|
||||
|
||||
// apply deselect to entire group
|
||||
if (recurse && widget && widget.group.length > 1) {
|
||||
for (let w of widget.group) {
|
||||
platformDeselect(w, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!widget) {
|
||||
api.widgets.each(function(widget) {
|
||||
platform.deselect(widget);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (selection.remove(widget)) {
|
||||
api.event.emit('widget.deselect', widget);
|
||||
}
|
||||
|
||||
$(`ws-${widget.id}`)?.classList.remove('selected');
|
||||
widget.setColor(COLOR.deselected);
|
||||
platform.update_selected();
|
||||
selection.update_info();
|
||||
space.update();
|
||||
}
|
||||
|
||||
function platformLoad(url, onload) {
|
||||
if (url.toLowerCase().indexOf(".stl") > 0) {
|
||||
platform.load_stl(url, onload);
|
||||
} else {
|
||||
ajax(url, vertices => {
|
||||
vertices = js2o(vertices).toFloat32();
|
||||
let widget = newWidget().loadVertices(vertices);
|
||||
widget.meta.url = url;
|
||||
platform.add(widget);
|
||||
if (onload) onload(vertices, widget);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function platformLoadSTL(url, onload, formdata, credentials, headers) {
|
||||
new load.STL().load(url, (vertices, filename) => {
|
||||
if (vertices) {
|
||||
let widget = newWidget().loadVertices(vertices);
|
||||
widget.meta.file = filename;
|
||||
platform.add(widget);
|
||||
if (onload) {
|
||||
onload(vertices, widget);
|
||||
}
|
||||
}
|
||||
}, formdata, 1 / api.view.unit_scale(), credentials, headers);
|
||||
}
|
||||
|
||||
function platformLoadURL(url, options = {}) {
|
||||
platform.group();
|
||||
load.URL.load(url, options).then(objects => {
|
||||
let widgets = [];
|
||||
for (let object of objects) {
|
||||
let widget = newWidget(undefined, options.group).loadVertices(object.mesh);
|
||||
widget.meta.file = object.file;
|
||||
platform.add(widget);
|
||||
widgets.push(widget);
|
||||
}
|
||||
platform.group_done();
|
||||
api.event.emit("load.url", {
|
||||
url,
|
||||
options,
|
||||
widgets
|
||||
});
|
||||
}).catch(error => {
|
||||
api.show.alert(error);
|
||||
});
|
||||
}
|
||||
|
||||
function platformGroup() {
|
||||
grouping = true;
|
||||
}
|
||||
|
||||
// called after all new widgets are loaded to update group positions
|
||||
function platformGroupDone(skipLayout) {
|
||||
grouping = false;
|
||||
Widget.Groups.loadDone();
|
||||
if (api.feature.drop_layout && !skipLayout) {
|
||||
platform.layout();
|
||||
}
|
||||
}
|
||||
|
||||
let deferred = [];
|
||||
let deferTimeout;
|
||||
|
||||
function platformAdd(widget, shift, nolayout, defer) {
|
||||
api.widgets.add(widget);
|
||||
space.world.add(widget.mesh);
|
||||
widget.anno = widget.anno || {};
|
||||
widget.anno.extruder = widget.anno.extruder || 0;
|
||||
if (defer) {
|
||||
deferred.push({widget, shift, nolayout});
|
||||
clearTimeout(deferTimeout);
|
||||
deferTimeout = setTimeout(platformAddDeferred, 150);
|
||||
} else {
|
||||
platform.select(widget, shift);
|
||||
platform.update_bounds();
|
||||
api.space.auto_save();
|
||||
platformChanged();
|
||||
api.event.emit('widget.add', widget);
|
||||
if (nolayout) {
|
||||
return;
|
||||
}
|
||||
if (!grouping) {
|
||||
platformGroupDone(nolayout);
|
||||
if (!current().controller.autoLayout) {
|
||||
positionNewWidget(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function platformAddDeferred() {
|
||||
let skiplayout = false;
|
||||
for (let rec of deferred) {
|
||||
let { widget, shift, nolayout } = rec;
|
||||
skiplayout |= nolayout;
|
||||
// platform.select(widget, shift);
|
||||
if (!nolayout && !current().controller.autoLayout) {
|
||||
positionNewWidget(widget);
|
||||
}
|
||||
}
|
||||
if (!grouping) {
|
||||
platformGroupDone(skiplayout);
|
||||
}
|
||||
api.event.emit('widget.add', deferred.map(r => r.widget));
|
||||
platform.update_bounds();
|
||||
api.space.auto_save();
|
||||
platformChanged();
|
||||
deferred = [];
|
||||
}
|
||||
|
||||
function positionNewWidget(widget) {
|
||||
if (api.widgets.count() <= 1) {
|
||||
return;
|
||||
}
|
||||
const settings = current();
|
||||
const { device } = settings;
|
||||
const { bedWidth, bedDepth } = device;
|
||||
const wbb = widget.getBoundingBox();
|
||||
const dim = {
|
||||
x: wbb.max.x - wbb.min.x,
|
||||
y: wbb.max.y - wbb.min.y
|
||||
};
|
||||
const hdim = {
|
||||
x: dim.x / 2,
|
||||
y: dim.y / 2
|
||||
};
|
||||
const bounds = base.newBounds();
|
||||
const target = base.newBounds();
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
const WIDGETS = api.widgets.all();
|
||||
// look for best position for new widget that doesn't collide
|
||||
outer: for (let rad = 10; rad < 200; rad += 10) {
|
||||
inner: for (let d = 0; d < 360; d += 1) {
|
||||
let dx = Math.cos(d * DEG2RAD) * rad;
|
||||
let dy = Math.sin(d * DEG2RAD) * rad;
|
||||
bounds.set(dx - hdim.x, dx + hdim.x, dy - hdim.y, dy + hdim.y);
|
||||
for (let w = 0, wl = WIDGETS.length; w < wl; w++) {
|
||||
let wt = WIDGETS[w];
|
||||
if (wt === widget) {
|
||||
continue;
|
||||
}
|
||||
let tpo = wt.track.pos;
|
||||
let tbb = wt.getBoundingBox();
|
||||
let dim = {
|
||||
x: (tbb.max.x - tbb.min.x) / 2,
|
||||
y: (tbb.max.y - tbb.min.y) / 2
|
||||
};
|
||||
target.set(tpo.x - dim.x, tpo.x + dim.x, tpo.y - dim.y, tpo.y + dim.y);
|
||||
if (target.overlaps(bounds, 5)) {
|
||||
continue inner;
|
||||
}
|
||||
}
|
||||
widget._move(dx, dy, widget.track.pos.z, true);
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function platformDelete(widget, defer) {
|
||||
if (!widget) {
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(widget)) {
|
||||
const mc = widget.slice();
|
||||
for (let i = 0; i < mc.length; i++) {
|
||||
platform.delete(mc[i].widget || mc[i], true);
|
||||
}
|
||||
platformDeletePost();
|
||||
api.event.emit('widget.delete', widget);
|
||||
return;
|
||||
}
|
||||
kiri.client.clear(widget);
|
||||
api.widgets.remove(widget);
|
||||
api.selection.remove(widget);
|
||||
Widget.Groups.remove(widget);
|
||||
space.world.remove(widget.mesh);
|
||||
if (!defer) {
|
||||
platformDeletePost();
|
||||
api.event.emit('widget.delete', widget);
|
||||
}
|
||||
}
|
||||
|
||||
function platformDeletePost() {
|
||||
api.view.update_slider_max();
|
||||
platform.update_bounds();
|
||||
if (get_mode() !== MODES.FDM) {
|
||||
platform.layout();
|
||||
}
|
||||
space.update();
|
||||
platform.update_selected();
|
||||
if (api.feature.drop_layout) {
|
||||
platform.layout();
|
||||
}
|
||||
api.space.auto_save();
|
||||
platformChanged();
|
||||
}
|
||||
|
||||
// render list of current widgets
|
||||
function platformChanged() {
|
||||
h.bind($('ws-widgets'), api.widgets.all().map(w => {
|
||||
let color;
|
||||
let file = w.meta.file || 'no name';
|
||||
let fsho = file.length > 25 ? file.slice(0,20) + '..' + file.slice(-2) : file;
|
||||
return [
|
||||
h.div({
|
||||
onmouseenter() {
|
||||
color = w.getColor();
|
||||
w.setColor(0x0088ff, null, false);
|
||||
},
|
||||
onmouseleave() {
|
||||
w.setColor(color, null, false);
|
||||
},
|
||||
},[
|
||||
h.div({ class: "widpop" }, [
|
||||
h.div({ class: "widopt" }, [
|
||||
h.button(
|
||||
{
|
||||
title: "rename",
|
||||
onclick() { api.widgets.rename(w)
|
||||
}},
|
||||
[ h.i({ class:"fas fa-pen-to-square" }) ]
|
||||
),
|
||||
h.button(
|
||||
{
|
||||
title: "replace",
|
||||
onclick() { api.widgets.replace(null,w) }
|
||||
},
|
||||
[ h.i({ class:"fas fa-rotate" }) ]
|
||||
),
|
||||
h.button(
|
||||
{
|
||||
id: `w-en-${w.id}`,
|
||||
title: "toggle disabled",
|
||||
class: w.meta.disabled ? "disabled" : "",
|
||||
onclick() {
|
||||
let dis = w.meta.disabled = !w.meta.disabled;
|
||||
let sel = api.selection.contains(w);
|
||||
api.uc.setClass($(`w-en-${w.id}`), "disabled", dis);
|
||||
w.setColor(sel ? COLOR.selected : COLOR.deselected, null, false);
|
||||
}
|
||||
},
|
||||
[ h.i({ class:"fas fa-ban" }) ]
|
||||
),
|
||||
h.button(
|
||||
{
|
||||
title: "delete",
|
||||
onclick() { platformDelete(w) }
|
||||
},
|
||||
[ h.i({ class:"fas fa-trash" }), ]
|
||||
)
|
||||
]),
|
||||
h.button([
|
||||
h.i({ class: "fa-solid fa-caret-left" })
|
||||
])
|
||||
]),
|
||||
h.button({
|
||||
_: fsho,
|
||||
id: `ws-${w.id}`,
|
||||
class: "grow name",
|
||||
title: file,
|
||||
// onmouseenter() {
|
||||
// color = w.getColor();
|
||||
// w.setColor(0x0088ff, null, false);
|
||||
// },
|
||||
// onmouseleave() {
|
||||
// w.setColor(color, null, false);
|
||||
// },
|
||||
onclick() {
|
||||
platformSelect(w, true, false);
|
||||
color = w.getColor();
|
||||
}
|
||||
})
|
||||
])
|
||||
]
|
||||
}));
|
||||
}
|
||||
|
||||
function platformSelectAll() {
|
||||
api.widgets.each(widget => {
|
||||
platform.select(widget, true, false)
|
||||
});
|
||||
}
|
||||
|
||||
function platformLayout() {
|
||||
const MODE = get_mode();
|
||||
const settings = current();
|
||||
const { process, device, controller } = settings;
|
||||
const { ui } = api;
|
||||
|
||||
const auto = ui.autoLayout.checked,
|
||||
isBelt = device.bedBelt,
|
||||
isArrange = api.view.is_arrange(),
|
||||
layout = isArrange && auto;
|
||||
|
||||
let gap = controller.spaceLayout;
|
||||
|
||||
switch (MODE) {
|
||||
case MODES.SLA:
|
||||
gap = gap || (process.slaSupportLayers && process.slaSupportDensity ? 2 : 1);
|
||||
break;
|
||||
case MODES.CAM:
|
||||
case MODES.LASER:
|
||||
gap = gap || process.ctOutTileSpacing || 1;
|
||||
break;
|
||||
case MODES.FDM:
|
||||
gap = gap || ((process.sliceSupportExtra || 0) * 2) + 1;
|
||||
// auto resize device to support a larger object
|
||||
if (isBelt) {
|
||||
fitDeviceToWidgets();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
api.view.set_arrange();
|
||||
api.view.hide_slices();
|
||||
api.space.auto_save();
|
||||
|
||||
if (!isArrange) {
|
||||
// skip auto-layout when not in arrange mode
|
||||
api.event.emit('platform.layout');
|
||||
return space.update();
|
||||
}
|
||||
|
||||
// do not layout when switching back from slice view
|
||||
if (!auto || (!space && !layout)) {
|
||||
api.event.emit('platform.layout');
|
||||
return space.update();
|
||||
}
|
||||
|
||||
// TODO: in CNC mode with >1 widget, force layout min spacing @ largest tool diameter
|
||||
|
||||
// space parts to account for anchor in belt mode
|
||||
if (isBelt) {
|
||||
gap += process.firstLayerBeltLead || 0;
|
||||
const WIDGETS = api.widgets.all();
|
||||
let bounds = platformUpdateBounds(),
|
||||
movey = -(device.bedDepth / 2);
|
||||
for (let widget of WIDGETS) {
|
||||
let ylen = widget.track.box.h;
|
||||
// only move the root widget in the group
|
||||
if (widget.id === widget.group.id) {
|
||||
widget.move(0, movey + ylen/2, 0, true);
|
||||
movey += ylen + gap;
|
||||
}
|
||||
}
|
||||
if (controller.spaceLayout === 0) {
|
||||
let sum = 0;
|
||||
let sorted = WIDGETS.sort((a, b) => a.track.pos.y - b.track.pos.y);
|
||||
sorted.forEach(w => {
|
||||
w.move(0, sum, 0);
|
||||
sum += w.track.box.d + 1 + gap;
|
||||
});
|
||||
}
|
||||
if (controller.spaceRandoX) {
|
||||
for (let w of WIDGETS) {
|
||||
w.move(
|
||||
(0.5 - Math.random()) * (device.bedWidth - w.track.box.w - process.firstLayerBrim),
|
||||
0, 0);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let i, m, sz = space.platform.size(),
|
||||
mp = [sz.x, sz.y],
|
||||
ms = [mp[0] / 2, mp[1] / 2],
|
||||
c = Widget.Groups.blocks().sort(),
|
||||
p = new kiri.Pack(ms[0], ms[1], gap).fit(c);
|
||||
|
||||
while (!p.packed) {
|
||||
ms[0] *= 1.1;
|
||||
ms[1] *= 1.1;
|
||||
p = new kiri.Pack(ms[0], ms[1], gap).fit(c);
|
||||
}
|
||||
|
||||
for (i = 0; i < c.length; i++) {
|
||||
m = c[i];
|
||||
m.fit.x += m.w / 2;
|
||||
m.fit.y += m.h / 2;
|
||||
m.move(p.max.w / 2 - m.fit.x, p.max.h / 2 - m.fit.y, 0, true);
|
||||
}
|
||||
}
|
||||
|
||||
platform.update_origin();
|
||||
space.update();
|
||||
api.event.emit('platform.layout');
|
||||
}
|
||||
|
||||
function platformLoadWidget(group, vertices, filename) {
|
||||
const widget = newWidget(undefined, group).loadVertices(vertices.toFloat32(), true);
|
||||
widget.meta.file = filename;
|
||||
if (filename) widget.saveToCatalog(filename);
|
||||
platformAdd(widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
function platformLoadFiles(files, group) {
|
||||
platform.group();
|
||||
let loading = files.length;
|
||||
for (let file of files) {
|
||||
const name = file.name,
|
||||
reader = new FileReader(),
|
||||
lower = name.toLowerCase(),
|
||||
isstl = lower.endsWith(".stl"),
|
||||
isobj = lower.endsWith(".obj"),
|
||||
is3mf = lower.endsWith(".3mf"),
|
||||
issvg = lower.endsWith(".svg"),
|
||||
ispng = lower.endsWith(".png"),
|
||||
isjpg = lower.endsWith(".jpg"),
|
||||
iskmz = lower.endsWith(".kmz"),
|
||||
isini = lower.endsWith(".ini"),
|
||||
isgbr = lower.endsWith(".gbr"),
|
||||
israw = lower.endsWith(".raw") || lower.indexOf('.') < 0,
|
||||
isset = lower.endsWith(".b64") || lower.endsWith(".km"),
|
||||
isgcode = lower.endsWith(".gcode") || lower.endsWith(".nc");
|
||||
reader.file = file;
|
||||
reader.onloadend = function(e) {
|
||||
const data = e.target.result;
|
||||
function load_dec() {
|
||||
if (--loading === 0) {
|
||||
platform.group_done(isgcode);
|
||||
}
|
||||
}
|
||||
if (israw) {
|
||||
platformLoadWidget(group, JSON.parse(data));
|
||||
load_dec();
|
||||
} else if (api.feature.on_load && (isstl || isobj || is3mf)) {
|
||||
api.feature.on_load(data, file);
|
||||
load_dec();
|
||||
} else if (api.feature.on_add_stl && isstl) {
|
||||
api.feature.on_add_stl(data, file);
|
||||
load_dec();
|
||||
} else if (isstl) {
|
||||
const stl = new load.STL().parse(data, api.view.unit_scale());
|
||||
platformLoadWidget(group, stl, name);
|
||||
load_dec();
|
||||
} else if (isobj) {
|
||||
const objs = load.OBJ.parse(data.textDecode('utf-8'));
|
||||
const ondn = function() {
|
||||
for (let obj of objs) {
|
||||
platformLoadWidget(group, obj, obj.name ? `${obj.name}-${name}` : name);
|
||||
}
|
||||
load_dec();
|
||||
};
|
||||
if (objs.length > 1 && !group) {
|
||||
api.uc.confirm('group objects?').then(ok => {
|
||||
group = ok ? [] : group;
|
||||
ondn();
|
||||
});
|
||||
} else {
|
||||
ondn();
|
||||
}
|
||||
} else if (isgbr) {
|
||||
let text = data.textDecode('utf-8');
|
||||
if (api.conf.get().controller.devel) {
|
||||
api.event.emit('cam.parse.gerber', { data: text });
|
||||
} else {
|
||||
kiri.client.gerber2mesh(text, progress => {
|
||||
api.show.progress(progress, "converting");
|
||||
}, vertices => {
|
||||
api.show.progress(0);
|
||||
let wid = platformLoadWidget(group, vertices, name);
|
||||
if (api.mode.is_cam()) {
|
||||
// attach raw illustration
|
||||
// api.event.emit('cam.parse.gerber', { data: text, mesh: wid.mesh });
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (is3mf) {
|
||||
let odon = function(models) {
|
||||
let msg = api.show.alert('Adding Objects');
|
||||
for (let model of models) {
|
||||
platformLoadWidget(group, model.faces, model.name ? `${model.name}-${name}` : name);
|
||||
}
|
||||
load_dec();
|
||||
api.hide.alert(msg);
|
||||
}
|
||||
let msg = api.show.alert('Decoding 3MF');
|
||||
load.TMF.parseAsync(data).then(models => {
|
||||
api.hide.alert(msg);
|
||||
if (models.length > 1 && !group) {
|
||||
api.uc.confirm(`group ${models.length} objects?`).then(ok => {
|
||||
if (ok) {
|
||||
group = [];
|
||||
}
|
||||
odon(models);
|
||||
});
|
||||
} else {
|
||||
odon(models);
|
||||
}
|
||||
});
|
||||
} else if (isgcode) {
|
||||
api.function.parse(data.textDecode('utf-8'), 'gcode');
|
||||
load_dec();
|
||||
} else if (issvg) {
|
||||
loadSVGDialog(opt => {
|
||||
group = group || [];
|
||||
let svg = load.SVG.parse(data.textDecode('utf-8'), opt);
|
||||
let ind = 0;
|
||||
if (svg.length === 0) {
|
||||
api.show.alert(`SVG contains no polylines`, 10);
|
||||
api.show.alert(`Fonts must be converted to paths`, 10);
|
||||
}
|
||||
for (let v of svg) {
|
||||
platformLoadWidget(group, svg[ind++], ind ? `${name}-${ind}` : name);
|
||||
}
|
||||
load_dec();
|
||||
});
|
||||
}
|
||||
else if (iskmz) api.settings.import_zip(data, true);
|
||||
else if (isset) api.settings.import(data.textDecode('utf-8'), true);
|
||||
else if (ispng) api.image.dialog(data, name);
|
||||
else if (isjpg) api.image.convert(data, name);
|
||||
else if (isini) api.settings.import_prusa(data.textDecode('utf-8'));
|
||||
else api.show.alert(`Unsupported file: ${reader.file.name}`);
|
||||
};
|
||||
reader.readAsArrayBuffer(reader.file);
|
||||
}
|
||||
}
|
||||
|
||||
function loadSVGDialog(doit) {
|
||||
const opt = {pre: [
|
||||
"<div class='f-col a-center'>",
|
||||
" <h3>Import SVG</h3>",
|
||||
" <p class='t-just' style='width:300px;line-height:1.5em'>",
|
||||
" Extrude a 3D model from a 2D SVG.",
|
||||
" Fonts must be converted to paths in an SVG editor.",
|
||||
" </p>",
|
||||
" <div class='f-row t-right'><table>",
|
||||
" <tr><th>z height in mm</th><td><input id='svg-depth' value='5' size='3'></td></tr>",
|
||||
" <tr><th title='higher values results in more points. floating point values like 0.5 are accepted'>arc segments / mm</th><td><input id='svg-arcs' value='1' size='3'></td></tr>",
|
||||
" <tr><th title='minimum number of segments in an arc. helps better represent small arcs'>minimum arc segments</th><td><input id='svg-marc' value='10' size='3'></td></tr>",
|
||||
" <tr><th title='interpret dimensions as pixels using dpi'>pixel dpi (optional)</th><td><input id='svg-dpi' value='0' size='3'></td></tr>",
|
||||
" <tr><th>nest shapes</th><td><input id='svg-nest' value='1' type='checkbox' checked></td></tr>",
|
||||
" </table></div>",
|
||||
"</div>"
|
||||
]};
|
||||
api.uc.confirm(undefined, {convert:true, cancel:false}, undefined, opt).then((ok) => {
|
||||
let depth = Math.max(0.1, parseFloat($('svg-depth').value));
|
||||
let arcs = Math.max(0.01, parseFloat($('svg-arcs').value));
|
||||
let marc = Math.max(1, parseInt($('svg-marc').value));
|
||||
let sdpi = Math.max(0, parseInt($('svg-dpi').value));
|
||||
let soup = $('svg-nest').checked;
|
||||
ok && doit({ soup, resolution: arcs, segmin: marc, depth, dpi: sdpi });
|
||||
});
|
||||
}
|
||||
|
||||
function fitDeviceToWidgets() {
|
||||
let maxy = 0;
|
||||
api.widgets.each(widget => {
|
||||
let wb = widget.mesh.getBoundingBox().clone();
|
||||
maxy = Math.max(maxy, wb.max.y - wb.min.y);
|
||||
});
|
||||
const { device } = current();
|
||||
if (maxy > device.bedDepth) {
|
||||
device.bedDepthSave = device.bedDepth;
|
||||
device.bedDepth = maxy + 10;
|
||||
space.platform.setSize(
|
||||
parseInt(device.bedWidth),
|
||||
parseInt(device.bedDepth),
|
||||
parseFloat(device.bedHeight),
|
||||
parseFloat(device.maxHeight)
|
||||
);
|
||||
space.platform.update();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// extend API (api.platform)
|
||||
const platform = Object.assign(api.platform, {
|
||||
fit: fitDeviceToWidgets,
|
||||
add: platformAdd,
|
||||
changed: platformChanged,
|
||||
delete: platformDelete,
|
||||
layout: platformLayout,
|
||||
group: platformGroup,
|
||||
group_done: platformGroupDone,
|
||||
load: platformLoad,
|
||||
load_stl: platformLoadSTL,
|
||||
load_url: platformLoadURL,
|
||||
load_files: platformLoadFiles,
|
||||
load_verts: platformLoadWidget,
|
||||
deselect: platformDeselect,
|
||||
select: platformSelect,
|
||||
select_all: platformSelectAll,
|
||||
selected_count: platformSelectedCount,
|
||||
set_bounds: platformSetBounds,
|
||||
update_origin: platformUpdateOrigin,
|
||||
update_bounds: platformUpdateBounds,
|
||||
update_size: platformUpdateSize,
|
||||
update_top_z: platformUpdateTopZ,
|
||||
update_selected: platformUpdateSelected,
|
||||
update: space.platform.update,
|
||||
set_font: space.platform.setFont,
|
||||
show_axes: space.platform.showAxes,
|
||||
show_volume: space.platform.showVolume,
|
||||
top_z() { return topZ },
|
||||
clear() { api.space.clear(); api.space.save(true) }
|
||||
});
|
||||
|
||||
});
|
||||
688
src.old/kiri/print.js
Normal file
688
src.old/kiri/print.js
Normal file
|
|
@ -0,0 +1,688 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.point
|
||||
// dep: geo.polygon
|
||||
// dep: geo.paths
|
||||
// dep: kiri.consts
|
||||
gapp.register("kiri.print", [], (root, evets) => {
|
||||
|
||||
const { base, kiri } = self;
|
||||
const { paths, util, newPoint } = base;
|
||||
const { arcToPath } = paths;
|
||||
const { numOrDefault } = util;
|
||||
const { beltfact } = kiri.consts;
|
||||
const XAXIS = new THREE.Vector3(1,0,0);
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
|
||||
class Print {
|
||||
constructor(settings, widgets, id) {
|
||||
this.id = id || new Date().getTime().toString(36);
|
||||
this.settings = settings;
|
||||
this.widgets = widgets;
|
||||
this.lastPoint = null;
|
||||
this.lastPoly = null;
|
||||
this.lastEmit = null;
|
||||
this.lastOut = null;
|
||||
this.lastPos = null;
|
||||
this.tools = {};
|
||||
// set to 1 to enable flow rate analysis (console)
|
||||
this.debugE = settings ? (settings.controller.devel ? 1 : 0) : 0;
|
||||
}
|
||||
|
||||
setType(type) {
|
||||
this.nextType = type;
|
||||
}
|
||||
|
||||
// allows for gcode object id annotations enabling
|
||||
// discrete object cancellation during print (bambu)
|
||||
setWidget(widget) {
|
||||
this.widget = widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* addOutput - add a new point to the output gcode array
|
||||
* @param {any[]} array - the output gcode array
|
||||
* @param {Point} point - the new point
|
||||
* @param {number} emit - the extrusion value
|
||||
* @param {number} speed - the feed rate
|
||||
* @param {string} tool - the tool id
|
||||
* @param {"lerp"|string} opts.type - the output type
|
||||
* @param {Point} opts.center - the center of the arc
|
||||
* @param {Point[]} opts.arcPoints - point based approximation of arc used for rendering
|
||||
* @param {unknown} opts.retract - the retraction value used for FDM
|
||||
* @return {Output} - the new output object
|
||||
*/
|
||||
addOutput(array, point, emit, speed, tool, opts) {
|
||||
const { type, retract, center, arcPoints} = opts ?? {};
|
||||
let { lastPoint, lastEmit, lastOut } = this;
|
||||
let arc = emit == 2 || emit == 3;
|
||||
// drop duplicates (usually intruced by FDM bisections)
|
||||
if (lastPoint && point && !arc && type !== 'lerp') {
|
||||
// nested due to uglify confusing browser
|
||||
const { x, y, z } = lastPoint;
|
||||
if (point.x == x && point.y == y && point.z == z && lastEmit == emit) {
|
||||
return lastOut;
|
||||
}
|
||||
}
|
||||
// if (emit && emit < 1) console.log(emit);
|
||||
this.lastPoint = point;
|
||||
this.lastEmit = emit;
|
||||
this.lastOut = lastOut = new Output(point, emit, speed, tool, {
|
||||
type: type ?? this.nextType,
|
||||
center,
|
||||
arcPoints,
|
||||
});
|
||||
if (tool !== undefined) {
|
||||
this.tools[tool] = true;
|
||||
}
|
||||
lastOut.retract = retract;
|
||||
lastOut.widget = this.widget;
|
||||
array.push(lastOut);
|
||||
// console.log("addOutput Called", structuredClone({lastOut,array}))
|
||||
this.nextType = undefined;
|
||||
return lastOut;
|
||||
}
|
||||
|
||||
addPrintPoints(input, output, startPoint, tool) {
|
||||
if (this.startPoint && input.length > 0) {
|
||||
this.lastPoint = this.startPoint;
|
||||
// TODO: revisit seek to origin as the first move
|
||||
// addOutput(output, startPoint, 0, undefined, tool);
|
||||
}
|
||||
output.appendAll(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a polygon to a given output array, possibly with a given extrude factor,
|
||||
* and starting from a given point. The last point is returned.
|
||||
* used for FDM and laser
|
||||
* @param {Polygon} poly - the polygon to print
|
||||
* @param {Point} startPoint - the point to start printing from
|
||||
* @param {Array} output - the array to print to
|
||||
* @param {Object} [options] - optional parameters
|
||||
* @param {boolean} [options.ccw] - set the polygon to be counter-clockwise
|
||||
* @param {number} [options.extrude] - extrude factor for the polygon
|
||||
* @param {number} [options.rate] - print speed in mm/s
|
||||
* @param {number} [options.coast] - distance to coast at the end of the polygon
|
||||
* @param {number} [options.simple] - if true, use the first point of the polygon
|
||||
* @param {number} [options.open] - if true, don't close the polygon
|
||||
* @param {number} [options.tool] - the tool to use
|
||||
* @param {function} [options.onfirst] - called with the first point of the polygon
|
||||
* @param {function} [options.onfirstout] - called with the first output point
|
||||
* @returns {Point} the last point of the polygon
|
||||
*/
|
||||
polyPrintPath(poly, startPoint, output, options = {}) {
|
||||
if (options.ccw) {
|
||||
poly.setCounterClockwise();
|
||||
} else {
|
||||
poly.setClockwise();
|
||||
}
|
||||
|
||||
const scope = this;
|
||||
const { settings } = scope;
|
||||
const { process } = settings;
|
||||
|
||||
let shortDist = process.outputShortDistance,
|
||||
shellMult = numOrDefault(options.extrude, process.outputShellMult),
|
||||
printSpeed = options.rate || process.outputFeedrate,
|
||||
moveSpeed = process.outputSeekrate,
|
||||
minSpeed = process.outputMinSpeed,
|
||||
coastDist = options.coast || 0,
|
||||
closest = options.simple ? poly.first() : poly.findClosestPointTo(startPoint),
|
||||
perimeter = poly.perimeter(),
|
||||
close = !options.open,
|
||||
tool = options.tool,
|
||||
last = startPoint,
|
||||
first = true;
|
||||
|
||||
// if short, use calculated print speed based on sliding scale
|
||||
if (perimeter < process.outputShortPoly) {
|
||||
printSpeed = minSpeed + (printSpeed - minSpeed) * (perimeter / process.outputShortPoly);
|
||||
}
|
||||
|
||||
poly.forEachPoint((point, pos, points, count) => {
|
||||
if (first) {
|
||||
if (options.onfirst) {
|
||||
options.onfirst(point);
|
||||
}
|
||||
// move to first output point on poly
|
||||
let out = scope.addOutput(output, point, 0, moveSpeed, tool);
|
||||
if (options.onfirstout) {
|
||||
options.onfirstout(out);
|
||||
}
|
||||
first = false;
|
||||
} else {
|
||||
let seglen = last.distTo2D(point);
|
||||
if (coastDist && shellMult && perimeter - seglen <= coastDist) {
|
||||
let delta = perimeter - coastDist;
|
||||
let offset = seglen - delta;
|
||||
let offPoint = last.offsetPointFrom(point, offset)
|
||||
scope.addOutput(output, offPoint, shellMult, printSpeed, tool);
|
||||
shellMult = 0;
|
||||
}
|
||||
perimeter -= seglen;
|
||||
scope.addOutput(output, point, shellMult, printSpeed, tool);
|
||||
}
|
||||
last = point;
|
||||
}, close, closest.index);
|
||||
|
||||
this.lastPoly = poly;
|
||||
|
||||
return output.last().point;
|
||||
}
|
||||
|
||||
constReplace(str, consts, start, pad, short) {
|
||||
let cs = str.indexOf("{", start || 0),
|
||||
ce = str.indexOf("}", cs),
|
||||
tok, nutok, nustr;
|
||||
if (cs >=0 && ce > cs) {
|
||||
tok = str.substring(cs+1,ce);
|
||||
let eva = [];
|
||||
for (let [k,v] of Object.entries(consts)) {
|
||||
switch (typeof v) {
|
||||
case 'object':
|
||||
eva.push(`let ${k} = ${JSON.stringify(v)};`);
|
||||
break;
|
||||
case 'number':
|
||||
case 'boolean':
|
||||
eva.push(`let ${k} = ${v};`);
|
||||
break;
|
||||
default:
|
||||
if (v === undefined) v = '';
|
||||
eva.push(`let ${k} = "${v.replace(/\"/g,"\\\"")}";`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
eva.push(`function range(a,b) { return (a + (layer / layers) * (b-a)).round(4) }`);
|
||||
eva.push(`try {( ${tok} )} catch (e) {console.log(e);0}`);
|
||||
let scr = eva.join('');
|
||||
let evl = eval(`{ ${scr} }`);
|
||||
nutok = evl;
|
||||
if (pad === 666) {
|
||||
return evl;
|
||||
}
|
||||
if (pad) {
|
||||
nutok = nutok.toString();
|
||||
let oldln = ce-cs+1;
|
||||
let tokln = nutok.length;
|
||||
if (tokln < oldln) {
|
||||
short = (short || 1) + (oldln - tokln);
|
||||
}
|
||||
}
|
||||
nustr = str.replace("{"+tok+"}",nutok);
|
||||
return this.constReplace(nustr, consts, ce+1+(nustr.length-str.length), pad, short);
|
||||
} else {
|
||||
// insert compensating spaces for accumulated replace string shortages
|
||||
if (short) {
|
||||
let si = str.indexOf(';');
|
||||
if (si > 0) {
|
||||
str = str.replace(';', ';'.padStart(short,' '));
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
parseSVG(code, offset) {
|
||||
let scope = this,
|
||||
svg = new DOMParser().parseFromString(code, 'text/xml'),
|
||||
lines = [...svg.getElementsByTagName('polyline')],
|
||||
output = scope.output = [],
|
||||
bounds = scope.bounds = {
|
||||
max: { x:-Infinity, y:-Infinity, z:-Infinity},
|
||||
min: { x:Infinity, y:Infinity, z:Infinity}
|
||||
};
|
||||
lines.forEach(line => {
|
||||
let seq = [];
|
||||
let points = [...line.points];
|
||||
points.forEach(point => {
|
||||
if (offset) {
|
||||
point.x += offset.x;
|
||||
point.y += offset.y;
|
||||
}
|
||||
if (point.x) bounds.min.x = Math.min(bounds.min.x, point.x);
|
||||
if (point.x) bounds.max.x = Math.max(bounds.max.x, point.x);
|
||||
if (point.y) bounds.min.y = Math.min(bounds.min.y, point.y);
|
||||
if (point.y) bounds.max.y = Math.max(bounds.max.y, point.y);
|
||||
if (point.z) bounds.min.z = Math.min(bounds.min.z, point.z);
|
||||
if (point.z) bounds.max.z = Math.max(bounds.max.z, point.z);
|
||||
const { x, y, z } = point; // SVGPoint is not serializable
|
||||
scope.addOutput(seq, { x, y, z }, seq.length > 0);
|
||||
});
|
||||
output.push(seq);
|
||||
});
|
||||
scope.imported = code;
|
||||
scope.lines = lines.length;
|
||||
scope.bytes = code.length;
|
||||
return scope.output;
|
||||
};
|
||||
|
||||
parseGCode(gcode, offset, progress, done, opts = {}) {
|
||||
const fdm = opts.fdm;
|
||||
const cam = opts.cam;
|
||||
const belt = opts.belt;
|
||||
const lines = gcode
|
||||
.toUpperCase()
|
||||
.replaceAll("X", " X")
|
||||
.replaceAll("Y", " Y")
|
||||
.replaceAll("Z", " Z")
|
||||
.replaceAll("A", " A")
|
||||
.replaceAll("E", " E")
|
||||
.replaceAll("F", " F")
|
||||
.replaceAll("G", " G")
|
||||
.replaceAll("I", " I")
|
||||
.replaceAll("J", " J")
|
||||
.replaceAll(" ", " ")
|
||||
.split("\n");
|
||||
|
||||
const scope = this,
|
||||
// morph = false,
|
||||
morph = true,
|
||||
bounds = scope.bounds = {
|
||||
max: { x:-Infinity, y:-Infinity, z:-Infinity},
|
||||
min: { x:Infinity, y:Infinity, z:Infinity}
|
||||
},
|
||||
pos = {
|
||||
X: 0.0,
|
||||
Y: 0.0,
|
||||
Z: 0.0,
|
||||
A: 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
|
||||
},
|
||||
xoff = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
};
|
||||
|
||||
let dz = 0,
|
||||
abs = true,
|
||||
absE = true,
|
||||
defh = 0,
|
||||
height = 0,
|
||||
factor = 1,
|
||||
tool = 0,
|
||||
time = 0,
|
||||
minf = Infinity,
|
||||
maxf = 0,
|
||||
seq = [],
|
||||
autolayer = true,
|
||||
newlayer = false,
|
||||
hasmoved = false,
|
||||
lastG = 'G1';
|
||||
|
||||
const output = scope.output = [ seq ];
|
||||
const beltaxis = { X: "X", Y: "Z", Z: "Y", E: "E", F: "F" };
|
||||
|
||||
function LOG() {
|
||||
console.log(...[...arguments].map(o => Object.clone(o)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @function processLine
|
||||
* @description parses a line of g-code into individual axis movements
|
||||
* @param {string[]} line - the line of g-code as an array of strings,
|
||||
* each representing a single axis movement
|
||||
* @param {Object} axes - an object to store the axis values
|
||||
* @returns {Object} an object containing the current and previous points
|
||||
*/
|
||||
function processLine(line, axes) {
|
||||
const prevPoint = newPoint(
|
||||
factor * pos.X ,
|
||||
factor * pos.Y ,
|
||||
factor * pos.Z + dz
|
||||
)
|
||||
.add(xoff)
|
||||
.add(off);
|
||||
|
||||
// apply origin offset
|
||||
// for (let layer of output) {
|
||||
// for (let rec of layer) {
|
||||
// let point = rec.point;
|
||||
// point.x += off.X;
|
||||
// point.y += off.Y;
|
||||
// point.z += off.Z;
|
||||
// }
|
||||
// }
|
||||
|
||||
const point = prevPoint.clone()
|
||||
|
||||
line.forEach(tok => {
|
||||
let axis = tok.charAt(0).toUpperCase();
|
||||
if (morph && belt) {
|
||||
axis = beltaxis[axis];
|
||||
}
|
||||
// console.log("position updated",structuredClone(pos))
|
||||
|
||||
let val = parseFloat(tok.substring(1));
|
||||
axes[axis] = val;
|
||||
// if( axis == 'I' || axis == "J") return
|
||||
if (abs) {
|
||||
pos[axis] = val;
|
||||
|
||||
if (axis == "X") point.x = factor * pos.X + xoff.x + off.x
|
||||
else if (axis == "Y") point.y = factor * pos.Y + xoff.y + off.y
|
||||
else if (axis == "Z") point.z = factor * pos.Z + xoff.z + off.z + dz
|
||||
|
||||
|
||||
} else {
|
||||
mov[axis] = val;
|
||||
pos[axis] += val;
|
||||
}
|
||||
// console.log("position updated",structuredClone(pos))
|
||||
});
|
||||
|
||||
let center;
|
||||
if(axes.I !== undefined && axes.J !== undefined) {
|
||||
center = newPoint(
|
||||
factor* axes.I+ xoff.x,
|
||||
factor* axes.J+ xoff.y,
|
||||
0,
|
||||
);
|
||||
}else if(axes.R !== undefined) {
|
||||
center = newPoint(
|
||||
factor* Math.cos(axes.R * DEG2RAD),
|
||||
factor* Math.sin(axes.R * DEG2RAD),
|
||||
0,
|
||||
);
|
||||
}
|
||||
if(center){
|
||||
center = center.add(prevPoint);
|
||||
center.setZ((prevPoint.z+point.z)/2+dz);
|
||||
}
|
||||
|
||||
return {
|
||||
center,
|
||||
point,
|
||||
prevPoint
|
||||
};
|
||||
}
|
||||
|
||||
function outputPoint(point,lastP,emit,{center,arcPoints,retract}) {
|
||||
|
||||
// non-move in a new plane means burp out
|
||||
// the old sequence and start a new one
|
||||
|
||||
if (newlayer || (autolayer && seq.z != point.z)) {
|
||||
newlayer = false;
|
||||
let dz = point.z - seq.z;
|
||||
let nh = dz > 0 ? dz : defh;
|
||||
seq = [];
|
||||
seq.height = height = nh;
|
||||
if (fdm) dz = -height / 2;
|
||||
output.push(seq);
|
||||
}
|
||||
|
||||
if (!hasmoved) {
|
||||
seq.height = seq.z = pos.Z;
|
||||
hasmoved = true;
|
||||
}
|
||||
|
||||
// debug extrusion rate
|
||||
const lastPos = scope.lastPos;
|
||||
if (scope.debugE && fdm && lastPos && pos.E) {
|
||||
// extruder move
|
||||
let dE = (absE ? pos.E - scope.lastPosE : pos.E);
|
||||
// distance moved in XY
|
||||
let dV = point.distTo2D(lastP);
|
||||
// debug print time
|
||||
time += (dV * pos.F) / 1000;
|
||||
// filament per mm
|
||||
let dR = (dE / dV);
|
||||
if (dV > 2 && dE > 0.001) {
|
||||
let lab = (absE ? 'aA' : 'rR')[scope.debugE++ % 2];
|
||||
console.log(lab, height.toFixed(2), dV.toFixed(2), dE.toFixed(3), dR.toFixed(4), pos.F.toFixed(0));
|
||||
}
|
||||
}
|
||||
// add point to current sequence
|
||||
scope.addOutput(seq, point, emit, pos.F, tool,{retract,arcPoints});
|
||||
scope.lastPos = Object.assign({}, pos);
|
||||
scope.lastPosE = pos.E;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles G2 and G3 arcs, which are circular arcs.
|
||||
* @param {boolean} g2 - Whether this is a G2 or G3 arc. G2 is a clockwise arc, G3 is a counter-clockwise arc.
|
||||
* @param {string[]} line - The line of the g-code file that contains the G2 or G3 command.
|
||||
* @param {number} index - The line number of the g-code file that contains the G2 or G3 command.
|
||||
*/
|
||||
function G2G3(g2, line, index) {
|
||||
const axes = {};
|
||||
const {point, prevPoint, center} = processLine(line,axes);
|
||||
|
||||
// console.log(structuredClone({point,prevPoint,center}));
|
||||
|
||||
let arcPoints = arcToPath( prevPoint, point, 64,{ clockwise:g2,center}) ?? []
|
||||
let emit = g2 ? 2 : 3;
|
||||
|
||||
// console.log("clone point",structuredClone({point,prevPoint,center,arcPoints,emit}));
|
||||
// console.log("pointer point",{point,prevPoint,center,arcPoints,emit});
|
||||
|
||||
outputPoint(point,prevPoint,emit,{center,arcPoints});
|
||||
// scope.addOutput(seq, point, emit, pos.F, tool,{center,arcPoints});
|
||||
}
|
||||
|
||||
function G0G1(g0, line) {
|
||||
const mov = {};
|
||||
const axes = {};
|
||||
|
||||
lastG = g0 ? 'G0' : 'G1';
|
||||
const {point, prevPoint} = processLine(line,axes);
|
||||
|
||||
if (morph && belt) {
|
||||
point.y -= point.z * beltfact;
|
||||
point.z *= beltfact;
|
||||
}
|
||||
|
||||
if (pos.A) {
|
||||
let ip = new THREE.Vector3(pos.X, pos.Y, pos.Z)
|
||||
.applyAxisAngle(XAXIS, -pos.A * DEG2RAD);
|
||||
point.x = ip.x;
|
||||
point.y = ip.y;
|
||||
point.z = ip.z;
|
||||
}
|
||||
|
||||
const retract = (fdm && pos.E < 0) || undefined;
|
||||
const moving = g0 || (fdm && (pos.E <= 0 || !(axes.X || axes.Y || axes.Z)));
|
||||
|
||||
if (!moving && point.x) bounds.min.x = Math.min(bounds.min.x, point.x);
|
||||
if (!moving && point.x) bounds.max.x = Math.max(bounds.max.x, point.x);
|
||||
if (!moving && point.y) bounds.min.y = Math.min(bounds.min.y, point.y);
|
||||
if (!moving && point.y) bounds.max.y = Math.max(bounds.max.y, point.y);
|
||||
if (!moving && point.z) bounds.min.z = Math.min(bounds.min.z, point.z);
|
||||
if (!moving && point.z) bounds.max.z = Math.max(bounds.max.z, point.z);
|
||||
|
||||
// update max speed
|
||||
if (pos.F) minf = Math.min(minf, pos.F);
|
||||
maxf = Math.max(maxf, pos.F);
|
||||
|
||||
// always add moves to the current sequence
|
||||
if (moving) {
|
||||
// console.log("move",structuredClone(point))
|
||||
scope.addOutput(seq, point, 0, pos.F, tool,{retract})
|
||||
scope.lastPos = Object.assign({}, pos);
|
||||
return;
|
||||
}
|
||||
if (seq.z === undefined) {
|
||||
seq.z = point.z;
|
||||
}
|
||||
if (fdm && height === 0) {
|
||||
seq.height = defh = height = pos.Z;
|
||||
}
|
||||
|
||||
outputPoint(point,prevPoint,1,{retract})
|
||||
}
|
||||
|
||||
const linemod = cam ? Math.ceil(lines.length / 2500) : 0;
|
||||
|
||||
lines.forEach((line, idx) => {
|
||||
if (linemod && idx % linemod === 0) {
|
||||
newlayer = true;
|
||||
autolayer = false;
|
||||
}
|
||||
if (line.indexOf(';LAYER:') === 0) {
|
||||
newlayer = true;
|
||||
autolayer = false;
|
||||
}
|
||||
if (line.indexOf('- LAYER ') > 0) {
|
||||
seq.height = defh;
|
||||
const hd = line.replace('(','').replace(')','').split(' ');
|
||||
defh = parseFloat(hd[4]);
|
||||
if (fdm) dz = -defh / 2;
|
||||
newlayer = true;
|
||||
autolayer = false;
|
||||
}
|
||||
// if (["X","Y","Z"].indexOf(line.charAt(0)) >= 0) {
|
||||
// line = `G0${line}`;
|
||||
// }
|
||||
line = line.trim().split(";")[0].split(" ").filter(v => v);
|
||||
if (!line.length) return;
|
||||
const c0 = line[0].charAt(0);
|
||||
let cmd = ["X","Y","Z"].indexOf(c0) >= 0 ? lastG : line.shift();
|
||||
if (!cmd) return;
|
||||
if (cmd.charAt(0) === 'T') {
|
||||
let ext = scope.settings.device.extruders;
|
||||
let pos = parseInt(cmd.charAt(1));
|
||||
if (ext && ext[pos]) {
|
||||
xoff.X = -ext[pos].extOffsetX;
|
||||
xoff.Y = -ext[pos].extOffsetY;
|
||||
}
|
||||
}
|
||||
|
||||
pos.E = 0.0;
|
||||
switch (cmd) {
|
||||
case 'M82':
|
||||
absE = true;
|
||||
break;
|
||||
case 'M83':
|
||||
absE = false;
|
||||
break;
|
||||
case 'G20':
|
||||
factor = 25.4;
|
||||
break;
|
||||
case 'G21':
|
||||
factor = 1;
|
||||
break;
|
||||
case 'G90':
|
||||
// absolute positioning
|
||||
abs = true;
|
||||
break;
|
||||
case 'G91':
|
||||
// relative positioning
|
||||
abs = false;
|
||||
break;
|
||||
case 'G92':
|
||||
line.forEach(tok => {
|
||||
pos[tok.charAt(0)] = parseFloat(tok.substring(1));
|
||||
});
|
||||
break;
|
||||
case 'G10':
|
||||
if (seq && seq.length) {
|
||||
seq.last().retract = true;
|
||||
}
|
||||
break;
|
||||
case 'G11':
|
||||
break;
|
||||
case 'G0':
|
||||
G0G1(1, line);
|
||||
break;
|
||||
case 'G1':
|
||||
G0G1(0, line);
|
||||
break;
|
||||
case 'G2':
|
||||
// turn arc into a series of points
|
||||
G2G3(true, line, idx)
|
||||
break;
|
||||
case 'G3':
|
||||
// turn arc into a series of points
|
||||
G2G3(false, line, idx);
|
||||
break;
|
||||
case 'M6':
|
||||
tool = parseInt(line[0].substring(1));
|
||||
break;
|
||||
}
|
||||
});
|
||||
scope.imported = gcode;
|
||||
scope.lines = lines.length;
|
||||
scope.bytes = gcode.length;
|
||||
scope.minSpeed = Math.floor(minf / 60);
|
||||
scope.maxSpeed = Math.floor(maxf / 60);
|
||||
scope.belt = belt;
|
||||
|
||||
if (scope.debugE) {
|
||||
console.log({ bounds, print_time: time.round(2) });
|
||||
}
|
||||
|
||||
console.log(scope.output)
|
||||
done({ output: scope.output });
|
||||
}
|
||||
}
|
||||
|
||||
class Output {
|
||||
/**
|
||||
* Construct a new output element.
|
||||
*
|
||||
* in cam, emit is the G code number (G0, G1, G2, G3)
|
||||
*
|
||||
* @param {Point} point point to emit, with x, y, and z properties
|
||||
* @param {number} emit emit (feed for printers, power for lasers, cut for cam)
|
||||
* @param {number} speed speed in mm/min
|
||||
* @param {number} tool tool id
|
||||
* @param {Object} options options object
|
||||
* @param {string} [options.type] type of point
|
||||
* @param {Point} [options.center] the center of the arc
|
||||
* @param {Point[]} [options.arcPoints] point based approximation of arc
|
||||
*/
|
||||
constructor(point, emit, speed, tool, options) {
|
||||
|
||||
const { type, center, arcPoints } = (options ?? {});
|
||||
//speed, tool, type, center, arcPoints
|
||||
this.point = point;
|
||||
this.emit = Number(emit); //convert bools into 0/1
|
||||
this.speed = speed;
|
||||
this.tool = tool;
|
||||
this.type = type;
|
||||
this.center = center;
|
||||
this.arcPoints = arcPoints;
|
||||
// this.where = new Error().stack.split("\n");
|
||||
}
|
||||
|
||||
clone(z) {
|
||||
let o = new Output(
|
||||
this.point.clone(),
|
||||
this.emit,
|
||||
this.speed,
|
||||
this.tool,
|
||||
this.type
|
||||
);
|
||||
if (z !== undefined) {
|
||||
o.point.setZ(z);
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
set_retract() {
|
||||
this.retract = true;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
function newPrint(settings, widgets, id) {
|
||||
return new Print(settings, widgets, id);
|
||||
};
|
||||
|
||||
gapp.overlay(kiri, {
|
||||
Print,
|
||||
newPrint
|
||||
});
|
||||
|
||||
});
|
||||
546
src.old/kiri/render.js
Normal file
546
src.old/kiri/render.js
Normal file
|
|
@ -0,0 +1,546 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* uses `layers.js` to convert paths (usually preview) into primordial geometries.
|
||||
* the input is based on an array (layers) of arrays containing `Output` objects
|
||||
*/
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.point
|
||||
// dep: geo.polygon
|
||||
gapp.register("kiri.render", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
const { config, util, newPolygon } = base;
|
||||
const hsV = 0.9;
|
||||
const XAXIS = new THREE.Vector3(1,0,0);
|
||||
const DEG2RAD = Math.PI / 180;
|
||||
|
||||
exports({
|
||||
path,
|
||||
is_dark,
|
||||
rate_to_color
|
||||
});
|
||||
|
||||
function is_cam() {
|
||||
return root.worker.print.settings.mode === 'CAM';
|
||||
}
|
||||
|
||||
function is_dark() {
|
||||
return root.worker.print.settings.controller.dark ? true : false;
|
||||
};
|
||||
|
||||
function rate_to_color(rate, max) {
|
||||
if (is_cam()) {
|
||||
return is_dark() ?
|
||||
darkColorFunctionCAM(rate/max, 1, hsV) :
|
||||
lightColorFunctionCAM(rate/max, 1, hsV);
|
||||
} else {
|
||||
return is_dark() ?
|
||||
darkColorFunction(rate/max, 1, hsV) :
|
||||
lightColorFunction(rate/max, 1, hsV);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate visual representation of a gcode program.
|
||||
* @param {Output[][]} levels - Array of arrays containing `Output` objects.
|
||||
* @param {function} update - Called with a completion percentage and the rendered layer.
|
||||
* @param {{tools: Object, flat: Boolean, thin: Boolean, speed: Boolean, lineWidth: Number, toolMode: Boolean, z: Number, action: String, other: String}} [opts] - Optional parameters.
|
||||
* @returns {Promise.kiri.Layers[]} - Array of layers.
|
||||
*/
|
||||
async function path(levels, update, opts = {}) {
|
||||
levels = levels.filter(level => level.length);
|
||||
if (levels.length === 0) {
|
||||
self.worker.print.maxSpeed = 0;
|
||||
return [];
|
||||
}
|
||||
|
||||
const dark = is_dark();
|
||||
const tools = opts.tools || {};
|
||||
const flat = opts.flat;
|
||||
const thin = opts.thin && !flat;
|
||||
const ckspeed = opts.speed !== false;
|
||||
const headColor = 0x888888;
|
||||
const moveColor = opts.move >= 0 ? opts.move : (dark ? 0x666666 : 0xaaaaaa);
|
||||
const printColor = opts.print >= 0 ? opts.print : 0x777700;
|
||||
const arrowAll = true;
|
||||
const arrowSize = arrowAll ? 0.2 : 0.4;
|
||||
const layers = [];
|
||||
const toolMode = opts.toolMode;
|
||||
const lineWidth = opts.lineWidth;
|
||||
|
||||
const moveOpt = {
|
||||
face: moveColor,
|
||||
line: flat ? 1 : moveColor,
|
||||
opacity: flat ? 0.5 : 1
|
||||
};
|
||||
const printOpt = {
|
||||
// fat: thin ? 1.5 : 0,
|
||||
face: printColor,
|
||||
line: flat ? 1 : printColor,
|
||||
opacity: flat ? 0.5 : 1
|
||||
};
|
||||
|
||||
let minspd = Infinity;
|
||||
let maxspd = opts.maxspeed || 0;
|
||||
let maxtool = [];
|
||||
|
||||
for (let level of levels) {
|
||||
for (let o of level) {
|
||||
if (o.speed) {
|
||||
minspd = Math.min(minspd, o.speed);
|
||||
maxspd = Math.max(maxspd, o.speed);
|
||||
}
|
||||
if (toolMode && o.tool !== undefined) {
|
||||
if (maxtool.indexOf(o.tool) < 0) {
|
||||
maxtool.push(o.tool);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// const maxspd = levels.map(level => {
|
||||
// return level.map(o => o.speed || 0).reduce((a, v) => Math.max(a,v));
|
||||
// }).reduce((a, v) => Math.max(a, v)) + 1;
|
||||
|
||||
// for reporting
|
||||
self.worker.print.minSpeed = minspd;
|
||||
self.worker.print.maxSpeed = maxspd;
|
||||
self.worker.print.thinColor = thin;
|
||||
self.worker.print.flatColor = flat;
|
||||
|
||||
let lastTool = null;
|
||||
let lastEnd = null;
|
||||
let lastOut = null;
|
||||
let lastOutPoint = null;
|
||||
let current = null;
|
||||
let currentLaser = null;
|
||||
let retracted = false;
|
||||
let retractz = 0;
|
||||
|
||||
function color(point) {
|
||||
if (toolMode) {
|
||||
return rate_to_color(maxtool.indexOf(point.tool), maxtool.length);
|
||||
} else {
|
||||
return rate_to_color(point.speed, maxspd);
|
||||
}
|
||||
}
|
||||
|
||||
levels.forEach((level, index) => {
|
||||
const prints = {};
|
||||
const moves = [];
|
||||
const heads = [];
|
||||
const changes = [];
|
||||
const retracts = [];
|
||||
const engages = [];
|
||||
const lasers = [];
|
||||
const sparks = [];
|
||||
const output = new kiri.Layers();
|
||||
layers.push(output);
|
||||
|
||||
const pushPrint = (toolid, poly) => {
|
||||
toolid = toolid || 0;
|
||||
const array = prints[toolid] = prints[toolid] || [];
|
||||
const tool = tools[toolid] || {};
|
||||
array.width = (lineWidth || tool.extNozzle || 1) / 2;
|
||||
array.push(poly);
|
||||
emits++;
|
||||
};
|
||||
|
||||
let height = level.height / 2;
|
||||
let width = 1;
|
||||
let emits = 0;
|
||||
|
||||
level.forEach(out => {
|
||||
// skip cam gcode records
|
||||
if (out.gcode) {
|
||||
return;
|
||||
}
|
||||
if (retracted && out.emit) {
|
||||
retracted = false;
|
||||
engages.push(lastOutPoint);
|
||||
}
|
||||
let outPoint = out.point;
|
||||
// rotate 4th axis indexed points
|
||||
// todo -- add spark lines
|
||||
if (outPoint && outPoint.a !== undefined) {
|
||||
let { point } = out;
|
||||
// calculate rotated point
|
||||
let p2 = new THREE.Vector3(point.x, point.y, point.z)
|
||||
.applyAxisAngle(XAXIS, point.a * DEG2RAD);
|
||||
// reconstruct point point for display without A axis
|
||||
outPoint = base.newPoint(p2.x, p2.y, p2.z);
|
||||
// let sp = base.newPoint(outPoint.x * 1.1, outPoint.y * 1.1, outPoint.z * 1.1);
|
||||
// sparks.push(outPoint, sp);
|
||||
}
|
||||
if (out.tool !== lastTool) {
|
||||
lastTool = out.tool;
|
||||
changes.push(outPoint);
|
||||
}
|
||||
if (out.retract) {
|
||||
retracts.push(outPoint);
|
||||
retracted = true;
|
||||
retractz++;
|
||||
}
|
||||
if (!outPoint) {
|
||||
// in cam mode, these are drilling or dwell ops
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastOut) { // if last outpoint is defined
|
||||
|
||||
if (arrowAll || lastOut.emit !== out.emit) {
|
||||
heads.push({p1: lastOutPoint, p2: outPoint});
|
||||
}
|
||||
const op = outPoint, lp = lastOutPoint;
|
||||
// const moved = Math.max(
|
||||
// Math.abs(op.x - lp.x),
|
||||
// Math.abs(op.y - lp.y),
|
||||
// Math.abs(op.z - lp.z));
|
||||
// if (moved < 0.0001) return;
|
||||
if( is_cam() && (out.emit == 2 || out.emit == 3 )) { // cam arc emit
|
||||
// checks if a new poly should be started
|
||||
if (!lastOut.emit || (ckspeed && out.speed !== lastOut.speed) || lastEnd) {
|
||||
current = newPolygon().setOpen();
|
||||
current.push(lastOutPoint);
|
||||
current.color = color(out);
|
||||
pushPrint(out.tool, current);
|
||||
}
|
||||
|
||||
out.arcPoints.forEach(p => {
|
||||
current.push(p);
|
||||
})
|
||||
|
||||
current.push(outPoint);
|
||||
|
||||
} else if (out.emit) {
|
||||
// explicity G1 in CAM mode
|
||||
// just a non-move in other modes
|
||||
|
||||
// checks if a new poly should be started
|
||||
if (!lastOut.emit || (ckspeed && out.speed !== lastOut.speed) || lastEnd) {
|
||||
current = newPolygon().setOpen();
|
||||
current.push(lastOutPoint);
|
||||
current.color = color(out);
|
||||
pushPrint(out.tool, current);
|
||||
}
|
||||
current.push(outPoint);
|
||||
} else {
|
||||
if (lastOut.emit || lastEnd) {
|
||||
current = newPolygon().setOpen();
|
||||
current.push(lastOutPoint);
|
||||
moves.push(current);
|
||||
}
|
||||
current.push(outPoint);
|
||||
}
|
||||
if (out.type === 'laser') {
|
||||
current.isLaser = true;
|
||||
if (out.emit) {
|
||||
if (!lastOut.emit || out.emit !== lastOut.emit) {
|
||||
// off to on or different power
|
||||
currentLaser = newPolygon().setOpen();
|
||||
currentLaser.push(lastOutPoint);
|
||||
currentLaser.color = rate_to_color(out.emit, 1);
|
||||
lasers.push(currentLaser);
|
||||
}
|
||||
currentLaser.push(outPoint);
|
||||
}
|
||||
}
|
||||
lastEnd = null;
|
||||
} else {
|
||||
current = newPolygon().setOpen();
|
||||
current.push(outPoint);
|
||||
if (out.emit) {
|
||||
current.color = color(out);
|
||||
pushPrint(out.tool, current);
|
||||
} else {
|
||||
moves.push(current);
|
||||
}
|
||||
}
|
||||
lastOut = out;
|
||||
lastOutPoint = outPoint;
|
||||
});
|
||||
|
||||
if (!lastOut) {
|
||||
// laser levels alone
|
||||
return;
|
||||
}
|
||||
|
||||
lastEnd = lastOut;
|
||||
|
||||
// all moves with an emit at the very end (common in contouring)
|
||||
if (lastOut.emit && !emits) {
|
||||
pushPrint(lastOut.tool, current)
|
||||
}
|
||||
|
||||
if (lasers.length) {
|
||||
for (let poly of lasers)
|
||||
output
|
||||
.setLayer('power', { line: 0x000055, face: 0x0000ff, opacity: 0.5 }, true)
|
||||
.addPolys([ poly ], { thin: true, z: opts.z, color: poly.color });
|
||||
}
|
||||
|
||||
if (changes.length) {
|
||||
output
|
||||
.setLayer('tool', { line: 0x000055, face: 0x0000ff, opacity: 0.5 }, true)
|
||||
.addAreas(changes.map(point => {
|
||||
return newPolygon().centerCircle(point, 0.2, 4).setZ(point.z + 0.03);
|
||||
}), { outline: true });
|
||||
}
|
||||
|
||||
if (retracts.length) {
|
||||
output
|
||||
.setLayer('retract', { line: 0x550000, face: 0xff0000, opacity: 0.5 }, true)
|
||||
.addAreas(retracts.map(point => {
|
||||
return newPolygon().centerCircle(point, 0.2, 5).setZ(point.z + 0.01);
|
||||
}), { outline: true });
|
||||
}
|
||||
|
||||
if (engages.length) {
|
||||
output
|
||||
.setLayer('engage', { line: 0x005500, face: 0x00ff00, opacity: 0.5 }, true)
|
||||
.addAreas(engages.map(point => {
|
||||
return newPolygon().centerCircle(point, 0.2, 7).setZ(point.z + 0.02);
|
||||
}), { outline: true });
|
||||
}
|
||||
|
||||
if (thin && sparks.length) {
|
||||
let line = dark ? 0xffffff : 0x112233;
|
||||
output
|
||||
.setLayer('sparks', { face: headColor, line, opacity: 0.75 }, true)
|
||||
.addLines(sparks, { thin: true });
|
||||
}
|
||||
|
||||
if (thin && heads.length) {
|
||||
let line = dark ? 0xffffff : 0x112233;
|
||||
output
|
||||
.setLayer('arrows', { face: headColor, line, opacity: 0.75 }, true)
|
||||
.addAreas(heads.map(points => {
|
||||
const {p1, p2} = points;
|
||||
const slope = p2.slopeTo(p1);
|
||||
const s1 = base.newSlopeFromAngle(slope.angle + 20);
|
||||
const s2 = base.newSlopeFromAngle(slope.angle - 20);
|
||||
const p3 = points.p2.projectOnSlope(s1, arrowSize);
|
||||
const p4 = points.p2.projectOnSlope(s2, arrowSize);
|
||||
return newPolygon().addPoints([p2,p3,p4]).setZ(p2.z + 0.01);
|
||||
}), { thin: true, outline: true });
|
||||
}
|
||||
|
||||
// always add moves
|
||||
output
|
||||
.setLayer(opts.other || 'move', moveOpt, opts.moves !== true)
|
||||
.addPolys(moves, { thin: true, z: opts.z });
|
||||
|
||||
// force level when present
|
||||
let pz = level.z ? level.z - height : opts.z;
|
||||
for (let array of Object.values(prints)) {
|
||||
for (let poly of array) {
|
||||
if (flat && poly.appearsClosed()) {
|
||||
poly.setClosed();
|
||||
poly.points.pop();
|
||||
}
|
||||
let action = poly.isLaser ? 'speed' : opts.action || 'print';
|
||||
output
|
||||
.setLayer(action, printOpt)
|
||||
.addPolys([ poly ],
|
||||
thin ? { thin, z: opts.z, color: poly.color } :
|
||||
flat ? {
|
||||
flat, z: pz, color: poly.color,
|
||||
outline: true, offset: array.width, open: poly.open } :
|
||||
{
|
||||
offset: array.width, height, z: pz,
|
||||
color: { face: poly.color, line: poly.color }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
update(index / levels.length, output);
|
||||
});
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
const colorFunctions = {
|
||||
simple: hsv2rgb.bind({ seg: 3, fn: color4 }),
|
||||
light: hsv2rgb.bind({ seg: 4, fn: color4light }),
|
||||
light2: hsv2rgb.bind({ seg: 4, fn: color4light2 }),
|
||||
light_cam: hsv2rgb.bind({ seg: 2, fn: color4light }),
|
||||
dark: hsv2rgb.bind({ seg: 4, fn: color4dark }),
|
||||
dark_cam: hsv2rgb.bind({ seg: 2, fn: color4dark })
|
||||
};
|
||||
|
||||
let lightColorFunction = colorFunctions.light2;
|
||||
let darkColorFunction = colorFunctions.dark;
|
||||
let lightColorFunctionCAM = colorFunctions.light_cam;
|
||||
let darkColorFunctionCAM = colorFunctions.dark_cam;
|
||||
|
||||
// hsv values all = 0 to 1
|
||||
function hsv2rgb(h, s, v) {
|
||||
const div = this.seg;
|
||||
const ss = 1 / div;
|
||||
const seg = Math.floor(h / ss);
|
||||
const rem = h - (seg * ss);
|
||||
const inc = (rem / ss);
|
||||
const dec = (1 - inc);
|
||||
const rgb = {r: 0, g: 0, b: 0};
|
||||
this.fn(rgb, inc, seg);
|
||||
rgb.r = ((rgb.r * 255 * v) & 0xff) << 16;
|
||||
rgb.g = ((rgb.g * 255 * v) & 0xff) << 8;
|
||||
rgb.b = ((rgb.b * 255 * v) & 0xff);
|
||||
return rgb.r | rgb.g | rgb.b;
|
||||
}
|
||||
|
||||
function color5(rgb, inc, seg) {
|
||||
const dec = 1 - inc;
|
||||
switch (seg) {
|
||||
case 0:
|
||||
rgb.r = 1;
|
||||
rgb.g = inc;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 1:
|
||||
rgb.r = dec;
|
||||
rgb.g = 1;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 2:
|
||||
rgb.r = 0;
|
||||
rgb.g = dec;
|
||||
rgb.b = inc;
|
||||
break;
|
||||
case 3:
|
||||
rgb.r = inc;
|
||||
rgb.g = 0;
|
||||
rgb.b = 1;
|
||||
break;
|
||||
case 4:
|
||||
rgb.r = dec;
|
||||
rgb.g = 0;
|
||||
rgb.b = dec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function color4light(rgb, inc, seg) {
|
||||
const dec = 1 - inc;
|
||||
switch (seg) {
|
||||
case 0:
|
||||
rgb.r = 0.8;
|
||||
rgb.g = inc * 0.75;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 1:
|
||||
rgb.r = dec * 0.8;
|
||||
rgb.g = 0.75;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 2:
|
||||
rgb.r = 0;
|
||||
rgb.g = dec * 0.75;
|
||||
rgb.b = inc;
|
||||
break;
|
||||
case 3:
|
||||
rgb.r = inc * 0.75;
|
||||
rgb.g = 0;
|
||||
rgb.b = 1;
|
||||
break;
|
||||
case 4:
|
||||
rgb.r = 0.85;
|
||||
rgb.g = 0;
|
||||
rgb.b = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function color4light2(rgb, inc, seg) {
|
||||
const dec = 1 - inc;
|
||||
switch (seg) {
|
||||
case 0:
|
||||
rgb.r = 0.9;
|
||||
rgb.g = inc * 0.9;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 1:
|
||||
rgb.r = dec * 0.9;
|
||||
rgb.g = 0.8;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 2:
|
||||
rgb.r = 0;
|
||||
rgb.g = dec * 0.8;
|
||||
rgb.b = inc;
|
||||
break;
|
||||
case 3:
|
||||
rgb.r = inc * 0.9;
|
||||
rgb.g = 0;
|
||||
rgb.b = 1;
|
||||
break;
|
||||
case 4:
|
||||
rgb.r = 0.9;
|
||||
rgb.g = 0;
|
||||
rgb.b = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function color4dark(rgb, inc, seg) {
|
||||
const dec = 1 - inc;
|
||||
switch (seg) {
|
||||
case 0:
|
||||
rgb.r = 1;
|
||||
rgb.g = inc;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 1:
|
||||
rgb.r = dec;
|
||||
rgb.g = 1;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 2:
|
||||
rgb.r = 0;
|
||||
rgb.g = dec * 0.85 + 0.25;
|
||||
rgb.b = inc;
|
||||
break;
|
||||
case 3:
|
||||
rgb.r = inc * 0.85;
|
||||
rgb.g = 0.25;
|
||||
rgb.b = 1;
|
||||
break;
|
||||
case 4:
|
||||
rgb.r = 0.85;
|
||||
rgb.g = 0.25;
|
||||
rgb.b = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function color4(rgb, inc, seg) {
|
||||
const dec = 1 - inc;
|
||||
switch (seg) {
|
||||
case 0:
|
||||
rgb.r = 0;
|
||||
rgb.g = inc;
|
||||
rgb.b = 1;
|
||||
break;
|
||||
case 1:
|
||||
rgb.r = inc;
|
||||
rgb.g = 1;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 2:
|
||||
rgb.r = 1;
|
||||
rgb.g = dec;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
case 3:
|
||||
rgb.r = dec;
|
||||
rgb.g = 0;
|
||||
rgb.b = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
325
src.old/kiri/selection.js
Normal file
325
src.old/kiri/selection.js
Normal file
|
|
@ -0,0 +1,325 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: moto.space
|
||||
// dep: kiri.api
|
||||
// dep: kiri.consts
|
||||
// dep: kiri.utils
|
||||
gapp.register("kiri.selection", (root, exports) => {
|
||||
|
||||
const { kiri, moto, noop } = self;
|
||||
const { api, consts, utils } = kiri;
|
||||
const { space } = moto;
|
||||
|
||||
const selectedMeshes = [];
|
||||
|
||||
function for_groups(fn) {
|
||||
let groups = widgets(true).map(w => w.group).uniq();
|
||||
for (let group of groups) {
|
||||
fn(group[0]);
|
||||
}
|
||||
}
|
||||
|
||||
function for_widgets(fn, noauto) {
|
||||
let m = selectedMeshes;
|
||||
let w = api.widgets.all();
|
||||
if (m.length === 0 && w.length) {
|
||||
m = noauto ? [] : w.map(w => w.mesh);
|
||||
}
|
||||
m.slice().forEach(mesh => { fn(mesh.widget) });
|
||||
}
|
||||
|
||||
function for_status(fn) {
|
||||
api.widgets.all().forEach(w => {
|
||||
fn(w, selectedMeshes.contains(w.mesh));
|
||||
});
|
||||
}
|
||||
|
||||
function for_meshes(fn) {
|
||||
selectedMeshes.slice().forEach(mesh => { fn(mesh) });
|
||||
}
|
||||
|
||||
function move(x, y, z, abs) {
|
||||
if (!api.view.is_arrange()) {
|
||||
return;
|
||||
}
|
||||
for_groups(w => w.move(x, y, z, abs));
|
||||
update_bounds();
|
||||
api.platform.update_bounds();
|
||||
api.event.emit('selection.move', {x, y, z, abs});
|
||||
space.update();
|
||||
api.space.auto_save();
|
||||
}
|
||||
|
||||
function update_info() {
|
||||
const ui = api.ui;
|
||||
let bounds = new THREE.Box3(), track;
|
||||
for_meshes(mesh => {
|
||||
bounds = bounds.union(mesh.getBoundingBox());
|
||||
track = mesh.widget.track;
|
||||
});
|
||||
if (bounds.min.x === Infinity) {
|
||||
if (selectedMeshes.length === 0) {
|
||||
ui.sizeX.value = 0;
|
||||
ui.sizeY.value = 0;
|
||||
ui.sizeZ.value = 0;
|
||||
ui.scaleX.value = 1;
|
||||
ui.scaleY.value = 1;
|
||||
ui.scaleZ.value = 1;
|
||||
}
|
||||
return;
|
||||
}
|
||||
let dx = bounds.max.x - bounds.min.x,
|
||||
dy = bounds.max.y - bounds.min.y,
|
||||
dz = bounds.max.z - bounds.min.z,
|
||||
scale = api.view.unit_scale();
|
||||
ui.sizeX.value = ui.sizeX.was = (dx / scale).round(2)
|
||||
ui.sizeY.value = ui.sizeY.was = (dy / scale).round(2)
|
||||
ui.sizeZ.value = ui.sizeZ.was = (dz / scale).round(2)
|
||||
ui.scaleX.value = ui.scaleX.was = track.scale.x.round(2);
|
||||
ui.scaleY.value = ui.scaleY.was = track.scale.y.round(2);
|
||||
ui.scaleZ.value = ui.scaleZ.was = track.scale.z.round(2);
|
||||
update_bounds();
|
||||
}
|
||||
|
||||
function update_bounds(widgets) {
|
||||
const settings = api.conf.get();
|
||||
// update bounds on selection for drag limiting
|
||||
const isBelt = settings.device.bedBelt;
|
||||
if (isBelt) {
|
||||
if (api.platform.fit()) {
|
||||
api.platform.update_origin();
|
||||
space.update();
|
||||
}
|
||||
}
|
||||
const dvy = settings.device.bedDepth;
|
||||
const dvx = settings.device.bedWidth;
|
||||
const bounds_sel = new THREE.Box3();
|
||||
if (!widgets) {
|
||||
widgets = selectedMeshes.map(m => m.widget);
|
||||
}
|
||||
for (let widget of widgets) {
|
||||
const wp = widget.track.pos;
|
||||
const bx = widget.track.box;
|
||||
const miny = wp.y - bx.h / 2 + dvy / 2;
|
||||
const maxy = wp.y + bx.h / 2 + dvy / 2;
|
||||
const minx = wp.x - bx.w / 2 + dvx / 2;
|
||||
const maxx = wp.x + bx.w / 2 + dvx / 2;
|
||||
// keep widget in bounds when rotated or scaled
|
||||
const ylo = miny < 0;
|
||||
const yhi = !isBelt && maxy > dvy
|
||||
if (ylo && !yhi) {
|
||||
widget.move(0, -miny, 0);
|
||||
} else if (yhi && !ylo) {
|
||||
widget.move(0, dvy - maxy, 0);
|
||||
}
|
||||
const xlo = minx < 0;
|
||||
const xhi = maxx > dvx;
|
||||
if (xlo && !xhi) {
|
||||
widget.move(-minx, 0, 0);
|
||||
} else if (xhi && !xlo) {
|
||||
widget.move(dvx - maxx, 0, 0);
|
||||
}
|
||||
|
||||
const wb = widget.mesh.getBoundingBox().clone();
|
||||
wb.min.x += wp.x;
|
||||
wb.max.x += wp.x;
|
||||
wb.min.y += wp.y;
|
||||
wb.max.y += wp.y;
|
||||
bounds_sel.union(wb);
|
||||
}
|
||||
settings.bounds_sel = bounds_sel;
|
||||
}
|
||||
|
||||
function duplicate() {
|
||||
if (!api.view.is_arrange()) {
|
||||
return;
|
||||
}
|
||||
for_widgets(widget => {
|
||||
const mesh = widget.mesh;
|
||||
const bb = mesh.getBoundingBox();
|
||||
const ow = widget;
|
||||
const nw = api.widgets.new().loadGeometry(mesh.geometry.clone());
|
||||
nw.meta.file = ow.meta.file;
|
||||
nw.meta.vertices = ow.meta.vertices;
|
||||
nw.anno = ow.annotations();
|
||||
nw.move(bb.max.x - bb.min.x + 1, 0, 0);
|
||||
api.platform.add(nw, true);
|
||||
api.event.emit("widget.duplicate", nw, ow);
|
||||
});
|
||||
}
|
||||
|
||||
function mirror() {
|
||||
if (!api.view.is_arrange()) {
|
||||
return;
|
||||
}
|
||||
for_widgets(widget => {
|
||||
widget.mirror();
|
||||
});
|
||||
space.update();
|
||||
api.space.auto_save();
|
||||
}
|
||||
|
||||
function scale() {
|
||||
if (!api.view.is_arrange()) {
|
||||
return;
|
||||
}
|
||||
let args = [...arguments];
|
||||
for_groups(w => w.scale(...args));
|
||||
selection.update_bounds();
|
||||
api.platform.update_bounds();
|
||||
api.event.emit('selection.scale', args);
|
||||
// skip update if last argument is strictly 'false'
|
||||
if (args.last() === false) {
|
||||
return;
|
||||
}
|
||||
api.space.auto_save();
|
||||
selection.update_info();
|
||||
space.update();
|
||||
}
|
||||
|
||||
function rotate(x, y, z) {
|
||||
if (!api.view.is_arrange()) {
|
||||
return;
|
||||
}
|
||||
for_groups(w => w.rotate(x, y, z));
|
||||
selection.update_bounds();
|
||||
api.platform.update_bounds();
|
||||
api.event.emit('selection.rotate', {x, y, z});
|
||||
api.space.auto_save();
|
||||
selection.update_info();
|
||||
space.update();
|
||||
}
|
||||
|
||||
function merge() {
|
||||
let sel = widgets();
|
||||
if (sel.length === 0) {
|
||||
sel = api.widgets.all();
|
||||
}
|
||||
let obj = [];
|
||||
sel.forEach(widget => {
|
||||
let {x, y, z} = widget.track.pos || { x:0, y:0, z:0 };
|
||||
let pos = widget.mesh.geometry.attributes.position;
|
||||
let pvals = pos.array;
|
||||
for (let i=0, il=pos.count; i<il; i += 3) {
|
||||
let pi = i * pos.itemSize;
|
||||
obj.push(pvals[pi++] + x, pvals[pi++] + y, pvals[pi++] + z);
|
||||
obj.push(pvals[pi++] + x, pvals[pi++] + y, pvals[pi++] + z);
|
||||
obj.push(pvals[pi++] + x, pvals[pi++] + y, pvals[pi++] + z);
|
||||
}
|
||||
});
|
||||
let w = api.platform.load_verts([], obj, "merged");
|
||||
api.platform.delete(sel);
|
||||
api.platform.select(w);
|
||||
}
|
||||
|
||||
function exportWidgets(format = "stl") {
|
||||
let sel = widgets();
|
||||
if (sel.length === 0) {
|
||||
sel = api.widgets.all();
|
||||
}
|
||||
let facets = 0;
|
||||
let outs = [];
|
||||
sel.forEach(widget => {
|
||||
let mesh = widget.mesh;
|
||||
let geo = mesh.geometry;
|
||||
outs.push({geo, widget});
|
||||
facets += geo.attributes.position.count;
|
||||
});
|
||||
if (format === "obj") {
|
||||
let obj = [];
|
||||
let vpad = 0;
|
||||
for (let out of outs) {
|
||||
let meta = out.widget.meta;
|
||||
let name = meta.file || 'unnamed';
|
||||
obj.push(`g ${name}`);
|
||||
let { position } = out.geo.attributes;
|
||||
let pvals = position.array;
|
||||
for (let i=0, il=position.count; i<il; i += 3) {
|
||||
let pi = i * position.itemSize;
|
||||
obj.push(`v ${pvals[pi++]} ${pvals[pi++]} ${pvals[pi++]}`);
|
||||
obj.push(`v ${pvals[pi++]} ${pvals[pi++]} ${pvals[pi++]}`);
|
||||
obj.push(`v ${pvals[pi++]} ${pvals[pi++]} ${pvals[pi++]}`);
|
||||
obj.push(`f ${i+1+vpad} ${i+2+vpad} ${i+3+vpad}`);
|
||||
}
|
||||
vpad += position.count;
|
||||
}
|
||||
return obj.join('\n');
|
||||
}
|
||||
let stl = new Uint8Array(80 + 4 + facets/3 * 50);
|
||||
let dat = new DataView(stl.buffer);
|
||||
let pos = 84;
|
||||
dat.setInt32(80, facets/3, true);
|
||||
for (let out of outs) {
|
||||
let { position } = out.geo.attributes;
|
||||
let pvals = position.array;
|
||||
for (let i=0, il=position.count; i<il; i += 3) {
|
||||
let pi = i * position.itemSize;
|
||||
let p0 = new THREE.Vector3(pvals[pi++], pvals[pi++], pvals[pi++]);
|
||||
let p1 = new THREE.Vector3(pvals[pi++], pvals[pi++], pvals[pi++]);
|
||||
let p2 = new THREE.Vector3(pvals[pi++], pvals[pi++], pvals[pi++]);
|
||||
let norm = THREE.computeFaceNormal(p0, p1, p2);
|
||||
let xo = 0, yo = 0, zo = 0;
|
||||
if (outs.length > 1) {
|
||||
let {x, y, z} = out.widget.track.pos;
|
||||
xo = x;
|
||||
yo = y;
|
||||
zo = z;
|
||||
}
|
||||
dat.setFloat32(pos + 0, norm.x, true);
|
||||
dat.setFloat32(pos + 4, norm.y, true);
|
||||
dat.setFloat32(pos + 8, norm.z, true);
|
||||
dat.setFloat32(pos + 12, p0.x + xo, true);
|
||||
dat.setFloat32(pos + 16, p0.y + yo, true);
|
||||
dat.setFloat32(pos + 20, p0.z + zo, true);
|
||||
dat.setFloat32(pos + 24, p1.x + xo, true);
|
||||
dat.setFloat32(pos + 28, p1.y + yo, true);
|
||||
dat.setFloat32(pos + 32, p1.z + zo, true);
|
||||
dat.setFloat32(pos + 36, p2.x + xo, true);
|
||||
dat.setFloat32(pos + 40, p2.y + yo, true);
|
||||
dat.setFloat32(pos + 44, p2.z + zo, true);
|
||||
pos += 50;
|
||||
}
|
||||
}
|
||||
return stl;
|
||||
}
|
||||
|
||||
function widgets(orall) {
|
||||
let sel = selectedMeshes.slice().map(m => m.widget);
|
||||
return sel.length ? sel : orall ? api.widgets.all() : []
|
||||
}
|
||||
|
||||
function setDisabled(bool) {
|
||||
for_widgets(w => w.meta.disabled = bool);
|
||||
api.platform.update_selected();
|
||||
}
|
||||
|
||||
// extend API (api.selection)
|
||||
const selection = Object.assign(api.selection, {
|
||||
move,
|
||||
merge,
|
||||
scale,
|
||||
rotate,
|
||||
mirror,
|
||||
duplicate,
|
||||
for_groups,
|
||||
for_meshes,
|
||||
for_status,
|
||||
for_widgets,
|
||||
update_bounds,
|
||||
update_info,
|
||||
widgets,
|
||||
export: exportWidgets,
|
||||
add(w) { selectedMeshes.addOnce(w.mesh) },
|
||||
delete() { api.platform.delete(widgets()) },
|
||||
remove(w) { return selectedMeshes.remove(w.mesh) },
|
||||
count() { return selectedMeshes.length },
|
||||
contains(w) { return selectedMeshes.indexOf(w.mesh) >= 0 },
|
||||
enable() { setDisabled(false) },
|
||||
disable() { setDisabled(true) },
|
||||
opacity() { api.widgets.opacity(...arguments) },
|
||||
meshes() { return selectedMeshes.slice() },
|
||||
});
|
||||
|
||||
});
|
||||
801
src.old/kiri/settings.js
Normal file
801
src.old/kiri/settings.js
Normal file
|
|
@ -0,0 +1,801 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.api
|
||||
// dep: kiri.conf
|
||||
// dep: kiri.utils
|
||||
// dep: moto.space
|
||||
// dep: data.local
|
||||
// use: kiri.widgets
|
||||
// use: ext.base64
|
||||
gapp.register("kiri.settings", (root, exports) => {
|
||||
|
||||
const { data, kiri, moto, noop } = self;
|
||||
const { api, conf, consts, utils } = kiri;
|
||||
const { space } = moto;
|
||||
const { local } = data;
|
||||
const { clone } = Object;
|
||||
const { areEqual, ls2o, js2o } = utils;
|
||||
const { COLOR } = consts;
|
||||
const localFilterKey ='kiri-gcode-filters';
|
||||
const localFilters = js2o(local.getItem(localFilterKey)) || [];
|
||||
|
||||
let settings = clone(conf.template);
|
||||
|
||||
function normalize(set) {
|
||||
return conf.normalize(set);
|
||||
}
|
||||
|
||||
function getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
function putSettings(newset) {
|
||||
settings = normalize(newset);
|
||||
saveSettings();
|
||||
api.space.restore(null, true);
|
||||
}
|
||||
|
||||
function saveSettings() {
|
||||
const { ui } = api;
|
||||
const view = space.view.save();
|
||||
if (view.left || view.up) {
|
||||
settings.controller.view = view;
|
||||
}
|
||||
const mode = settings.mode;
|
||||
settings.sproc[mode].default = settings.process;
|
||||
settings.sproc[mode][settings.process.processName] = settings.process;
|
||||
// moved to init.onBooleanClick() -- delete later after confirming no side-effects
|
||||
// settings.device.bedBelt = ui.deviceBelt.checked;
|
||||
// settings.device.bedRound = ui.deviceRound.checked;
|
||||
// settings.device.originCenter = ui.deviceOrigin.checked || ui.deviceRound.checked;
|
||||
// settings.device.fwRetract = ui.fwRetract.checked;
|
||||
local.setItem('ws-settings', JSON.stringify(settings));
|
||||
api.event.emit('settings.saved', settings);
|
||||
}
|
||||
|
||||
function loadSettings(evt, named) {
|
||||
let mode = settings.mode,
|
||||
name = evt ? evt.target.getAttribute("load") : named || api.process.get() || "default",
|
||||
load = settings.sproc[mode][name];
|
||||
|
||||
if (!load) return;
|
||||
|
||||
// cloning loaded process into settings requires user to save
|
||||
// process before switching devices or risk losing any changes
|
||||
settings.process = clone(load);
|
||||
// update process name
|
||||
settings.process.processName = name;
|
||||
// save named process with the current device
|
||||
settings.devproc[api.device.get()] = name;
|
||||
// preserve name of last library loaded
|
||||
if (name !== 'default') {
|
||||
settings.cproc[mode] = name;
|
||||
}
|
||||
// allow mode driver to take any necessary actions
|
||||
api.event.emit("settings.load", settings);
|
||||
|
||||
// update UI fields to reflect current settings
|
||||
api.conf.update_fields();
|
||||
api.conf.update();
|
||||
|
||||
// publish event when loading from ui event
|
||||
if (evt) api.event.settings();
|
||||
|
||||
// update hidden map from settings
|
||||
api.uc.setHidden(settings.hidden);
|
||||
}
|
||||
|
||||
function showSettings() {
|
||||
api.settings.sync.get().then(() => {
|
||||
api.dialog.update_process_list();
|
||||
api.modal.show("saves");
|
||||
api.ui.settingsName.focus();
|
||||
});
|
||||
}
|
||||
|
||||
function updateSettings(opt = {}) {
|
||||
const { controller, device, process, mode, sproc, cproc } = settings;
|
||||
const { uc } = api;
|
||||
const changes = {};
|
||||
|
||||
updateSettingsFromFields(controller, undefined, changes);
|
||||
|
||||
switch (controller.units) {
|
||||
case 'mm': uc.setUnits(1); break;
|
||||
case 'in': uc.setUnits(25.4); break;
|
||||
}
|
||||
|
||||
// limit update to controller fields (ui prefs dialog)
|
||||
if (opt.controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateSettingsFromFields(device, undefined, changes);
|
||||
|
||||
// range-specific values
|
||||
if (settings.mode === 'FDM' && api.view.is_slice()) {
|
||||
let changes = {};
|
||||
let values = process;
|
||||
let { layer_lo, layer_hi, layer_max } = api.var;
|
||||
let range = { lo: layer_lo, hi: layer_hi };
|
||||
let add = false;
|
||||
if (layer_lo > 0 || layer_hi < layer_max) {
|
||||
values = Object.clone(process);
|
||||
add = true;
|
||||
}
|
||||
// collect changes to pass to range updates
|
||||
updateSettingsFromFields(values, undefined, changes);
|
||||
if (range) {
|
||||
updateRange(range.lo, range.hi, changes, add);
|
||||
}
|
||||
} else {
|
||||
updateSettingsFromFields(process, undefined, changes);
|
||||
}
|
||||
|
||||
if (device.extruders && device.extruders[device.internal]) {
|
||||
updateSettingsFromFields(device.extruders[device.internal]);
|
||||
}
|
||||
|
||||
// invalidate slice, preview, export on settings changes
|
||||
if (Object.keys(changes).length) {
|
||||
api.function.clear_progress();
|
||||
}
|
||||
|
||||
api.conf.save();
|
||||
|
||||
let compare = sproc[mode][cproc[mode]];
|
||||
let same = true;
|
||||
if (compare)
|
||||
for (let [key, val] of Object.entries(compare).filter(v => v[0] !== 'processName')) {
|
||||
let tval = process[key];
|
||||
// outputLoopLayers misbehaving and setting null on empty
|
||||
if (val === '' && tval == null) {
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(tval) && Array.isArray(val)) {
|
||||
if (JSON.stringify(tval) == JSON.stringify(val)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (tval != val) {
|
||||
// console.log(key, 'expected', val, 'got', tval);
|
||||
same = false;
|
||||
}
|
||||
}
|
||||
|
||||
$('mode-device').innerText = device.deviceName;
|
||||
$('mode-profile').innerText = `${cproc[mode]}${same ? '' : ' *'}`;
|
||||
}
|
||||
|
||||
function updateSettingsFromFields(setrec, uirec = api.ui, changes) {
|
||||
if (!setrec) {
|
||||
return console.trace("missing scope");
|
||||
}
|
||||
let lastChange = api.uc.lastChange();
|
||||
|
||||
// for each key in setrec object
|
||||
for (let key in setrec) {
|
||||
if (!setrec.hasOwnProperty(key)) {
|
||||
// console.log({no_setrec: key});
|
||||
continue;
|
||||
}
|
||||
if (!uirec.hasOwnProperty(key)) {
|
||||
// console.log({no_uirec: key});
|
||||
continue;
|
||||
}
|
||||
let nval = null, uie = uirec[key];
|
||||
// skip empty UI values
|
||||
if (!uie || uie === '') {
|
||||
// console.log({uie_empty: key});
|
||||
continue;
|
||||
}
|
||||
if (uie.type === 'text') {
|
||||
nval = uirec[key].convert();
|
||||
} else if (uie.type === 'checkbox') {
|
||||
nval = uirec[key].checked;
|
||||
} else if (uie.type === 'select-one') {
|
||||
if (uie.selectedIndex >= 0) {
|
||||
nval = uie.options[uie.selectedIndex].value;
|
||||
let src = uie.parentNode.getAttribute('source');
|
||||
if (src === 'tools' || src == 'extruders') {
|
||||
nval = parseInt(nval);
|
||||
}
|
||||
} else {
|
||||
nval = setrec[key];
|
||||
}
|
||||
} else if (uie.type === 'textarea') {
|
||||
nval = uie.value.trim().split('\n').filter(v => v !== '');
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (lastChange === uie) {
|
||||
if (changes) changes[key] = nval;
|
||||
}
|
||||
if (!areEqual(setrec[key], nval)) {
|
||||
setrec[key] = nval;
|
||||
if (changes) {
|
||||
changes[key] = nval;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
// given a settings region, update values of matching bound UI fields
|
||||
function updateFieldsFromSettings(setrec, uirec = api.ui, trace) {
|
||||
if (!setrec) {
|
||||
updateFieldsFromSettings(settings.device);
|
||||
updateFieldsFromSettings(settings.process);
|
||||
updateFieldsFromSettings(settings.controller);
|
||||
updateExtruderFields(settings.device);
|
||||
return;
|
||||
}
|
||||
for (let key in setrec) {
|
||||
if (!setrec.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
let val = setrec[key];
|
||||
if (!uirec.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
let uie = uirec[key], typ = uie ? uie.type : null;
|
||||
if (typ === 'text') {
|
||||
if (uie.setv) {
|
||||
uie.setv(val);
|
||||
} else {
|
||||
uie.value = val;
|
||||
}
|
||||
} else if (typ === 'checkbox') {
|
||||
uie.checked = val;
|
||||
} else if (typ === 'select-one') {
|
||||
uie.innerHTML = '';
|
||||
let source = uie.parentNode.getAttribute('source'),
|
||||
list = uie._source || settings[source] || api.lists[source],
|
||||
chosen = null;
|
||||
if (list) list.forEach(function(el, index) {
|
||||
let id = el.id || el.name;
|
||||
let ev = el.value || id;
|
||||
if (val == id) {
|
||||
chosen = index;
|
||||
}
|
||||
let opt = document.createElement('option');
|
||||
opt.appendChild(document.createTextNode(el.name));
|
||||
opt.setAttribute('value', ev);
|
||||
uie.appendChild(opt);
|
||||
});
|
||||
if (chosen) {
|
||||
uie.selectedIndex = chosen;
|
||||
}
|
||||
} else if (typ === 'textarea') {
|
||||
if (Array.isArray(val)) {
|
||||
uie.value = val.join('\n');
|
||||
} else {
|
||||
uie.value = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateExtruderFields(device) {
|
||||
const { ui } = api;
|
||||
const { LANG } = api.const;
|
||||
if (device.extruders && device.extruders[device.internal]) {
|
||||
updateFieldsFromSettings(device.extruders[device.internal]);
|
||||
ui.extruder.innerHTML =
|
||||
`${LANG.dv_gr_ext}<label class='grow'></label>${device.internal+1} of ${device.extruders.length}`;
|
||||
ui.extPrev.disabled = device.internal === 0;
|
||||
ui.extPrev.onclick = function() {
|
||||
device.internal--;
|
||||
updateExtruderFields(device);
|
||||
};
|
||||
ui.extNext.disabled = device.internal === device.extruders.length - 1;
|
||||
ui.extNext.onclick = function() {
|
||||
device.internal++;
|
||||
updateExtruderFields(device);
|
||||
};
|
||||
ui.extDel.disabled = ui.extDel.disabled || device.extruders.length < 2;
|
||||
ui.extDel.onclick = function() {
|
||||
device.extruders.splice(device.internal,1);
|
||||
device.internal = Math.min(device.internal, device.extruders.length-1);
|
||||
updateExtruderFields(device);
|
||||
};
|
||||
ui.extAdd.onclick = function() {
|
||||
let copy = clone(device.extruders[device.internal]);
|
||||
device.extruders.push(copy);
|
||||
device.internal = device.extruders.length - 1;
|
||||
updateExtruderFields(device);
|
||||
};
|
||||
api.uc.setClass($('tool-nozzle'), 'hide', false);
|
||||
h.bind($('ft-nozzle'), device.extruders.map((d,i) => h.div([
|
||||
h.button({
|
||||
_: `extruder ${i}`,
|
||||
onclick() {
|
||||
api.selection.for_widgets(w => {
|
||||
w.anno.extruder = i;
|
||||
w.setColor(
|
||||
api.selection.contains(w) ?
|
||||
COLOR.selected : COLOR.deselected,
|
||||
undefined,
|
||||
false);
|
||||
});
|
||||
}
|
||||
}),
|
||||
h.div({
|
||||
class: "splat",
|
||||
style: `background-color: #${COLOR.deselected[i].toString(16)}`
|
||||
})
|
||||
])));
|
||||
} else {
|
||||
api.uc.setClass($('tool-nozzle'), 'hide', true);
|
||||
}
|
||||
}
|
||||
|
||||
function getOverlappingRanges(lo, hi) {
|
||||
let ranges = [];
|
||||
for (let range of settings.process.ranges || []) {
|
||||
let in_lo = range.lo >= lo && range.lo <= hi;
|
||||
let in_hi = range.hi >= lo && range.hi <= hi;
|
||||
if (in_lo || in_hi) {
|
||||
ranges.push(range);
|
||||
}
|
||||
}
|
||||
return ranges;
|
||||
}
|
||||
|
||||
// set process override values for a range
|
||||
function updateRange(lo, hi, values, add) {
|
||||
let ranges = settings.process.ranges;
|
||||
let slices = {};
|
||||
let min = lo;
|
||||
let max = hi;
|
||||
|
||||
// special case for belt loops which should not be flattened
|
||||
if (values.outputLoops) {
|
||||
ranges.push({
|
||||
lo, hi, fields: values
|
||||
});
|
||||
api.conf.update_fields(settings.process);
|
||||
api.show.alert("update ranges", 2);
|
||||
api.event.emit("range.updates", ranges);
|
||||
return;
|
||||
}
|
||||
|
||||
// just remove values from matching ranges
|
||||
if (!add) {
|
||||
for (let range of getOverlappingRanges(lo, hi)) {
|
||||
for (let key of Object.keys(values)) {
|
||||
delete range.fields[key];
|
||||
}
|
||||
if (Object.keys(range.fields).length === 0) {
|
||||
let pos = ranges.indexOf(range);
|
||||
if (pos >= 0) {
|
||||
ranges.splice(pos,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
api.event.emit("range.updates", ranges);
|
||||
return;
|
||||
}
|
||||
|
||||
// set aside belt loops and re-append later
|
||||
// since we do not want to collapse/merge loops
|
||||
let exclude = ranges.filter(r => r.fields.outputLoops);
|
||||
ranges = ranges.filter(r => !r.fields.outputLoops);
|
||||
|
||||
// flatten ranges
|
||||
ranges.push({lo, hi, fields: values});
|
||||
for (let range of ranges) {
|
||||
min = Math.min(range.lo, min);
|
||||
max = Math.max(range.hi, max);
|
||||
for (let i=range.lo; i<=range.hi; i++) {
|
||||
let slice = slices[i];
|
||||
if (!slice) {
|
||||
slice = slices[i] = {};
|
||||
}
|
||||
for (let [key,val] of Object.entries(range.fields)) {
|
||||
slice[key] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// merge contiguous matching ranges
|
||||
ranges = settings.process.ranges = [];
|
||||
let range;
|
||||
for (let i=min; i<=max; i++) {
|
||||
let slice = slices[i];
|
||||
if (slice && !range) {
|
||||
range = {lo: i, hi: i, fields: slice};
|
||||
} else if (slice && range && areEqual(range.fields, slice)) {
|
||||
range.hi = i;
|
||||
} else if (range) {
|
||||
ranges.push(range);
|
||||
if (slice) {
|
||||
range = {lo: i, hi: i, fields: slice};
|
||||
} else {
|
||||
range = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ranges.push(range);
|
||||
ranges.appendAll(exclude);
|
||||
|
||||
api.conf.update_fields(settings.process);
|
||||
api.show.alert("update ranges", 2);
|
||||
api.event.emit("range.updates", ranges);
|
||||
}
|
||||
|
||||
let overrides = {};
|
||||
|
||||
// updates editable fields that are range dependent
|
||||
function updateFieldsFromRange() {
|
||||
if (true) return;
|
||||
if (settings.mode !== 'FDM' || !api.view.is_slice() || !settings.process.ranges) {
|
||||
let okeys = Object.keys(overrides);
|
||||
if (okeys.length) {
|
||||
api.conf.update_fields(overrides);
|
||||
overrides = {};
|
||||
}
|
||||
return;
|
||||
}
|
||||
let match = 0;
|
||||
let values = {};
|
||||
let restores = Object.clone(overrides);
|
||||
let { layer_lo, layer_hi } = api.var;
|
||||
for (let range of getOverlappingRanges(api.var.layer_lo, api.var.layer_hi)) {
|
||||
for (let key of Object.keys(range.fields)) {
|
||||
values[key] = range.fields[key];
|
||||
overrides[key] = settings.process[key];
|
||||
delete restores[key];
|
||||
match++;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
api.conf.update_fields(values);
|
||||
}
|
||||
let rkeys = Object.keys(restores);
|
||||
if (rkeys.length) {
|
||||
api.conf.update_fields(restores);
|
||||
for (let key of rkeys) {
|
||||
delete overrides[key];
|
||||
}
|
||||
}
|
||||
api.uc.refresh();
|
||||
}
|
||||
|
||||
function restoreSettings(save) {
|
||||
const widgets = api.widgets.all();
|
||||
const newset = ls2o('ws-settings') || settings;
|
||||
// extract legacy widget annotations into widgets
|
||||
if (newset.widget) {
|
||||
for (let id of Object.keys(newset.widget)) {
|
||||
let anno = newset.widget[id];
|
||||
let wid = widgets.filter(w => w.id === id)[0];
|
||||
if (wid && anno) {
|
||||
wid.anno = anno;
|
||||
console.log('transfer settings annotations to widget', id);
|
||||
wid.saveState();
|
||||
} else {
|
||||
console.log('missing widget for annotations', id);
|
||||
}
|
||||
delete newset.widget[id];
|
||||
}
|
||||
}
|
||||
settings = normalize(newset);
|
||||
// override camera from settings
|
||||
if (settings.controller.view) {
|
||||
local.removeItem('ws-camera');
|
||||
}
|
||||
// merge custom filters from localstorage into settings
|
||||
localFilters.forEach(function(fname) {
|
||||
let fkey = "gcode-filter-"+fname, ov = ls2o(fkey);
|
||||
if (ov) settings.devices[fname] = ov;
|
||||
local.removeItem(fkey)
|
||||
});
|
||||
local.removeItem(localFilterKey);
|
||||
// save updated settings
|
||||
if (save) api.conf.save();
|
||||
|
||||
return newset;
|
||||
}
|
||||
|
||||
function settingsExport(opts = {}) {
|
||||
const widgets = api.widgets.all();
|
||||
const note = opts.node || undefined;
|
||||
const shot = opts.work || opts.screen ? space.screenshot() : undefined;
|
||||
const work = opts.work ? kiri.codec.encode(widgets,{_json_:true}) : undefined;
|
||||
const view = opts.work ? space.view.save() : undefined;
|
||||
const setn = Object.clone(settings);
|
||||
// stuff in legacy annotations for re-import
|
||||
for (let w of widgets) {
|
||||
setn.widget[w.id] = w.anno;
|
||||
}
|
||||
const xprt = {
|
||||
settings: setn,
|
||||
version: kiri.version,
|
||||
screen: shot,
|
||||
space: space.info,
|
||||
note: note,
|
||||
work: work,
|
||||
view: view,
|
||||
moto: moto.id,
|
||||
init: local.getItem('kiri-init'),
|
||||
time: Date.now()
|
||||
};
|
||||
return opts.clear ? xprt : api.util.b64enc(xprt);
|
||||
}
|
||||
|
||||
function settingsImport(data, ask) {
|
||||
const { uc, ui } = api;
|
||||
if (typeof(data) === 'string') {
|
||||
try {
|
||||
data = data.charAt(0) === '{' ? JSON.parse(data) : api.util.b64dec(data);
|
||||
} catch (e) {
|
||||
uc.alert('invalid import format');
|
||||
console.log('data',data,{type: typeof data},e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (api.const.LOCAL) console.log('import', data);
|
||||
let isSettings = (data.settings && data.version && data.time);
|
||||
let isProcess = (data.process && data.version && data.time && data.mode && data.name);
|
||||
let isDevice = (data.device && data.version && data.time);
|
||||
let isTools = (data.tools && data.version && data.time)
|
||||
let isWork = (data.work);
|
||||
if (!isSettings && !isDevice && !isProcess && !isTools) {
|
||||
uc.alert('invalid settings or device format');
|
||||
console.log('data',data);
|
||||
return;
|
||||
}
|
||||
function doit() {
|
||||
function devset() {
|
||||
settings.devices[data.device] = data.code;
|
||||
settings.devproc[data.device] = data.name;
|
||||
api.show.devices();
|
||||
}
|
||||
function procset() {
|
||||
settings.sproc[data.mode][data.name] = data.process;
|
||||
if (!isDevice) {
|
||||
api.conf.show();
|
||||
}
|
||||
}
|
||||
if (isDevice) {
|
||||
if (data.process && data.process.processName && data.code.mode) {
|
||||
data.mode = data.code.mode;
|
||||
data.name = data.process.processName;
|
||||
isProcess = true;
|
||||
}
|
||||
if (settings.devices[data.device]) {
|
||||
uc.confirm(`Replace device ${data.device}?`).then(yes => {
|
||||
if (yes) devset()
|
||||
});
|
||||
} else {
|
||||
devset();
|
||||
}
|
||||
}
|
||||
if (isProcess) {
|
||||
console.log({ data });
|
||||
if (data.name === 'default') data.name = data.device || data.name;
|
||||
if (settings.sproc[data.mode][data.name]) {
|
||||
uc.confirm(`Replace process ${data.name}?`).then(yes => {
|
||||
if (yes) procset();
|
||||
});
|
||||
} else {
|
||||
procset();
|
||||
}
|
||||
}
|
||||
if (isSettings) {
|
||||
api.space.clear();
|
||||
settings = normalize(data.settings);
|
||||
local.setItem('ws-settings', JSON.stringify(settings));
|
||||
if (api.const.LOCAL) console.log('settings', Object.clone(settings));
|
||||
if (isWork) {
|
||||
api.platform.clear();
|
||||
// really old workspaces encoded the types as strings
|
||||
for (let work of data.work) {
|
||||
if (work.type === "widget") {
|
||||
work.type = 100;
|
||||
}
|
||||
}
|
||||
kiri.codec.decode(data.work).forEach(widget => {
|
||||
api.platform.add(widget, 0, true, true);
|
||||
});
|
||||
if (data.view) {
|
||||
space.view.load(data.view);
|
||||
}
|
||||
}
|
||||
restoreSettings();
|
||||
api.space.restore(() => { ui.sync() }, true);
|
||||
}
|
||||
if (isTools && Array.isArray(data.tools)) {
|
||||
const settool = settings.tools;
|
||||
for (let tool of data.tools) {
|
||||
if (settool.filter(t => t.id === tool.id).length === 0) {
|
||||
settool.push(tool);
|
||||
}
|
||||
}
|
||||
// settings.tools = data.tools;
|
||||
api.show.tools();
|
||||
}
|
||||
}
|
||||
if (ask) {
|
||||
let opt = {};
|
||||
let prompt = isDevice ?
|
||||
`Import device "${data.device}"?` : isProcess ?
|
||||
`Import process "${data.name}"?` : isTools ?
|
||||
`Import tool definitions?`:
|
||||
`Import settings made in Kiri:Moto version ${data.version} on<br>${new Date(data.time)}?`;
|
||||
if (data.screen) {
|
||||
opt.pre = [
|
||||
'<div class="f-col a-center">',
|
||||
`<img src="${data.screen}" style="width:300px"/>`,
|
||||
'</div>'
|
||||
];
|
||||
}
|
||||
uc.confirm(prompt,undefined,undefined,opt).then((yes) => {
|
||||
if (yes) doit();
|
||||
});
|
||||
} else {
|
||||
doit();
|
||||
}
|
||||
}
|
||||
|
||||
function settingsImportUrl(url, ask) {
|
||||
const kmz = url.endsWith(".kmz");
|
||||
fetch(url).then(r => kmz ? r.arrayBuffer() : r.text()).then(a => {
|
||||
kmz ? settingsImportZip(a, ask) : settingsImport(a, ask);
|
||||
}).catch(error => {
|
||||
console.log({workspace_url: url, error: error.message || error});
|
||||
api.show.alert('workspace load failed');
|
||||
});
|
||||
}
|
||||
|
||||
function settingsImportZip(data, ask) {
|
||||
let alert = api.show.alert("Importing Workspace");
|
||||
JSZip.loadAsync(data).then(zip => {
|
||||
for (let [key,value] of Object.entries(zip.files)) {
|
||||
if (key === "workspace.json") {
|
||||
value.async("string").then(json => {
|
||||
api.hide.alert(alert);
|
||||
settingsImport(JSON.parse(json), ask);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// import and convert prusa ini file
|
||||
function settingsPrusaConvert(data) {
|
||||
const { uc } = api;
|
||||
const map = {};
|
||||
const vsub = {
|
||||
'[first_layer_bed_temperature]': '{bed_temp}',
|
||||
'[first_layer_temperature]': '{temp}',
|
||||
'[layer_z]': '{z}'
|
||||
};
|
||||
try {
|
||||
data.split('\n')
|
||||
.filter(l => l.charAt(0) !== '#')
|
||||
.map(l => l.split('=').map(v => v.trim()))
|
||||
.map(l => {
|
||||
// convert gcode string into a string array
|
||||
if (l[0].indexOf('_gcode') > 0) {
|
||||
l[1] = l[1].replaceAll('\\n','\n').split('\n')
|
||||
.map(line => {
|
||||
for (let [k,v] of Object.entries(vsub)) {
|
||||
line = line.replace(k,v);
|
||||
}
|
||||
return line;
|
||||
});
|
||||
}
|
||||
return l;
|
||||
})
|
||||
.forEach(l => {
|
||||
map[l[0]] = l[1];
|
||||
});
|
||||
} catch (e) {
|
||||
return uc.alert('invalid file');
|
||||
}
|
||||
// device setup
|
||||
let device = Object.clone(conf.defaults.fdm.d);
|
||||
let dname = device.deviceName = map.printer_model;
|
||||
if (dname) {
|
||||
device.mode = "FDM";
|
||||
device.extruders[0].extNozzle = parseFloat(map.nozzle_diameter);
|
||||
device.gcodePre = map.start_gcode;
|
||||
device.gcodePost = map.end_gcode;
|
||||
device.gcodeLayer = map.layer_gcode || [];
|
||||
device.maxHeight = parseInt(map.max_print_height || device.maxHeight);
|
||||
if (map.bed_shape) {
|
||||
let shape = map.bed_shape.split(',').map(l => l.split('x'));
|
||||
device.bedWidth = parseInt(shape[2][0]);
|
||||
device.bedDepth = parseInt(shape[2][1]);
|
||||
}
|
||||
}
|
||||
// profile setup
|
||||
let process = Object.clone(conf.defaults.fdm.p);
|
||||
let pname = process.processName = map.print_settings_id;
|
||||
if (pname) {
|
||||
process.sliceShells = parseInt(map.perimeters);
|
||||
process.sliceHeight = parseFloat(map.layer_height);
|
||||
process.outputFeedrate = parseInt(map.perimeter_speed);
|
||||
process.outputSeekrate = parseInt(map.travel_speed);
|
||||
process.outputTemp = parseInt(map.temperature);
|
||||
process.outputBedTemp = parseInt(map.bed_temperature);
|
||||
process.sliceTopLayers = parseInt(map.top_solid_layers);
|
||||
process.sliceBottomLayers = parseInt(map.bottom_solid_layers);
|
||||
process.firstSliceHeight = parseFloat(map.first_layer_height);
|
||||
process.firstLayerNozzleTemp = parseInt(map.first_layer_temperature);
|
||||
process.firstLayerRate = (
|
||||
(parseFloat(map.first_layer_speed) / 100) * process.outputFeedrate);
|
||||
process.firstLayerBedTemp = parseInt(map.first_layer_bed_temperature);
|
||||
process.outputRetractDist = parseFloat(map.retract_length);
|
||||
process.outputRetractSpeed = parseFloat(map.retract_speed);
|
||||
}
|
||||
uc.confirm(`Import "${dname}"?`).then(yes => {
|
||||
if (yes) {
|
||||
// create device, associated profile, set as current and show dialog
|
||||
settings.devices[dname] = device;
|
||||
settings.devproc[dname] = pname;
|
||||
settings.process = settings.sproc.FDM[pname] = process;
|
||||
settings.filter.FDM = dname;
|
||||
settings.cproc.FDM = pname;
|
||||
api.show.devices();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setThreaded(bool) {
|
||||
bool = bool ? true : false;
|
||||
settings.controller.threaded = bool;
|
||||
api.event.emit("set.threaded", bool);
|
||||
}
|
||||
|
||||
function setEnableWASM(bool) {
|
||||
bool = bool ? true : false;
|
||||
settings.controller.assembly = bool;
|
||||
api.event.emit("set.assembly", bool);
|
||||
}
|
||||
|
||||
// extend API (api.conf)
|
||||
Object.assign(api.conf, {
|
||||
dbo: () => { return ls2o('ws-settings') },
|
||||
get: getSettings,
|
||||
put: putSettings,
|
||||
load: loadSettings,
|
||||
save: saveSettings,
|
||||
show: showSettings,
|
||||
update: updateSettings,
|
||||
update_from: updateSettingsFromFields,
|
||||
update_fields: updateFieldsFromSettings,
|
||||
update_fields_from_range: updateFieldsFromRange,
|
||||
set_enable_wasm: setEnableWASM,
|
||||
set_threaded: setThreaded,
|
||||
restore: restoreSettings,
|
||||
export: settingsExport,
|
||||
import: settingsImport,
|
||||
});
|
||||
|
||||
// extend API (api.settings)
|
||||
Object.assign(api.settings, {
|
||||
get: getSettings,
|
||||
import: settingsImport,
|
||||
import_zip: settingsImportZip,
|
||||
import_url: settingsImportUrl,
|
||||
import_prusa: settingsPrusaConvert,
|
||||
dev() { return settings.device },
|
||||
proc() { return settings.process },
|
||||
ctrl() { return settings.controller },
|
||||
mode() { return settings.mode },
|
||||
prof() { return settings.sproc[settings.mode] },
|
||||
sync: {
|
||||
async get() {},
|
||||
async put() {},
|
||||
status: false
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
262
src.old/kiri/slice.js
Normal file
262
src.old/kiri/slice.js
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: geo.base
|
||||
// dep: geo.polygons
|
||||
gapp.register("kiri.slice", [], (root, exports) => {
|
||||
|
||||
const { base, kiri } = root;
|
||||
|
||||
const POLY = base.polygons;
|
||||
|
||||
let tracker;
|
||||
|
||||
function setSliceTracker(nv) {
|
||||
return tracker = nv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object encapsulates a z-slice from an object. This code is shared by the
|
||||
* client and the worker thread. As such, the view layers are ignored in the
|
||||
* worker code paths.
|
||||
*/
|
||||
class Slice {
|
||||
|
||||
constructor (z, view) {
|
||||
this.z = z; // z-index
|
||||
this.index = 0; // slice index
|
||||
this.lines = null; // slice raw
|
||||
this.groups = null; // grouped lines
|
||||
this.up = null; // slice above (linked list)
|
||||
this.down = null; // slice below (linked list)
|
||||
this.tops = []; // array of Top objects
|
||||
this.view = view; // for rendering this slice
|
||||
this.finger = null; // cached fingerprint
|
||||
this.layers = null; // will replace most of the layer output data
|
||||
}
|
||||
|
||||
/**
|
||||
* return Layers object for this slice. creates it if necessary.
|
||||
*/
|
||||
output() {
|
||||
if (this.layers) return this.layers;
|
||||
let layers = this.layers = new kiri.Layers();
|
||||
if (tracker) {
|
||||
layers.setRotation(-tracker.rotation || 0);
|
||||
}
|
||||
return layers;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns a cloned slice the option of a deep clone on the top polys
|
||||
*/
|
||||
clone(deep) {
|
||||
const from = this, slice = newSlice(from.z, from.view);
|
||||
from.tops?.forEach(function(top) {
|
||||
slice.addTop(top.poly.clone(deep));
|
||||
});
|
||||
return slice;
|
||||
};
|
||||
|
||||
topPolys() {
|
||||
return this.tops.map(top => top.poly);
|
||||
};
|
||||
|
||||
topSimples() {
|
||||
return this.tops.map(top => top.simple);
|
||||
};
|
||||
|
||||
// FDM top intersect optimization
|
||||
topPolysFlat() {
|
||||
if (this.topFlatPolys) {
|
||||
return this.topFlatPolys;
|
||||
}
|
||||
return this.topFlatPolys = POLY.flatten(this.topPolys().clone(true), [], true);
|
||||
};
|
||||
|
||||
// FDM retract path routing using first shell
|
||||
topRouteFlat() {
|
||||
if (this.topFlatRoutes) {
|
||||
return this.topFlatRoutes;
|
||||
}
|
||||
let topShells0 = this.tops.map(top => top.shells[0]).filter(p => p);
|
||||
return this.topFlatRoutes = POLY.flatten(topShells0.clone(true), [], true);
|
||||
}
|
||||
|
||||
// CAM only
|
||||
topPolyInners() {
|
||||
return this.tops.map(top => top.poly.inner).flat().filter(poly => poly);
|
||||
};
|
||||
|
||||
// FDM / SLA only
|
||||
topInners() {
|
||||
return this.tops.map(top => top.last).flat().filter(poly => poly);
|
||||
};
|
||||
|
||||
// FDM / SLA only
|
||||
topFillOff() {
|
||||
return this.tops.map(top => top.fill_off).flat().filter(poly => poly);
|
||||
};
|
||||
|
||||
// FDM only
|
||||
topFill() {
|
||||
return this.tops.map(top => top.fill_lines).flat().filter(poly => poly);
|
||||
};
|
||||
|
||||
// FDM only
|
||||
topShells() {
|
||||
return this.tops.map(top => top.shells).flat().filter(poly => poly);
|
||||
};
|
||||
|
||||
/**
|
||||
* produces a fingerprint for a slice that should be the same for
|
||||
* layers that are identical. this happens in parts with unchanging
|
||||
* vertical wall regions. this allows us to eliminate expensive diffs
|
||||
* and infill computation when we detect the layers are the same.
|
||||
*/
|
||||
fingerprint() {
|
||||
if (this.finger) {
|
||||
return this.finger;
|
||||
}
|
||||
return this.finger = POLY.fingerprint(this.topPolys());
|
||||
};
|
||||
|
||||
/**
|
||||
* returns true if the layers' fingerprints are the same
|
||||
*/
|
||||
fingerprintSame(slice) {
|
||||
return slice ? POLY.fingerprintCompare(this.fingerprint(), slice.fingerprint()) : false;
|
||||
};
|
||||
|
||||
addTops(polys) {
|
||||
polys.forEach(p => {
|
||||
this.addTop(p);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object | Polygon} data
|
||||
* @returns Slice.Top
|
||||
*/
|
||||
addTop(data) {
|
||||
if (data.length) {
|
||||
// standard legacy polygon
|
||||
let top = new Top(data);
|
||||
this.tops.push(top);
|
||||
top.simple = data;
|
||||
return top;
|
||||
} else {
|
||||
// create top object from object bundle passed back by slicePost()
|
||||
let top = new Top(data.poly);
|
||||
top.thin_fill = data.thin_fill ? data.thin_fill.map(p => base.newPoint(p.x,p.y,p.z)) : undefined;
|
||||
top.fill_lines = data.fill_lines;
|
||||
top.fill_sparse = data.fill_sparse;
|
||||
top.fill_off = data.fill_off;
|
||||
top.last = data.last;
|
||||
top.gaps = data.gaps;
|
||||
top.shells = data.shells;
|
||||
top.simple = data.simple;
|
||||
this.tops.push(top);
|
||||
return top;
|
||||
}
|
||||
};
|
||||
|
||||
findClosestPointTo(target) {
|
||||
let min, find;
|
||||
|
||||
if (this.tops && this.tops.length) {
|
||||
this.tops.forEach(function(top) {
|
||||
find = top.poly.findClosestPointTo(target);
|
||||
if (!min || find.distance < min.distance) {
|
||||
min = find;
|
||||
}
|
||||
});
|
||||
} else if (this.supports) {
|
||||
this.supports.forEach(function(poly) {
|
||||
find = poly.findClosestPointTo(target);
|
||||
if (!min || find.distance < min.distance) {
|
||||
min = find;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return min;
|
||||
};
|
||||
|
||||
setFields(fields = {}) {
|
||||
Object.assign(this, fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
// xray(dash = 3) {
|
||||
// // console.log('xray', this);
|
||||
// this.output().setLayer(`xp`, 0x888800).addPolys(this.topPolys());
|
||||
// this.lines.forEach((line, i) => {
|
||||
// const group = i % dash;
|
||||
// const color = [ 0xff0000, 0x00ff00, 0x0000ff, 0xffff00, 0xff00ff ][group];
|
||||
// this.output().setLayer(`xl-${group}`, color).addLine(line.p1, line.p2);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a top-level (outer) polygon in a slice. Slices may contain
|
||||
* multiple tops each with nested structures. Top objects contain cached
|
||||
* and computed objects for quick access for rendering and dependent computations.
|
||||
*/
|
||||
class Top {
|
||||
|
||||
constructor(polygon) {
|
||||
this.poly = polygon; // outline poly
|
||||
}
|
||||
|
||||
clone(deep) {
|
||||
let top = new Top(this.poly.clone(deep));
|
||||
return top;
|
||||
}
|
||||
|
||||
/**
|
||||
* return innermost traces under a given top. for FDM, this represents
|
||||
* the outline shell that the fill touches. used by Print, Laser
|
||||
*/
|
||||
innerShells() {
|
||||
let shells = this.shells,
|
||||
array = [];
|
||||
if (shells) shells.forEach(function(p) {
|
||||
if (p.inner) array.appendAll(p.inner);
|
||||
});
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns shell polygons of a given depth
|
||||
* used by Print (FDM)
|
||||
*
|
||||
* @param {Polygon[]} out array to populate
|
||||
* @returns {Polygon[]} array of top polygons
|
||||
*/
|
||||
shellsAtDepth(depth) {
|
||||
return this.shells ? this.shells.filter(poly => poly.depth === depth) : [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function newTop(poly) {
|
||||
return new Top(poly);
|
||||
}
|
||||
|
||||
function newSlice(z, view) {
|
||||
return new Slice(z, view);
|
||||
}
|
||||
|
||||
gapp.overlay(kiri, {
|
||||
Top,
|
||||
Slice,
|
||||
newTop,
|
||||
newSlice,
|
||||
setSliceTracker
|
||||
});
|
||||
|
||||
});
|
||||
305
src.old/kiri/stack.js
Normal file
305
src.old/kiri/stack.js
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri.stack", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
|
||||
/*
|
||||
* converts `layers.js` output data structures into three.js meshes for display
|
||||
*/
|
||||
class Stack {
|
||||
constructor(view, freeMem, shiny) {
|
||||
this._view = view;
|
||||
this.view = view.newGroup();
|
||||
this.slices = [];
|
||||
this.meshes = [];
|
||||
this.freeMem = freeMem;
|
||||
newMat = shiny ? createPhongMaterial : createLambertMaterial;
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.slices.length;
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.view.visible = false;
|
||||
}
|
||||
|
||||
show() {
|
||||
this.view.visible = true;
|
||||
}
|
||||
|
||||
rotate(set) {
|
||||
this.view.rotation.x = -set.angle * (Math.PI/180);
|
||||
this.view.position.y = -set.dy;
|
||||
this.view.position.z = -set.dz;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this._view.remove(this.view);
|
||||
THREE.dispose(this.view);
|
||||
this.view = this.slices = this.meshes = null;
|
||||
}
|
||||
|
||||
setFreeMem(bool) {
|
||||
this.freeMem = bool;
|
||||
}
|
||||
|
||||
setVisible(newMin, newMax) {
|
||||
this.show();
|
||||
for (let i=0, s=this.slices, len=s.length; i<len; i++) {
|
||||
s[i].visible = i >= newMin && i <= newMax;
|
||||
if (s[i].visible) this.lastVis = s[i];
|
||||
}
|
||||
}
|
||||
|
||||
setLastFraction(frac = 1) {
|
||||
if (this.lastVis)
|
||||
for (let mesh of this.lastVis.children) {
|
||||
let geo = mesh.geometry;
|
||||
let pos = geo.attributes.position;
|
||||
let len = frac === 1 ? Infinity : Math.round(pos.count * frac);
|
||||
geo.setDrawRange(0, len);
|
||||
}
|
||||
}
|
||||
|
||||
addLayers(layers) {
|
||||
// each slice gets a group so the slice can be toggled efficiently
|
||||
let group = this.view.newGroup();
|
||||
this.slices.push(group);
|
||||
let map = this.renderLayers(layers, group);
|
||||
// by default release memory after layers are rendered
|
||||
if (this.freeMem) {
|
||||
layers.init();
|
||||
}
|
||||
// return map of layers to materials (so they can be toggled on/off)
|
||||
return map;
|
||||
}
|
||||
|
||||
renderLayers(layers, group) {
|
||||
const map = {}
|
||||
for (const [label, layer] of Object.entries(layers.layers)) {
|
||||
map[label] = this.renderLayer(layer, group);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
renderLayer(layer, group) {
|
||||
function addPoly(vertices, poly) {
|
||||
// unroll native polys into points
|
||||
if (poly.id) {
|
||||
poly = {
|
||||
open: poly.open,
|
||||
points: poly.points.map(p => [p.x, p.y, p.z]).flat().toFloat32()
|
||||
};
|
||||
}
|
||||
const points = poly.points, len = points.length;
|
||||
for (let i=3; i<len; i += 3) {
|
||||
vertices.push(new THREE.Vector3(points[i-3], points[i-2], points[i-1]));
|
||||
vertices.push(new THREE.Vector3(points[i+0], points[i+1], points[i+2]));
|
||||
}
|
||||
if (!poly.open) {
|
||||
vertices.push(new THREE.Vector3(points[0], points[1], points[2]));
|
||||
vertices.push(new THREE.Vector3(points[len-3], points[len-2], points[len-1]));
|
||||
}
|
||||
}
|
||||
|
||||
const { polys, lines, faces, cface, paths, cpath } = layer;
|
||||
const { color, off, norms, rotation, position } = layer;
|
||||
const { fat, order, opacity } = color;
|
||||
const meshes = [];
|
||||
const defstate = !off;
|
||||
const mats = [];
|
||||
mats.state = defstate;
|
||||
|
||||
if (polys.length || lines.length) {
|
||||
const vert = []; // vertexes
|
||||
const mat = []; // materials
|
||||
const grp = []; // material groups
|
||||
const geo = fat ? new THREE.LineSegmentsGeometry() : new THREE.BufferGeometry();
|
||||
// map all the poly and line colors for re-use
|
||||
const cmap = {}
|
||||
let cidx = 0;
|
||||
let last = undefined;
|
||||
for (let i=0, il=polys.length; i<il; i++) {
|
||||
const vl = vert.length;
|
||||
const poly = polys[i];
|
||||
addPoly(vert, poly);
|
||||
const pc = poly.color !== undefined ? { line: poly.color, opacity } : color;
|
||||
const pk = pc.line;
|
||||
const cc = cmap[pk] = cmap[pk] || {
|
||||
idx: cidx++,
|
||||
mat: createLineMaterial(pc, mat)
|
||||
};
|
||||
if (last !== pk) {
|
||||
if (grp.length) {
|
||||
// rewrite counts for last group
|
||||
const prev = grp[grp.length - 1]
|
||||
prev[1] = vl;
|
||||
}
|
||||
grp.push([vl, Infinity, cc.idx]);
|
||||
last = pk;
|
||||
}
|
||||
}
|
||||
// for now, line segments inherit the last color
|
||||
for (let i=0, il=lines.length; i<il; i += 3) {
|
||||
vert.push(new THREE.Vector3(lines[i], lines[i+1], lines[i+2]));
|
||||
}
|
||||
// ensure at least one group using the default color settings
|
||||
if (grp.length === 0) {
|
||||
grp.push([0, Infinity, 0]);
|
||||
mat.push(createLineMaterial(color));
|
||||
}
|
||||
mat.forEach(m => m.visible = defstate);
|
||||
for (let i=0; i<grp.length; i++) {
|
||||
const g = grp[i];
|
||||
geo.addGroup(g[0], g[1], g[2]);
|
||||
}
|
||||
if (fat) {
|
||||
// LineSegmentsGeometry does not support setFromPoints()
|
||||
geo.setPositions(vert.map(v => v.toArray()).flat());
|
||||
} else {
|
||||
geo.setFromPoints(vert);
|
||||
}
|
||||
// LineSegments2 does not support multiple materials
|
||||
const segs = fat ?
|
||||
new THREE.LineSegments2(geo, mat[0]) :
|
||||
new THREE.LineSegments(geo, mat);
|
||||
if (rotation) segs.rotation.set(rotation.x, rotation.y, rotation.z);
|
||||
if (position) segs.position.set(position.x, position.y, position.z);
|
||||
if (order !== undefined) segs.renderOrder = order;
|
||||
meshes.push(segs);
|
||||
group.add(segs);
|
||||
mats.appendAll(mat);
|
||||
}
|
||||
if (faces.length) {
|
||||
const mat = [];
|
||||
if (cface) {
|
||||
cface.forEach(c => { mat.push(newMat(c, true)) });
|
||||
} else {
|
||||
mat.push(newMat(color, true));
|
||||
}
|
||||
mat.forEach(m => m.visible = defstate);
|
||||
const geo = new THREE.BufferGeometry();
|
||||
if (faces.toFloat32) {
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(faces.toFloat32(), 3));
|
||||
} else {
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
|
||||
}
|
||||
if (norms) {
|
||||
geo.setAttribute('normal', new THREE.BufferAttribute(norms, 3));
|
||||
} else {
|
||||
geo.computeVertexNormals();
|
||||
}
|
||||
if (cface) {
|
||||
cface.forEach((c, i) => geo.addGroup(c.start, c.count, i));
|
||||
} else {
|
||||
geo.addGroup(0, Infinity, 0);
|
||||
}
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
if (rotation) mesh.rotation.set(rotation.x, rotation.y, rotation.z);
|
||||
if (position) mesh.position.set(position.x, position.y, position.z);
|
||||
meshes.push(mesh);
|
||||
group.add(mesh);
|
||||
mats.appendAll(mat);
|
||||
}
|
||||
if (paths) {
|
||||
const mat = [];
|
||||
if (cpath) {
|
||||
cpath.forEach(c => { mat.push(newMat(c)) });
|
||||
} else {
|
||||
mat.push(newMat(color));
|
||||
}
|
||||
mat.forEach(m => m.visible = defstate);
|
||||
const { index, faces, norms, z } = paths;
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.BufferAttribute(faces, 3));
|
||||
if (index && index.length) {
|
||||
geo.setIndex(index);
|
||||
}
|
||||
if (norms) {
|
||||
geo.setAttribute('normal', new THREE.BufferAttribute(norms, 3));
|
||||
} else {
|
||||
geo.computeVertexNormals();
|
||||
}
|
||||
if (cpath) {
|
||||
cpath.forEach((c, i) => geo.addGroup(c.start, c.count, i));
|
||||
} else {
|
||||
geo.addGroup(0, Infinity, 0);
|
||||
}
|
||||
const mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.position.z = z;
|
||||
if (rotation) mesh.rotation.set(rotation.x, rotation.y, rotation.z);
|
||||
if (position) mesh.position.set(position.x, position.y, position.z);
|
||||
meshes.push(mesh);
|
||||
group.add(mesh);
|
||||
mats.appendAll(mat);
|
||||
}
|
||||
|
||||
this.new_meshes = meshes;
|
||||
this.meshes.appendAll(meshes);
|
||||
mats.forEach(mat => mat.visible = defstate);
|
||||
return mats;
|
||||
}
|
||||
}
|
||||
|
||||
kiri.Stack = Stack;
|
||||
|
||||
let shininess = 15,
|
||||
specular = 0x444444,
|
||||
emissive = 0x101010,
|
||||
metalness = 0,
|
||||
roughness = 0.3,
|
||||
newMat = createPhongMaterial;
|
||||
|
||||
function createLineMaterial(color, array) {
|
||||
const opacity = color.lopacity || color.opacity || 1;
|
||||
const mat = color.fat ? new THREE.LineMaterial({
|
||||
// transparent: true,
|
||||
// opacity: opacity,
|
||||
color: color.line,
|
||||
linewidth: color.fat,
|
||||
alphaToCoverage: false
|
||||
}) : new THREE.LineBasicMaterial({
|
||||
transparent: true,
|
||||
opacity: opacity,
|
||||
color: color.line
|
||||
});
|
||||
if (array) {
|
||||
array.push(mat);
|
||||
}
|
||||
return mat;
|
||||
}
|
||||
|
||||
function createStandardMaterial(color, flat) {
|
||||
return new THREE.MeshMatcapMaterial({
|
||||
transparent: color.opacity != 1,
|
||||
opacity: color.opacity || 1,
|
||||
color: color.face,
|
||||
side: flat ? THREE.DoubleSide : THREE.FrontSide
|
||||
});
|
||||
}
|
||||
|
||||
function createPhongMaterial(color, flat) {
|
||||
return new THREE.MeshPhongMaterial({
|
||||
shininess,
|
||||
specular,
|
||||
transparent: color.opacity != 1,
|
||||
opacity: color.opacity || 1,
|
||||
color: color.face,
|
||||
side: flat ? THREE.DoubleSide : THREE.FrontSide
|
||||
});
|
||||
}
|
||||
|
||||
function createLambertMaterial(color, flat) {
|
||||
return new THREE.MeshLambertMaterial({
|
||||
transparent: color.opacity != 1,
|
||||
opacity: color.opacity || 1,
|
||||
color: color.face,
|
||||
side: flat ? THREE.DoubleSide : THREE.FrontSide
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
173
src.old/kiri/stacks.js
Normal file
173
src.old/kiri/stacks.js
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: kiri.stack
|
||||
// use: kiri.api
|
||||
// use: kiri.ui
|
||||
gapp.register("kiri.stacks", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
|
||||
let freeMem = true,
|
||||
stacks = {},
|
||||
tallest = 0,
|
||||
min = 0,
|
||||
max = 0,
|
||||
labels, API, UC, UI, DYN;
|
||||
|
||||
function init() {
|
||||
labels = $("layers");
|
||||
API = kiri.api,
|
||||
UC = API.uc,
|
||||
UI = API.ui;
|
||||
}
|
||||
|
||||
function setFreeMem(bool) {
|
||||
Object.values(stacks).forEach(stack => stack.obj.setFreeMem(bool));
|
||||
freeMem = bool;
|
||||
return this;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
if (!API) {
|
||||
init();
|
||||
}
|
||||
// remove stacks from their views
|
||||
for (const [stack, data] of Object.entries(stacks)) {
|
||||
data.clear();
|
||||
}
|
||||
min = max = tallest = 0;
|
||||
stacks = {};
|
||||
DYN = UI.dyn = {};
|
||||
|
||||
// clear labels
|
||||
UC.setGroup(labels);
|
||||
labels.innerHTML = '';
|
||||
}
|
||||
|
||||
function rotate(set) {
|
||||
Object.values(stacks).forEach(stack => {
|
||||
stack.obj.rotate(set);
|
||||
});
|
||||
}
|
||||
|
||||
function getStack(name) {
|
||||
return stacks[name];
|
||||
}
|
||||
|
||||
function getStacks() {
|
||||
return Object.entries(stacks);
|
||||
}
|
||||
|
||||
function getLabels() {
|
||||
return Object.keys(DYN);
|
||||
}
|
||||
|
||||
function setVisible(label, bool) {
|
||||
let ctrl = DYN[label];
|
||||
if (ctrl) {
|
||||
ctrl.toggle.checked = bool;
|
||||
ctrl.group.forEach(mat => {
|
||||
mat.visible = bool;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function create(name, view) {
|
||||
if (stacks[name]) {
|
||||
return stacks[name];
|
||||
}
|
||||
const stack = stacks[name] = {
|
||||
layers: [ ],
|
||||
obj: new kiri.Stack(view, freeMem, kiri.api.conf.get().controller.shiny),
|
||||
add: function(layers) {
|
||||
let lmap = layers.layers;
|
||||
let map = stack.obj.addLayers(layers);
|
||||
for (const [label, mats] of Object.entries(map)) {
|
||||
if (!DYN[label]) {
|
||||
let check = DYN[label] = {
|
||||
group: [],
|
||||
toggle: UC.newBoolean(label, (abc) => {
|
||||
ctrl.group.forEach(mat => {
|
||||
mat.visible = ctrl.toggle.checked;
|
||||
});
|
||||
if (ctrl.toggle.checked) {
|
||||
kiri.api.event.emit("stack.show", label);
|
||||
} else {
|
||||
kiri.api.event.emit("stack.hide", label)
|
||||
}
|
||||
})
|
||||
};
|
||||
let color = lmap[label].color.check;
|
||||
if (color !== undefined) {
|
||||
let div = document.createElement('div');
|
||||
div.setAttribute('class', 'color');
|
||||
div.style.backgroundColor = `#${color.toString(16).padStart(6,0)}`;
|
||||
check.toggle.parentNode.appendChild(div);
|
||||
check.toggle.classList.add("color");
|
||||
}
|
||||
}
|
||||
const ctrl = DYN[label];
|
||||
ctrl.group.appendAll(mats);
|
||||
ctrl.toggle.checked = mats.state;
|
||||
}
|
||||
tallest = Math.max(tallest, stack.obj.size());
|
||||
},
|
||||
remove: function() {
|
||||
view.remove(stack.obj.view);
|
||||
THREE.dispose(stack.obj.view);
|
||||
},
|
||||
clear: function() {
|
||||
view.remove(stack.obj.view);
|
||||
THREE.dispose(stack.obj.view);
|
||||
},
|
||||
button: function(label, action) {
|
||||
UC.newRow([ UC.newButton(label, action) ]);
|
||||
}
|
||||
};
|
||||
return stack;
|
||||
}
|
||||
|
||||
function remove(name) {
|
||||
const stack = stacks[name];
|
||||
if (stack) {
|
||||
stack.remove();
|
||||
delete stacks[name];
|
||||
}
|
||||
}
|
||||
|
||||
function getRange() {
|
||||
return {min, max, tallest};
|
||||
}
|
||||
|
||||
function setRange(newMin, newMax) {
|
||||
Object.values(stacks).forEach(stack => {
|
||||
stack.obj.setVisible(newMin,newMax);
|
||||
});
|
||||
min = newMin;
|
||||
max = newMax;
|
||||
}
|
||||
|
||||
function setFraction(frac) {
|
||||
Object.values(stacks).forEach(stack => {
|
||||
stack.obj.setLastFraction(frac);
|
||||
});
|
||||
}
|
||||
|
||||
kiri.stacks = {
|
||||
clear,
|
||||
create,
|
||||
rotate,
|
||||
remove,
|
||||
getStack,
|
||||
getStacks,
|
||||
getRange,
|
||||
setRange,
|
||||
getLabels,
|
||||
setVisible,
|
||||
setFraction,
|
||||
setFreeMem
|
||||
};
|
||||
|
||||
});
|
||||
61
src.old/kiri/stats.js
Normal file
61
src.old/kiri/stats.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// dep: moto.broker
|
||||
// dep: kiri.api
|
||||
// dep: kiri.utils
|
||||
gapp.register("kiri.stats", [], (root, exports) => {
|
||||
|
||||
const { data, kiri, gapp } = root;
|
||||
const { broker } = gapp;
|
||||
const { api, utils } = kiri;
|
||||
const { js2o, o2js } = utils;
|
||||
|
||||
class Stats {
|
||||
constructor(db) {
|
||||
this.db = db || data.local;
|
||||
this.obj = js2o(this.db['stats'] || '{}');
|
||||
let o = this.obj, k;
|
||||
for (k in o) {
|
||||
if (!o.hasOwnProperty(k)) continue;
|
||||
if (['dn','lo','re'].indexOf(k) >= 0 || k.indexOf('-') > 0 || k.indexOf('_') > 0) {
|
||||
delete o[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
save(quiet) {
|
||||
this.db['stats'] = o2js(this.obj);
|
||||
if (!quiet) {
|
||||
broker.publish('stats', this.obj);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
get(k) {
|
||||
return this.obj[k];
|
||||
}
|
||||
|
||||
set(k,v,quiet) {
|
||||
this.obj[k] = v;
|
||||
this.save(quiet);
|
||||
return this;
|
||||
}
|
||||
|
||||
add(k,v,quiet) {
|
||||
this.obj[k] = (this.obj[k] || 0) + (v || 1);
|
||||
this.save(quiet);
|
||||
return this;
|
||||
}
|
||||
|
||||
del(k, quiet) {
|
||||
delete this.obj[k];
|
||||
this.save(quiet);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
kiri.stats = new Stats();
|
||||
|
||||
});
|
||||
167
src.old/kiri/tools.js
Normal file
167
src.old/kiri/tools.js
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
// dep: add.three
|
||||
// dep: kiri.api
|
||||
gapp.register("kiri.tools", (root, exports) => {
|
||||
|
||||
const { Vector3, Quaternion } = THREE;
|
||||
const { kiri, moto } = root;
|
||||
const { api } = kiri;
|
||||
|
||||
const XAXIS = new THREE.Vector3(1,0,0);
|
||||
const ZAXIS = new THREE.Vector3(0,0,1);
|
||||
|
||||
// circle
|
||||
let pgeo = new THREE.CircleGeometry(8, 30);
|
||||
let pmat = new THREE.MeshBasicMaterial({color: 0xddeeff, opacity: 0.6, transparent: true});
|
||||
let pmesh = new THREE.Mesh(pgeo, pmat);
|
||||
|
||||
// circle outline
|
||||
let pi = pgeo.index.array;
|
||||
let pp = pgeo.attributes.position.array;
|
||||
let np = [];
|
||||
for (let i=0; i<pi.length; i++) {
|
||||
if (pi[i] * pi[i+1]) {
|
||||
np.push(pp[pi[i]*3+0]);
|
||||
np.push(pp[pi[i]*3+1]);
|
||||
np.push(pp[pi[i]*3+2]);
|
||||
np.push(pp[pi[i+1]*3+0]);
|
||||
np.push(pp[pi[i+1]*3+1]);
|
||||
np.push(pp[pi[i+1]*3+2]);
|
||||
}
|
||||
}
|
||||
|
||||
let wgeo = new THREE.BufferGeometry();
|
||||
wgeo.setAttribute('position', new THREE.BufferAttribute(np.toFloat32(), 3));
|
||||
|
||||
let wmat = new THREE.LineBasicMaterial({ color: 0x6699cc });
|
||||
let wmesh = new THREE.LineSegments(wgeo, wmat);
|
||||
pmesh.add(wmesh);
|
||||
|
||||
let alert;
|
||||
let lastobj;
|
||||
let lastface;
|
||||
let enabled = false;
|
||||
let opName = 'lay flat';
|
||||
let onDone = onLayFlatSelect;
|
||||
|
||||
function onLayFlatSelect() {
|
||||
let q = new Quaternion().setFromUnitVectors(lastface.normal, new Vector3(0,0,-1));
|
||||
api.selection.rotate(q);
|
||||
// endIt();
|
||||
}
|
||||
|
||||
function onFaceUpSelect() {
|
||||
api.event.emit('tool.mesh.face-normal', lastface.normal);
|
||||
// endIt();
|
||||
}
|
||||
|
||||
function startIt() {
|
||||
if (enabled) {
|
||||
endIt();
|
||||
return;
|
||||
}
|
||||
if (api.feature.hover) {
|
||||
console.log(`${opName} cannot pre-empt hover`);
|
||||
return;
|
||||
}
|
||||
api.feature.hover = true;
|
||||
enabled = true;
|
||||
alert = api.show.alert(`[ESC] to end ${opName}`, 600000);
|
||||
}
|
||||
|
||||
function endIt() {
|
||||
if (enabled) {
|
||||
api.hide.alert(alert);
|
||||
api.feature.hover = false;
|
||||
enabled = false;
|
||||
alert = undefined;
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup() {
|
||||
if (lastobj) {
|
||||
lastobj.remove(pmesh);
|
||||
lastobj = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
api.event.on('key.esc', endIt);
|
||||
|
||||
api.event.on('tool.camera.focus', (fn) => {
|
||||
opName = 'camera focus';
|
||||
onDone = fn;
|
||||
startIt();
|
||||
});
|
||||
|
||||
api.event.on('tool.mesh.face-up', () => {
|
||||
opName = 'face select';
|
||||
onDone = onFaceUpSelect;
|
||||
startIt();
|
||||
});
|
||||
|
||||
api.event.on('tool.mesh.lay-flat', () => {
|
||||
opName = 'lay flat';
|
||||
onDone = onLayFlatSelect;
|
||||
startIt();
|
||||
});
|
||||
|
||||
function scale() {
|
||||
let cam = moto.space.internals().camera;
|
||||
let dist = cam.position.distanceTo(pmesh.position);
|
||||
let scale = opName === 'camera focus' ?
|
||||
Math.min(0.25, 100 / dist) : dist / 100;
|
||||
pmesh.scale.set(scale,scale,scale);
|
||||
}
|
||||
|
||||
api.event.on("space.view.zoom", scale);
|
||||
|
||||
api.event.on('mouse.hover', (ev) => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
cleanup();
|
||||
let { int, ints, event, point, type } = ev;
|
||||
if (type === 'widget') {
|
||||
lastface = int.face;
|
||||
let obj = lastobj = int.object;
|
||||
let norm = int.face.normal;
|
||||
let widget = obj.widget;
|
||||
let track = widget.track;
|
||||
let opos = track.pos;
|
||||
obj.add(pmesh);
|
||||
pmesh.position.x = point.x - opos.x + norm.x * 0.1;
|
||||
if (track.indexed) {
|
||||
let rad = track.indexRad;
|
||||
let py = point.y * 1.001;
|
||||
let pz = point.z * 1.001;
|
||||
let v = new THREE.Vector3(point.x, py, pz).applyAxisAngle(XAXIS, rad);
|
||||
pmesh.position.y = -v.z;
|
||||
pmesh.position.z = v.y;
|
||||
} else {
|
||||
const zcomp = api.mode.get() === 'CAM' ? track.tzoff : 0;
|
||||
pmesh.position.y = -point.z - opos.y + norm.y * 0.1;
|
||||
pmesh.position.z = point.y + zcomp + norm.z * 0.1;
|
||||
}
|
||||
let q = new Quaternion().setFromUnitVectors(ZAXIS,
|
||||
new Vector3(norm.x,norm.y,norm.z)
|
||||
);
|
||||
pmesh.setRotationFromQuaternion(q);
|
||||
scale();
|
||||
}
|
||||
});
|
||||
|
||||
api.event.on('mouse.hover.up', (ev) => {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
let { object } = ev;
|
||||
if (!object) {
|
||||
return;
|
||||
}
|
||||
onDone(ev);
|
||||
endIt();
|
||||
});
|
||||
|
||||
});
|
||||
857
src.old/kiri/ui.js
Normal file
857
src.old/kiri/ui.js
Normal file
|
|
@ -0,0 +1,857 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// use: kiri.init
|
||||
// use: kiri.main
|
||||
gapp.register("kiri.ui", [], (root, exports) => {
|
||||
|
||||
const { kiri } = root;
|
||||
|
||||
let DOC = self.document,
|
||||
inputAction = null,
|
||||
lastAddTo = null,
|
||||
lastGroup = null,
|
||||
lastDiv = null,
|
||||
addTo = null,
|
||||
bindTo = null,
|
||||
groups = {},
|
||||
groupSticky = false,
|
||||
groupName = undefined,
|
||||
heads = {}, // hideable group heads (clickable label)
|
||||
hidden = {}, // hidden groups (by name)
|
||||
hasModes = [],
|
||||
setters = [],
|
||||
lastMode = null,
|
||||
lastExpert = true,
|
||||
prefix = "tab",
|
||||
units = 1,
|
||||
lastChange = null,
|
||||
lastBtn = null,
|
||||
lastTxt = null,
|
||||
lastPop = null;
|
||||
|
||||
self.$ = function (id) { return DOC.getElementById(id) };
|
||||
|
||||
kiri.ui = {
|
||||
prefix: function(pre) { prefix = pre; return kiri.ui },
|
||||
inputAction: function(fn) { inputAction = fn; return kiri.ui },
|
||||
lastChange: function() { return lastChange },
|
||||
checkpoint,
|
||||
restore,
|
||||
refresh,
|
||||
setHidden,
|
||||
setMode,
|
||||
bound,
|
||||
toInt,
|
||||
toFloat,
|
||||
isSticky,
|
||||
setSticky,
|
||||
newBoolean,
|
||||
newButton,
|
||||
newBlank,
|
||||
newDiv,
|
||||
newElement,
|
||||
newExpand,
|
||||
endExpand,
|
||||
newGCode,
|
||||
newGroup,
|
||||
newLabel,
|
||||
newInput,
|
||||
newValue,
|
||||
newRange,
|
||||
newRow,
|
||||
newSelect,
|
||||
newText,
|
||||
setGroup,
|
||||
addUnits,
|
||||
setUnits,
|
||||
confirm,
|
||||
prompt,
|
||||
alert,
|
||||
onBlur,
|
||||
setEnabled(el, bool) {
|
||||
if (bool) {
|
||||
el.removeAttribute('disabled');
|
||||
} else {
|
||||
el.setAttribute('disabled','');
|
||||
}
|
||||
},
|
||||
setVisible(el, bool) {
|
||||
kiri.ui.setClass(el, 'hide', !bool);
|
||||
},
|
||||
setClass(el, clazz, bool) {
|
||||
if (bool) {
|
||||
el.classList.add(clazz);
|
||||
} else {
|
||||
el.classList.remove(clazz);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function setHidden(map) {
|
||||
hidden = map;
|
||||
refresh();
|
||||
for (let ctrl of Object.values(heads)) {
|
||||
ctrl.update();
|
||||
}
|
||||
}
|
||||
|
||||
function onBlur(obj, fn) {
|
||||
if (Array.isArray(obj)) {
|
||||
for (let o of obj) onBlur(o, fn);
|
||||
return;
|
||||
}
|
||||
obj.addEventListener('blur', fn);
|
||||
}
|
||||
|
||||
function alert(message) {
|
||||
return confirm(message, {ok:true});
|
||||
}
|
||||
|
||||
function prompt(message, value) {
|
||||
return confirm(message, {ok:true, cancel:undefined}, value);
|
||||
}
|
||||
|
||||
function confirm(message, buttons, input, opt = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let { api } = kiri;
|
||||
let { feature } = api;
|
||||
let onkey_save = feature.on_key;
|
||||
feature.on_key = key => {
|
||||
// console.log({ eat_key: key });
|
||||
return true;
|
||||
};
|
||||
let dialog = $('dialog');
|
||||
let btns = buttons || {
|
||||
"yes": true,
|
||||
"no": false
|
||||
};
|
||||
let rnd = Date.now().toString(36);
|
||||
let html = [
|
||||
`<div class="confirm f-col a-stretch" style="padding:5px !important">`
|
||||
];
|
||||
if (message) {
|
||||
html.push(`<label style="user-select:text">${message}</label>`);
|
||||
}
|
||||
if (opt.pre) {
|
||||
html = opt.pre.appendAll(html);
|
||||
}
|
||||
let iid;
|
||||
if (Array.isArray(input)) {
|
||||
iid = `confirm-input-${rnd}`;
|
||||
html.append(`<div><textarea rows="15" cols="40" class="grow" type="text" spellcheck="false" id="${iid}"></textarea></div>`);
|
||||
} else if (input !== undefined) {
|
||||
iid = `confirm-input-${rnd}`;
|
||||
html.append(`<div><input class="grow" type="text" spellcheck="false" id="${iid}"/></div>`);
|
||||
}
|
||||
html.append(`<div class="f-row j-end">`);
|
||||
Object.entries(btns).forEach((row,i) => {
|
||||
html.append(`<button id="confirm-${i}-${rnd}">${row[0]}</button>`);
|
||||
});
|
||||
html.append(`</div></div>`);
|
||||
if (opt.post) {
|
||||
html.appendAll(opt.post);
|
||||
}
|
||||
dialog.innerHTML = html.join('');
|
||||
function done(value) {
|
||||
dialog.close();
|
||||
feature.on_key = onkey_save;
|
||||
if (value !== undefined) {
|
||||
setTimeout(() => { resolve(value) }, 150);
|
||||
}
|
||||
}
|
||||
if (iid) {
|
||||
let array = Array.isArray(input);
|
||||
iid = $(iid);
|
||||
iid.value = array ? input.join('\n') : input;
|
||||
if (!array)
|
||||
iid.onkeypress = (ev) => {
|
||||
if (ev.key === 'Enter' || ev.charCode === 13) {
|
||||
done(ev.target.value);
|
||||
}
|
||||
};
|
||||
}
|
||||
Object.entries(btns).forEach((row,i) => {
|
||||
$(`confirm-${i}-${rnd}`).onclick = (ev) => {
|
||||
let value = iid && row[1] ? iid.value : row[1];
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
done(value);
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
dialog.showModal();
|
||||
if (iid) {
|
||||
iid.focus();
|
||||
iid.selectionStart = 0;
|
||||
iid.selectionEnd = iid.value.length;
|
||||
}
|
||||
}, 150);
|
||||
});
|
||||
}
|
||||
|
||||
function refresh() {
|
||||
setMode(lastMode);
|
||||
setters.forEach(input => {
|
||||
if (input.setv) {
|
||||
input.setv(input.real);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setMode(mode) {
|
||||
lastMode = mode;
|
||||
hasModes.forEach(div => div.setMode(mode));
|
||||
}
|
||||
|
||||
function checkpoint(at) {
|
||||
return { addTo: at || addTo, lastDiv: at || lastDiv, lastGroup, groupName };
|
||||
}
|
||||
|
||||
function restore(opt = {}) {
|
||||
addTo = opt.addTo || addTo;
|
||||
bindTo = opt.bindTo || null;
|
||||
lastDiv = opt.lastDiv || lastDiv;
|
||||
lastGroup = opt.lastGroup || lastGroup;
|
||||
groupName = opt.groupName || groupName;
|
||||
}
|
||||
|
||||
// at present only used by the layers popup menu
|
||||
function setGroup(div) {
|
||||
addTo = lastDiv = div;
|
||||
groupName = undefined;
|
||||
lastGroup = [];
|
||||
return div;
|
||||
}
|
||||
|
||||
function newElement(type, opt = {}) {
|
||||
let el = DOC.createElement(type);
|
||||
if (opt.id) {
|
||||
el.setAttribute("id", opt.id);
|
||||
}
|
||||
if (opt.class) {
|
||||
for (let cl of opt.class.split(' ')) {
|
||||
el.classList.add(cl);
|
||||
}
|
||||
}
|
||||
if (opt.attr) {
|
||||
for (let [key, val] of Object.entries(opt.attr)) {
|
||||
el.setAttribute(key, val);
|
||||
}
|
||||
}
|
||||
return el;
|
||||
}
|
||||
|
||||
function newGroup(label, div, opt = {}) {
|
||||
lastDiv = div = (div || lastDiv);
|
||||
|
||||
let group = opt.group || label,
|
||||
row = DOC.createElement('div'),
|
||||
dbkey = `beta-${prefix}-show-${group}`,
|
||||
link;
|
||||
|
||||
if (opt.class) {
|
||||
opt.class.split(' ').forEach(ce => {
|
||||
row.classList.add(ce);
|
||||
});
|
||||
} else {
|
||||
row.setAttribute("class", "set-header");
|
||||
}
|
||||
|
||||
if (div && opt.driven) {
|
||||
addModeControls(div, opt);
|
||||
}
|
||||
|
||||
addTo = lastDiv;
|
||||
|
||||
div.appendChild(row);
|
||||
if (label) {
|
||||
link = DOC.createElement('a');
|
||||
link.appendChild(DOC.createTextNode(label));
|
||||
row.appendChild(link);
|
||||
}
|
||||
|
||||
addModeControls(row, opt);
|
||||
lastGroup = groups[group] = [];
|
||||
lastGroup.key = dbkey;
|
||||
groupName = group;
|
||||
|
||||
if (opt.hideable) {
|
||||
let pad = DOC.createElement('i');
|
||||
let arr = DOC.createElement('span');
|
||||
pad.setAttribute('class','grow');
|
||||
row.appendChild(pad);
|
||||
row.appendChild(arr);
|
||||
const ctrl = heads[group] = {
|
||||
row,
|
||||
arr,
|
||||
update() {
|
||||
if (!hidden[group]) {
|
||||
arr.innerHTML = '<i class="fa-solid fa-caret-down"></i>';
|
||||
row.classList.add('hidden');
|
||||
} else {
|
||||
arr.innerHTML = '<i class="fa-solid fa-caret-up"></i>';
|
||||
row.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
row.onclick = () => {
|
||||
hidden[group] = !hidden[group];
|
||||
refresh();
|
||||
ctrl.update();
|
||||
};
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
function addCollapsableElement(parent, options = {}) {
|
||||
let row = newDiv(options);
|
||||
if (parent) parent.appendChild(row);
|
||||
if (lastGroup) lastGroup.push(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
function bound(low,high) {
|
||||
return function(v) {
|
||||
if (isNaN(v)) return low;
|
||||
return v < low ? low : v > high ? high : v;
|
||||
};
|
||||
}
|
||||
|
||||
function toInt() {
|
||||
let nv = this.value !== '' ? parseInt(this.value) : null;
|
||||
if (isNaN(nv)) nv = 0;
|
||||
if (nv !== null && this.bound) nv = this.bound(nv);
|
||||
this.value = nv;
|
||||
if (this.setv) {
|
||||
return this.real = Math.round(nv * units);
|
||||
};
|
||||
return nv;
|
||||
}
|
||||
|
||||
function toFloat() {
|
||||
let nv = this.value !== '' ? parseFloat(this.value) : null;
|
||||
if (nv !== null && this.bound) nv = this.bound(nv);
|
||||
if (this.setv) {
|
||||
return this.setv(nv * units);
|
||||
} else {
|
||||
this.value = nv;
|
||||
}
|
||||
return nv;
|
||||
}
|
||||
|
||||
function raw() {
|
||||
return this.value !== '' ? this.value : null;
|
||||
}
|
||||
|
||||
function setUnits(v) {
|
||||
if (v !== units) {
|
||||
units = v;
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function newLabel(text, opt = {}) {
|
||||
let label = DOC.createElement('label');
|
||||
label.appendChild(DOC.createTextNode(text));
|
||||
label.setAttribute("class", "noselect");
|
||||
if (opt.class) opt.class.split(' ').forEach(cl => {
|
||||
label.classList.add(cl);
|
||||
})
|
||||
return label;
|
||||
}
|
||||
|
||||
function newValue(size = 6, opt = {}) {
|
||||
let value = DOC.createElement('input');
|
||||
value.setAttribute("size", size);
|
||||
value.setAttribute("readonly", '');
|
||||
value.setAttribute("class", "nooutline noselect");
|
||||
if (opt.class) opt.class.split(' ').forEach(cl => {
|
||||
value.classList.add(cl);
|
||||
})
|
||||
return value;
|
||||
}
|
||||
|
||||
function addId(el, opt = {}) {
|
||||
if (opt.id) {
|
||||
el.setAttribute("id", opt.id);
|
||||
}
|
||||
}
|
||||
|
||||
function safecall(fn) {
|
||||
try {
|
||||
return fn();
|
||||
} catch (e) {
|
||||
// console.log({ safecall_error: e });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function addModeControls(el, opt = {}) {
|
||||
el.__opt = opt;
|
||||
el.showMe = function() {
|
||||
if (opt.trace) console.log({ showMe: el });
|
||||
el.classList.remove('hide');
|
||||
};
|
||||
el.hideMe = function() {
|
||||
if (opt.trace) console.log({ hideMe: el });
|
||||
el.classList.add('hide');
|
||||
};
|
||||
el.setVisible = function(show) {
|
||||
if (opt.trace) console.log({ setVisible: show });
|
||||
if (show) el.showMe();
|
||||
else el.hideMe();
|
||||
};
|
||||
el.setMode = function(mode) {
|
||||
let hidn = hidden[el._group] === true;
|
||||
let xprt = opt.expert === undefined || (opt.expert === lastExpert);
|
||||
let show = opt.show ? safecall(opt.show) : true;
|
||||
let disp = opt.visible ? opt.visible() : true;
|
||||
let hmod = el.hasMode(mode);
|
||||
if (opt.trace) console.log({ setMode: mode, xprt, show, disp, hmod, modes:el.modes });
|
||||
if (opt.manual) return;
|
||||
el.setVisible(!hidn && hmod && show && xprt && disp);
|
||||
}
|
||||
el.hasMode = function(mode) {
|
||||
return (el.modes.length === 0) ||
|
||||
(el.modes.contains && el.modes.contains(mode)) ||
|
||||
(el.modes === mode);
|
||||
}
|
||||
el.modes = opt.modes || [];
|
||||
hasModes.push(el);
|
||||
}
|
||||
|
||||
function newDiv(opt = {}) {
|
||||
let div = DOC.createElement(opt.tag || 'div');
|
||||
addModeControls(div, opt);
|
||||
(opt.addto || addTo).appendChild(div);
|
||||
if (opt.addto) lastDiv = addTo = div;
|
||||
if (opt.class) div.setAttribute('class', opt.class);
|
||||
lastGroup?.push(div);
|
||||
div._group = groupName;
|
||||
return div;
|
||||
}
|
||||
|
||||
function newExpand(label, opt = {}, opteach = {}) {
|
||||
let div = DOC.createElement('details');
|
||||
div.setAttribute('class', opt.class || 'f-col');
|
||||
addModeControls(div, opt);
|
||||
|
||||
let summary = DOC.createElement('summary');
|
||||
summary.setAttribute('class', opt.class || 'var-row');
|
||||
summary.innerHTML = `<label>${label}</label>`;
|
||||
|
||||
div.appendChild( summary );
|
||||
div.collapse = () => {
|
||||
div.removeAttribute('open');
|
||||
};
|
||||
|
||||
lastAddTo = addTo;
|
||||
addTo.appendChild(div);
|
||||
addTo = div;
|
||||
|
||||
return div;
|
||||
}
|
||||
|
||||
function endExpand() {
|
||||
addTo = lastAddTo;
|
||||
return addTo;
|
||||
}
|
||||
|
||||
function isSticky() {
|
||||
return groupSticky;
|
||||
}
|
||||
|
||||
function setSticky(bool) {
|
||||
groupSticky = bool;
|
||||
}
|
||||
|
||||
function newGCode(label, options) {
|
||||
let opt = options || {},
|
||||
btn = DOC.createElement("button"),
|
||||
txt = DOC.createElement("textarea"),
|
||||
area = opt.area;
|
||||
|
||||
txt.setAttribute("wrap", "off");
|
||||
txt.setAttribute("spellcheck", "false");
|
||||
txt.setAttribute("style", "resize: none");
|
||||
txt.onblur = bindTo || inputAction;
|
||||
txt.button = btn;
|
||||
|
||||
btn.setAttribute("class", "basis-50");
|
||||
btn.appendChild(DOC.createTextNode(label));
|
||||
btn.setAttribute("title", opt.title || undefined);
|
||||
btn.onclick = function(ev) {
|
||||
ev.stopPropagation();
|
||||
if (ev.target === txt) {
|
||||
// drop clicks on TextArea
|
||||
ev.target.focus();
|
||||
} else {
|
||||
let fc = area.firstChild;
|
||||
if (fc) area.removeChild(fc);
|
||||
area.appendChild(txt);
|
||||
txt.scrollTop = 0;
|
||||
txt.scrollLeft = 0;
|
||||
txt.selectionEnd = 0;
|
||||
let rows = txt.value.split('\n');
|
||||
let cols = 0;
|
||||
rows.forEach(row => {
|
||||
cols = Math.max(cols, row.length);
|
||||
});
|
||||
let showing = btn === lastBtn;
|
||||
if (lastTxt) {
|
||||
lastTxt.classList.remove('txt-sel');
|
||||
}
|
||||
if (lastBtn) {
|
||||
lastBtn.classList.remove('btn-sel');
|
||||
}
|
||||
if (!showing) {
|
||||
btn.classList.add('btn-sel');
|
||||
lastTxt = btn;
|
||||
lastBtn = btn;
|
||||
txt.focus();
|
||||
} else {
|
||||
inputAction();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
addModeControls(btn, opt);
|
||||
|
||||
return txt;
|
||||
}
|
||||
|
||||
function newText(label, options) {
|
||||
let opt = options || {},
|
||||
inline = opt.row,
|
||||
row = inline ? lastDiv : newDiv(options),
|
||||
btn = DOC.createElement("button"),
|
||||
pop = DOC.createElement("div"),
|
||||
txt = DOC.createElement("textarea"),
|
||||
area = opt.area;
|
||||
|
||||
txt.setAttribute("wrap", "off");
|
||||
txt.setAttribute("spellcheck", "false");
|
||||
txt.setAttribute("style", "resize: none");
|
||||
txt.onblur = inputAction;
|
||||
|
||||
btn.appendChild(DOC.createTextNode(inline ? label : "edit"));
|
||||
|
||||
btn.onclick = function(ev) {
|
||||
ev.stopPropagation();
|
||||
if (ev.target === txt) {
|
||||
// drop clicks on TextArea
|
||||
ev.target.focus();
|
||||
} else {
|
||||
let fc = area.firstChild;
|
||||
if (fc) area.removeChild(fc);
|
||||
area.appendChild(txt);
|
||||
// first time, button click / show
|
||||
btn.parentNode.onclick = btn.onclick;
|
||||
txt.scrollTop = 0;
|
||||
txt.scrollLeft = 0;
|
||||
txt.selectionEnd = 0;
|
||||
let rows = txt.value.split('\n');
|
||||
let cols = 0;
|
||||
rows.forEach(row => {
|
||||
cols = Math.max(cols, row.length);
|
||||
});
|
||||
|
||||
let showing = pop === lastPop;
|
||||
if (lastBtn) {
|
||||
lastBtn.classList.remove('btn-sel');
|
||||
}
|
||||
if (lastTxt) {
|
||||
lastTxt.classList.remove('txt-sel');
|
||||
}
|
||||
if (!showing) {
|
||||
row.classList.add('txt-sel');
|
||||
pop.style.display = "flex";
|
||||
lastPop = pop;
|
||||
lastTxt = row;
|
||||
txt.focus();
|
||||
} else {
|
||||
inputAction();
|
||||
}
|
||||
}
|
||||
};
|
||||
addModeControls(btn, opt);
|
||||
addId(btn, opt);
|
||||
|
||||
if (!inline) {
|
||||
row.appendChild(newLabel(label));
|
||||
row.setAttribute("class", "var-row");
|
||||
}
|
||||
row.appendChild(btn);
|
||||
if (opt.title) row.setAttribute("title", options.title);
|
||||
if (row.setVisible) {
|
||||
btn.setVisible = row.setVisible;
|
||||
}
|
||||
|
||||
return txt;
|
||||
}
|
||||
|
||||
function newInput(label, opt = {}) {
|
||||
let row = newDiv(opt),
|
||||
hide = opt.hide,
|
||||
size = opt.size || 5,
|
||||
height = opt.height || 0,
|
||||
ip = height > 1 ? DOC.createElement('textarea') : DOC.createElement('input'),
|
||||
action = opt.action || bindTo || inputAction;
|
||||
|
||||
row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", opt.class || "var-row");
|
||||
if (height > 1) {
|
||||
ip.setAttribute("cols", size);
|
||||
ip.setAttribute("rows", height);
|
||||
ip.setAttribute("wrap", "off");
|
||||
} else {
|
||||
if (Number.isInteger(size)) {
|
||||
ip.setAttribute("size", size);
|
||||
} else {
|
||||
ip.setAttribute("style", `width:${size}`);
|
||||
}
|
||||
}
|
||||
ip.setAttribute("type", "text");
|
||||
ip.setAttribute("spellcheck", "false");
|
||||
row.style.display = hide ? 'none' : '';
|
||||
if (opt.disabled) ip.setAttribute("disabled", "true");
|
||||
if (opt.title) row.setAttribute("title", opt.title);
|
||||
if (opt.convert) ip.convert = opt.convert.bind(ip);
|
||||
if (opt.bound) ip.bound = opt.bound;
|
||||
if (opt.action) action = opt.action;
|
||||
ip.addEventListener('focus', function(event) {
|
||||
setSticky(true);
|
||||
});
|
||||
if (action) {
|
||||
ip.addEventListener('keydown', function(event) {
|
||||
let key = event.key;
|
||||
if (
|
||||
opt.text ||
|
||||
(key >= '0' && key <= '9') ||
|
||||
key === '.' ||
|
||||
key === '-' ||
|
||||
key === 'Backspace' ||
|
||||
key === 'Delete' ||
|
||||
key === 'ArrowLeft' ||
|
||||
key === 'ArrowRight' ||
|
||||
key === 'Tab' ||
|
||||
event.metaKey ||
|
||||
event.ctrlKey ||
|
||||
(key === ',' && options.comma)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
});
|
||||
ip.addEventListener('keyup', function(event) {
|
||||
if (event.keyCode === 13) {
|
||||
ip.blur();
|
||||
}
|
||||
});
|
||||
ip.addEventListener('blur', function(event) {
|
||||
setSticky(false);
|
||||
lastChange = ip;
|
||||
action(event);
|
||||
if (opt.trigger) {
|
||||
refresh(opt.trigger === 1 || opt.trigger === true);
|
||||
}
|
||||
});
|
||||
if (opt.units) {
|
||||
addUnits(ip, opt.round || 3);
|
||||
}
|
||||
}
|
||||
if (!ip.convert) ip.convert = raw.bind(ip);
|
||||
ip.setVisible = row.setVisible;
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
function addUnits(input, round) {
|
||||
setters.push(input);
|
||||
input.setv = function(value) {
|
||||
if (typeof(value) === 'number') {
|
||||
input.real = value;
|
||||
input.value = (value / units).round(round);
|
||||
} else {
|
||||
input.value = value;
|
||||
}
|
||||
return input.real;
|
||||
};
|
||||
return input;
|
||||
}
|
||||
|
||||
function newRange(label, options) {
|
||||
let row = newDiv(options),
|
||||
ip = DOC.createElement('input'),
|
||||
hide = options && options.hide,
|
||||
action = bindTo || inputAction;
|
||||
|
||||
if (label) row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", "var-row");
|
||||
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 newSelect(label, options = {}, source) {
|
||||
let row = newDiv(options),
|
||||
ip = DOC.createElement('select'),
|
||||
hide = options && options.hide,
|
||||
action = bindTo || inputAction;
|
||||
|
||||
row.appendChild(newLabel(label));
|
||||
row.appendChild(ip);
|
||||
if (Array.isArray(source)) {
|
||||
ip._source = source;
|
||||
} else {
|
||||
row.setAttribute("source", source || "tools");
|
||||
}
|
||||
row.setAttribute("class", "var-row");
|
||||
row.style.display = hide ? 'none' : '';
|
||||
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.setVisible = row.setVisible;
|
||||
ip.onchange = function(ev) {
|
||||
lastChange = ip;
|
||||
action();
|
||||
if (options.trigger) {
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
ip.onclick = (ev) => {
|
||||
groupSticky = true;
|
||||
};
|
||||
// because firefox
|
||||
ip.onmouseenter = (ev) => {
|
||||
groupSticky = true;
|
||||
};
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newBoolean(label, action = bindTo, opt = {}) {
|
||||
let row = newDiv(opt),
|
||||
ip = DOC.createElement('input'),
|
||||
hide = opt.hide;
|
||||
|
||||
if (label) {
|
||||
row.appendChild(newLabel(label));
|
||||
}
|
||||
row.appendChild(ip);
|
||||
row.setAttribute("class", "var-row");
|
||||
row.style.display = hide ? 'none' : '';
|
||||
ip.setAttribute("type", "checkbox");
|
||||
ip.checked = false;
|
||||
if (opt.disabled) {
|
||||
ip.setAttribute("disabled", "true");
|
||||
}
|
||||
if (opt.title) {
|
||||
ip.setAttribute("title", opt.title);
|
||||
row.setAttribute("title", opt.title);
|
||||
}
|
||||
if (action) {
|
||||
ip.onclick = function(ev) {
|
||||
action(ip);
|
||||
if (opt.trigger) {
|
||||
refresh();
|
||||
}
|
||||
};
|
||||
}
|
||||
ip.setVisible = row.setVisible;
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
function newBlank(options) {
|
||||
let opt = options || {},
|
||||
row = newDiv(opt),
|
||||
hide = opt.hide;
|
||||
|
||||
row.isBlank = true;
|
||||
row.style.display = hide ? 'none' : '';
|
||||
|
||||
if (!opt.driven) {
|
||||
row.setAttribute("class", "var-row");
|
||||
}
|
||||
|
||||
if (opt.class) {
|
||||
opt.class.split(' ').forEach(ce => {
|
||||
row.classList.add(ce);
|
||||
});
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
// unlike other elements, does not auto-add to a row
|
||||
function newButton(label, action, opt = {}) {
|
||||
let b = DOC.createElement('button');
|
||||
let { api } = kiri;
|
||||
|
||||
b.onclick = function() {
|
||||
switch (typeof action) {
|
||||
case "string":
|
||||
api.event.emit(action);
|
||||
break;
|
||||
case "function":
|
||||
action(...arguments);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
if (opt.class) {
|
||||
opt.class.split(' ').forEach(ce => {
|
||||
b.classList.add(ce);
|
||||
});
|
||||
}
|
||||
if (opt.icon) {
|
||||
let d = DOC.createElement('div');
|
||||
d.innerHTML = opt.icon;
|
||||
b.appendChild(d);
|
||||
}
|
||||
if (opt.title) {
|
||||
b.setAttribute('title', opt.title);
|
||||
}
|
||||
if (label) {
|
||||
b.appendChild(DOC.createTextNode(label));
|
||||
}
|
||||
|
||||
addModeControls(b, opt);
|
||||
addId(b, opt);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
function newRow(children, options) {
|
||||
let row = addCollapsableElement((options && options.noadd) ? null : addTo);
|
||||
if (children) children.forEach(function (c) { row.appendChild(c) });
|
||||
addModeControls(row, options);
|
||||
if (options && options.class) {
|
||||
options.class.split(' ').forEach(ce => {
|
||||
row.classList.add(ce);
|
||||
});
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
});
|
||||
116
src.old/kiri/utils.js
Normal file
116
src.old/kiri/utils.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
gapp.register("kiri.utils", [], (root, exports) => {
|
||||
|
||||
function parseOpt(ov) {
|
||||
let opt = {}, kv, kva;
|
||||
// handle kiri legacy and proper url encoding better
|
||||
ov.replace(/&/g,',').split(',').forEach(function(el) {
|
||||
kv = decodeURIComponent(el).split(':');
|
||||
if (kv.length === 2) {
|
||||
kva = opt[kv[0]] = opt[kv[0]] || [];
|
||||
kva.push(decodeURIComponent(kv[1]));
|
||||
}
|
||||
});
|
||||
return opt;
|
||||
}
|
||||
|
||||
function encodeOpt(opt) {
|
||||
let out = [];
|
||||
Object.keys(opt).forEach(key => {
|
||||
if (key === 'ver') return;
|
||||
let val = opt[key];
|
||||
out.push(encodeURIComponent(key) + ":" + encodeURIComponent(val));
|
||||
});
|
||||
return out.length ? '?' + out.join(',') : '';
|
||||
}
|
||||
|
||||
function ajax(url, fn, rt, po, hd) {
|
||||
return moto.ajax.new(fn, rt).request(url, po, hd);
|
||||
}
|
||||
|
||||
function o2js(o,def) {
|
||||
return o ? JSON.stringify(o) : def || null;
|
||||
}
|
||||
|
||||
function js2o(s,def) {
|
||||
try {
|
||||
return s ? JSON.parse(s) : def || null;
|
||||
} catch (e) {
|
||||
console.log({malformed_json:s});
|
||||
return def || null;
|
||||
}
|
||||
}
|
||||
|
||||
function ls2o(key,def) {
|
||||
// defer ref b/c it may run in a worker
|
||||
return js2o(root.data.local.getItem(key),def);
|
||||
}
|
||||
|
||||
// split 24 bit color into [ r, g, b ]
|
||||
function rgb(c) {
|
||||
return [ (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff ];
|
||||
}
|
||||
|
||||
// return 24 bit color from [ r, g, b ] array
|
||||
function a2c(a) {
|
||||
return (a[0] << 16) | (a[1] << 8) | a[0];
|
||||
}
|
||||
|
||||
// average two color arrays with weighting @ return 24 bit color
|
||||
function avgc(c1, c2, w = 3) {
|
||||
let r1 = rgb(c1);
|
||||
let r2 = rgb(c2);
|
||||
let d = (w + 2);
|
||||
return ((r1[0] * w + r2[0]) / d) << 16
|
||||
| ((r1[1] * w + r2[1]) / d) << 8
|
||||
| ((r1[2] * w + r2[2]) / d);
|
||||
}
|
||||
|
||||
function areEqual(o1, o2) {
|
||||
if (o1 == o2) return true;
|
||||
if (Array.isArray(o1) && Array.isArray(o2)) {
|
||||
if (o1.length === o2.length) {
|
||||
for (let i=0; i<o1.length; i++) {
|
||||
if (o1[i] !== o2[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (typeof(o1) === 'object' && typeof(o2) === 'object') {
|
||||
let keys = Object.keys(Object.assign({}, o1, o2));
|
||||
for (let key of keys) {
|
||||
if (o1[key] !== o2[key]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function trackFn(fn, name) {
|
||||
return function() {
|
||||
console.log(name, ...arguments);
|
||||
fn(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
exports({
|
||||
trackFn,
|
||||
areEqual,
|
||||
parseOpt,
|
||||
encodeOpt,
|
||||
ajax,
|
||||
o2js,
|
||||
js2o,
|
||||
ls2o,
|
||||
avgc,
|
||||
rgb,
|
||||
a2c
|
||||
});
|
||||
|
||||
});
|
||||
1072
src.old/kiri/widget.js
Normal file
1072
src.old/kiri/widget.js
Normal file
File diff suppressed because it is too large
Load diff
125
src.old/kiri/widgets.js
Normal file
125
src.old/kiri/widgets.js
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
|
||||
|
||||
"use strict";
|
||||
|
||||
// use: load.file
|
||||
// use: kiri.selection
|
||||
// use: kiri.platform
|
||||
gapp.register("kiri.widgets", (root, exports) => {
|
||||
|
||||
const { data, kiri, moto, noop } = root;
|
||||
const { api, consts, utils, newWidget, Widget } = kiri;
|
||||
|
||||
let WIDGETS = [];
|
||||
|
||||
function map() {
|
||||
let map = {};
|
||||
for (let widget of WIDGETS) {
|
||||
map[widget.id] = widget;
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function annotate(id) {
|
||||
let w = WIDGETS.filter(w => w.id === id)[0];
|
||||
if (!w) {
|
||||
console.trace(`annotate missing widget ${id}`);
|
||||
return {};
|
||||
}
|
||||
return (w.anno = w.anno || {});
|
||||
}
|
||||
|
||||
/** TODO really should be on widget, not widgets */
|
||||
function rename(sel) {
|
||||
let widgets = sel ? [ sel ] : api.selection.widgets(true);
|
||||
if (widgets.length !== 1) {
|
||||
return;
|
||||
}
|
||||
let widget = widgets[0];
|
||||
kiri.ui.prompt("new widget name", widget.meta.file || "no name").then(newname => {
|
||||
if (newname) {
|
||||
widget.meta.file = newname;
|
||||
api.platform.changed();
|
||||
api.space.save(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function replace(vertices, sel) {
|
||||
let widgets = sel ? [sel] : api.selection.widgets(true);
|
||||
if (!widgets.length) {
|
||||
return;
|
||||
}
|
||||
function onload(vertices) {
|
||||
for (let w of widgets) {
|
||||
let track = Object.clone(w.track);
|
||||
let { scale, rot, pos } = track;
|
||||
let roto = w.roto.slice();
|
||||
w.loadVertices(vertices);
|
||||
for (let m of roto) {
|
||||
w.mesh.geometry.applyMatrix4(m.clone());
|
||||
}
|
||||
w._scale(scale.x, scale.y, scale.z);
|
||||
}
|
||||
api.platform.update();
|
||||
}
|
||||
if (vertices) {
|
||||
onload(vertices);
|
||||
} else {
|
||||
// dialog
|
||||
$('load-file').onchange = function(event) {
|
||||
load.File.load(event.target.files[0])
|
||||
.then(data => onload(data[0].mesh))
|
||||
.catch(error => console.log({error}));
|
||||
};
|
||||
$('load-file').click();
|
||||
}
|
||||
}
|
||||
|
||||
function meshes() {
|
||||
let out = [];
|
||||
widgets.each(widget => {
|
||||
if (!api.feature.hoverAdds) {
|
||||
out.push(widget.mesh);
|
||||
}
|
||||
out.appendAll(widget.adds.filter(m=>m.visible));
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
function opacity(value) {
|
||||
api.widgets.each(w => w.setOpacity(value));
|
||||
moto.space.update();
|
||||
}
|
||||
|
||||
function setIndexed(value) {
|
||||
api.widgets.each(w => w.setIndexed(value));
|
||||
}
|
||||
|
||||
function setAxisIndex(value) {
|
||||
api.widgets.each(w => w.setAxisIndex(value));
|
||||
}
|
||||
|
||||
// extend API (api.widgets)
|
||||
const widgets = Object.assign(api.widgets, {
|
||||
load: Widget.loadFromCatalog,
|
||||
new: newWidget,
|
||||
map,
|
||||
meshes,
|
||||
rename,
|
||||
replace,
|
||||
opacity,
|
||||
annotate,
|
||||
setIndexed,
|
||||
setAxisIndex,
|
||||
all() { return WIDGETS.slice() },
|
||||
add(w) { return WIDGETS.push(w) },
|
||||
remove(w) { return WIDGETS.remove(w) },
|
||||
filter(fn) { return WIDGETS = WIDGETS.filter(fn) },
|
||||
count() { return WIDGETS.length },
|
||||
each(fn) { WIDGETS.slice().forEach(widget => fn(widget)) },
|
||||
for(fn) { widgets.each(fn) },
|
||||
forid(id) { return WIDGETS.filter(w => w.id === id)[0] }
|
||||
});
|
||||
|
||||
});
|
||||
Loading…
Reference in a new issue