grid-apps-cmms/src/kiri/main.js

2286 lines
76 KiB
JavaScript
Raw Normal View History

/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
2017-04-18 17:09:33 -04:00
2020-06-03 22:39:30 -04:00
"use strict";
2017-04-18 17:09:33 -04:00
(function () {
2018-01-10 20:04:51 -05:00
2022-02-18 13:27:26 -05:00
const { base, data, load, kiri, moto, noop } = self;
2022-02-18 10:52:49 -05:00
const { api, consts, lang, Widget, newWidget, utils } = kiri;
const { areEqual, parseOpt, encodeOpt, ajax, o2js, js2o } = utils;
2022-02-18 13:27:26 -05:00
const { feature, platform, selection } = api;
2022-02-18 10:52:49 -05:00
const { COLOR, MODES, PMODES, VIEWS } = consts;
2022-02-17 14:40:51 -05:00
const LANG = lang.current,
WIN = self.window,
DOC = self.document,
LOC = self.location,
HOST = LOC.host.split(':'),
SETUP = parseOpt(LOC.search.substring(1)),
SECURE = isSecure(LOC.protocol),
LOCAL = self.debug && !SETUP.remote,
2022-02-17 14:40:51 -05:00
EVENT = kiri.broker = gapp.broker,
2022-02-18 10:52:49 -05:00
SDB = data.local,
2022-02-17 14:40:51 -05:00
SPACE = kiri.space = moto.Space,
2022-02-18 11:30:00 -05:00
FILES = kiri.catalog = kiri.openFiles(new data.Index(SETUP.d ? SETUP.d[0] : 'kiri')),
2017-04-18 17:09:33 -04:00
STATS = new Stats(SDB),
// ---------------
2022-02-17 14:40:51 -05:00
CONF = kiri.conf,
clone = Object.clone;
let settings = clone(CONF.template),
WIDGETS = kiri.widgets = [],
2017-04-18 17:09:33 -04:00
// ---------------
UI = {},
2022-02-17 14:40:51 -05:00
UC = kiri.ui.prefix('kiri').inputAction(updateSettings),
2017-04-18 17:09:33 -04:00
MODE = MODES.FDM,
2022-02-17 14:40:51 -05:00
STACKS = kiri.stacks,
DRIVER = undefined,
complete = {},
2022-02-18 13:27:26 -05:00
// selectedMeshes = [],
2017-04-18 17:09:33 -04:00
localFilterKey ='kiri-gcode-filters',
localFilters = js2o(SDB.getItem(localFilterKey)) || [],
// ---------------
viewMode = VIEWS.ARRANGE,
local = SETUP.local,
busy = 0,
showFavorites = SDB.getItem('dev-favorites') === 'true',
alerts = [],
saveTimer = null,
version = kiri.version = gapp.version;
2017-04-18 17:09:33 -04:00
2020-03-10 14:23:42 -04:00
// add show() to catalog for API
2022-02-18 11:30:00 -05:00
FILES.show = showCatalog;
2020-03-10 14:23:42 -04:00
2021-12-26 13:09:07 -05:00
// patch broker for api backward compatibility
EVENT.on = (topic, listener) => {
EVENT.subscribe(topic, listener);
return EVENT;
};
2022-02-17 14:40:51 -05:00
// augment api
Object.assign(api, {
2020-03-09 11:02:09 -04:00
ui: UI,
uc: UC,
2022-02-17 14:40:51 -05:00
focus: noop,
2020-03-09 11:02:09 -04:00
stats: STATS,
2022-02-18 11:30:00 -05:00
catalog: FILES,
busy: {
2022-02-17 14:40:51 -05:00
val() { return busy },
inc() { kiri.api.event.emit("busy", ++busy) },
dec() { kiri.api.event.emit("busy", --busy) }
},
2020-03-09 11:02:09 -04:00
conf: {
2021-10-25 22:09:22 -04:00
dbo: () => { return ls2o('ws-settings') },
2020-03-09 11:02:09 -04:00
get: getSettings,
put: putSettings,
2020-03-29 20:23:48 -04:00
load: loadSettings,
2020-03-09 11:02:09 -04:00
save: saveSettings,
show: showSettings,
2020-03-29 20:23:48 -04:00
update: updateSettings,
2020-04-07 21:45:21 -04:00
restore: restoreSettings,
export: settingsExport,
import: settingsImport
2020-03-09 11:02:09 -04:00
},
const: {
LANG,
LOCAL,
2020-06-10 18:56:55 -04:00
SETUP,
2020-11-09 01:42:17 -05:00
SECURE,
STACKS,
2020-03-09 11:02:09 -04:00
},
device: {
code: currentDeviceCode,
2020-03-17 16:32:43 -04:00
get: currentDeviceName,
set: noop, // set during init
clone: noop // set during init
2020-03-09 11:02:09 -04:00
},
dialog: {
2020-06-06 11:17:04 -04:00
show: showModal,
hide: hideModal
2020-03-09 11:02:09 -04:00
},
help: {
show: showHelp,
file: showHelpFile
},
event: {
2022-02-17 14:40:51 -05:00
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) },
2020-03-09 11:02:09 -04:00
import: loadFile,
alerts: updateAlerts,
settings: triggerSettingsEvent
},
function: {
slice: prepareSlices,
2020-11-08 18:39:21 -05:00
print: preparePreview,
prepare: preparePreview,
2021-03-04 15:17:09 -05:00
animate: prepareAnimation,
export: prepareExport,
cancel: cancelWorker,
2022-02-17 14:40:51 -05:00
clear: kiri.client.clear,
parse: loadCode
2020-03-09 11:02:09 -04:00
},
2021-03-01 00:22:07 -05:00
group: {
merge: groupMerge,
split: groupSplit,
},
2020-03-09 11:12:11 -04:00
hide: {
2022-02-17 14:40:51 -05:00
alert(rec,recs) { alert2cancel(rec,recs) },
import: noop,
2020-11-24 00:48:45 -05:00
slider: hideSlider
2020-03-09 11:12:11 -04:00
},
2022-02-17 21:52:54 -05:00
image: {
dialog: loadImageDialog,
convert: loadImageConvert
},
2022-02-17 14:40:51 -05:00
language: kiri.lang,
2020-03-09 11:02:09 -04:00
modal: {
show: showModal,
hide: hideModal,
visible: modalShowing
},
mode: {
2022-02-17 14:40:51 -05:00
get_id() { return MODE },
2020-03-09 11:02:09 -04:00
get_lower: getModeLower,
get: getMode,
set: setMode,
switch: switchMode,
set_expert: noop
2020-03-09 11:02:09 -04:00
},
probe: {
2022-02-17 14:40:51 -05:00
live: "https://live.grid.space",
grid: noop,
local: noop
2020-03-09 11:02:09 -04:00
},
process: {
code: currentProcessCode,
get: currentProcessName
},
2022-02-17 21:52:54 -05:00
settings: {
import: settingsImport,
import_zip: settingsImportZip,
import_prusa: settingsPrusaConvert
},
2020-03-09 11:02:09 -04:00
show: {
alert: alert2,
devices: noop, // set during init
2020-03-09 11:02:09 -04:00
progress: setProgress,
controls: setControlsVisible,
favorites: getShowFavorites,
slices: showSlices,
layer: setVisibleLayer,
2020-03-09 11:12:11 -04:00
local: showLocal,
import: function() { UI.import.style.display = '' }
2020-03-09 11:02:09 -04:00
},
space: {
2022-02-17 21:52:54 -05:00
reload,
auto_save,
2020-03-09 11:02:09 -04:00
restore: restoreWorkspace,
clear: clearWorkspace,
save: saveWorkspace,
set_focus: setFocus,
update: SPACE.update,
is_dark() { return settings.controller.dark }
2020-03-09 11:02:09 -04:00
},
2020-06-10 18:56:55 -04:00
util: {
2020-12-23 14:27:18 -05:00
isSecure,
ui2rec: updateSettingsFromFields,
rec2ui: updateFieldsFromSettings,
download: downloadBlob,
2022-02-17 14:40:51 -05:00
b64enc(obj) { return base64js.fromByteArray(new TextEncoder().encode(JSON.stringify(obj))) },
b64dec(obj) { return JSON.parse(new TextDecoder().decode(base64js.toByteArray(obj))) }
2020-06-10 18:56:55 -04:00
},
2020-03-09 11:02:09 -04:00
view: {
get() { return viewMode },
2022-02-17 14:40:51 -05:00
set() { setViewMode(...arguments) },
set_arrange() { api.view.set(VIEWS.ARRANGE) },
set_slice() { api.view.set(VIEWS.SLICE) },
set_preview() { api.view.set(VIEWS.PREVIEW) },
is_arrange() { return viewMode === VIEWS.ARRANGE },
is_slice() { return viewMode === VIEWS.SLICE },
is_preview() { return viewMode === VIEWS.PREVIEW },
2022-02-17 21:52:54 -05:00
hide_slices: hideSlices,
update_speeds: updateSpeeds,
update_fields: updateFields,
2022-02-17 21:52:54 -05:00
update_slider: updateSlider,
update_slider_max: updateSliderMax,
2020-11-14 11:12:12 -05:00
snapshot: null,
unit_scale: unitScale,
2022-02-17 21:52:54 -05:00
wireframe: setWireframe,
2020-03-09 11:02:09 -04:00
},
widgets: {
new: newWidget,
2022-02-17 21:52:54 -05:00
all() { return WIDGETS.slice() },
add(widget) { WIDGETS.push(widget) },
remove(widget) { return WIDGETS.remove(widget) },
filter(fn) { WIDGETS = WIDGETS.filter(fn) },
count() { return WIDGETS.length },
heal: healWidgets,
2020-03-09 11:02:09 -04:00
for: forAllWidgets,
2022-02-17 21:52:54 -05:00
each: forAllWidgets,
2020-03-09 11:02:09 -04:00
load: Widget.loadFromCatalog,
replace: replaceVertices,
meshes: meshArray,
2020-11-22 17:01:36 -05:00
opacity: setOpacity,
2022-02-17 21:52:54 -05:00
annotate(id) {
let w = WIDGETS.filter(w => w.id === id)[0];
if (!w) {
2022-02-17 21:52:54 -05:00
console.trace(`annotate missing widget ${id}`);
return {};
}
return (w.anno = w.anno || {});
2022-02-17 21:52:54 -05:00
},
map() {
let map = {};
for (let widget of WIDGETS) {
map[widget.id] = widget;
}
return map;
2020-11-22 17:01:36 -05:00
}
2020-03-09 11:02:09 -04:00
},
2022-02-17 14:40:51 -05:00
work: kiri.work
});
2017-04-18 17:09:33 -04:00
2022-02-18 13:27:26 -05:00
function setOpacity(value) {
api.widgets.each(w => w.setOpacity(value));
SPACE.update();
}
2021-04-21 22:30:42 -04:00
function setFocus(widgets, 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 (widgets === undefined) {
widgets = WIDGETS;
} else if (!Array.isArray) {
widgets = [ widgets ];
2021-01-22 10:44:01 -05:00
} else if (widgets.length === 0) {
widgets = WIDGETS;
}
let pos = { x:0, y:0, z:0 };
for (let widget of widgets) {
pos.x += widget.track.pos.x;
pos.y += widget.track.pos.y;
pos.z += widget.track.pos.z;
}
2021-01-23 10:20:31 -05:00
if (widgets.length) {
pos.x /= widgets.length;
pos.y /= widgets.length;
pos.z /= widgets.length;
}
2022-02-17 21:52:54 -05:00
SPACE.platform.setCenter(pos.x, -pos.y, platform.top_z() / 2);
SPACE.view.setFocus(new THREE.Vector3(pos.x, platform.top_z() / 2, -pos.y));
}
function reload() {
2022-02-17 14:40:51 -05:00
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.controller.autoSave) {
return;
}
clearTimeout(saveTimer);
saveTimer = setTimeout(() => {
2022-02-17 14:40:51 -05:00
api.space.save(true);
}, 1000);
}
2021-01-04 17:19:13 -05:00
// frame message api
WIN.addEventListener('message', msg => {
2022-02-02 20:19:28 -05:00
if (!feature.frame) return;
2021-01-04 17:19:13 -05:00
let { origin, source, target, data } = msg;
if (source.window === target.window) return;
let send = source.window.postMessage;
2022-02-17 14:40:51 -05:00
if (data.mode) { api.mode.set(data.mode.toUpperCase()) }
if (data.view) { api.view.set(VIEWS[data.view.toUpperCase()]) }
if (data.function) {
let cb = data.callback ? (output) => {
send({event:`${data.function}.done`, data: output});
} : undefined;
2022-02-17 14:40:51 -05:00
api.function[data.function.toLowerCase()](cb);
}
2021-01-04 17:19:13 -05:00
if (data.event) {
2022-02-17 14:40:51 -05:00
api.event.on(data.event, (evd) => {
2021-01-04 17:19:13 -05:00
send({event: data.event, data: evd});
});
}
2022-02-17 14:40:51 -05:00
if (data.emit) api.event.emit(data.emit, data.message)
if (data.get) switch (data.get) {
2021-01-04 17:19:13 -05:00
case "mode": send({mode: settings.mode}); break;
case "device": send({device: settings.device}); break;
case "process": send({process: settings.process}); break;
default: send({all: settings}); break;
2021-01-04 17:19:13 -05:00
}
if (data.set) switch (data.set) {
2021-01-04 17:19:13 -05:00
case "features":
Object.assign(feature, data.features);
break;
case "device":
Object.assign(settings.device, data.options);
saveSettings();
break;
case "process":
Object.assign(settings.process, data.options);
saveSettings();
break;
}
if (data.parse) {
let type = (data.type || 'stl').toLowerCase();
let bin = data.parse;
let widget;
switch (type) {
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 = [];
2022-02-17 14:40:51 -05:00
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) {
api.platform.load(data.load, (verts, widget) => {
send({event: "loaded", data: [ widget.id ]});
})
};
if (data.clear) platform.clear();
2021-01-04 17:19:13 -05:00
if (data.alert) alert2(data.alert, data.time);
if (data.progress >= 0) setProgress(data.progress, data.message);
});
2017-04-18 17:09:33 -04:00
/** ******************************************************************
* Stats accumulator
******************************************************************* */
function Stats(db) {
this.db = db;
2020-03-07 13:14:22 -05:00
this.obj = js2o(this.db['stats'] || '{}');
2020-04-09 14:52:43 -04:00
let o = this.obj, k;
2017-04-18 17:09:33 -04:00
for (k in o) {
if (!o.hasOwnProperty(k)) continue;
2020-07-08 12:54:49 -04:00
if (['dn','lo','re'].indexOf(k) >= 0 || k.indexOf('-') > 0 || k.indexOf('_') > 0) {
2020-03-07 13:14:22 -05:00
delete o[k];
}
2017-04-18 17:09:33 -04:00
}
}
2020-03-07 12:33:26 -05:00
Stats.prototype.save = function(quiet) {
2017-04-18 17:09:33 -04:00
this.db['stats'] = o2js(this.obj);
2020-03-07 12:33:26 -05:00
if (!quiet) {
2022-02-17 14:40:51 -05:00
api.event.emit('stats', this.obj);
2020-03-07 12:33:26 -05:00
}
2017-04-18 17:09:33 -04:00
return this;
};
2020-03-07 12:33:26 -05:00
2017-04-18 17:09:33 -04:00
Stats.prototype.get = function(k) {
return this.obj[k];
2020-03-07 12:33:26 -05:00
};
Stats.prototype.set = function(k,v,quiet) {
2017-04-18 17:09:33 -04:00
this.obj[k] = v;
2020-03-07 12:33:26 -05:00
this.save(quiet);
2017-04-18 17:09:33 -04:00
return this;
};
2020-03-07 12:33:26 -05:00
Stats.prototype.add = function(k,v,quiet) {
2017-04-18 17:09:33 -04:00
this.obj[k] = (this.obj[k] || 0) + (v || 1);
2020-03-07 12:33:26 -05:00
this.save(quiet);
2017-04-18 17:09:33 -04:00
return this;
};
2020-03-07 12:33:26 -05:00
Stats.prototype.del = function(k, quiet) {
2017-04-18 17:09:33 -04:00
delete this.obj[k];
2020-03-07 12:33:26 -05:00
this.save(quiet);
2017-04-18 17:09:33 -04:00
return this;
2020-03-07 12:33:26 -05:00
};
2017-04-18 17:09:33 -04:00
2020-03-07 15:57:45 -05:00
let inits = parseInt(SDB.getItem('kiri-init') || STATS.get('init') || 0) + 1;
2020-03-07 14:00:41 -05:00
SDB.setItem('kiri-init', inits);
STATS.set('init', inits);
2017-04-18 17:09:33 -04:00
STATS.set('kiri', kiri.version);
2020-12-31 23:22:39 -05:00
// remove version from url, preserve other settings
WIN.history.replaceState({},'','/kiri/' + encodeOpt(SETUP) + LOC.hash);
2017-04-18 17:09:33 -04:00
/** ******************************************************************
* Utility Functions
******************************************************************* */
2021-12-09 23:12:35 -05:00
function unitScale() {
return MODE === MODES.CAM && settings.controller.units === 'in' ? 25.4 : 1;
}
function alert2(message, time) {
if (message === undefined || message === null) {
return updateAlerts(true);
}
let rec = [message, Date.now(), time, true];
if (feature.alert_event) {
2022-02-17 14:40:51 -05:00
api.event.emit('alert', rec);
2021-12-09 23:12:35 -05:00
} else {
alerts.push(rec);
updateAlerts();
}
return rec;
}
function alert2cancel(rec,recs) {
if (Array.isArray(recs)) {
for (let r of recs) {
alert2cancel(r);
}
return;
}
if (feature.alert_event) {
2022-02-17 14:40:51 -05:00
api.event.emit('alert.cancel', rec);
2021-12-09 23:12:35 -05:00
return;
}
if (Array.isArray(rec)) {
rec[3] = false;
updateAlerts();
}
}
function updateAlerts(clear) {
if (clear) {
alerts = [];
}
let now = Date.now();
// 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';
}
}
function getShowFavorites(bool) {
if (bool !== undefined) {
SDB.setItem('dev-favorites', bool);
showFavorites = bool;
return bool;
}
return showFavorites;
}
function triggerSettingsEvent() {
2022-02-17 14:40:51 -05:00
api.event.emit('settings', settings);
2021-12-09 23:12:35 -05:00
}
2020-03-07 12:33:26 -05:00
2017-04-18 17:09:33 -04:00
function isSecure(proto) {
return proto.toLowerCase().indexOf("https") === 0;
}
function ls2o(key,def) {
return js2o(SDB.getItem(key),def);
}
function setProgress(value, msg) {
if (value) {
2022-02-17 14:40:51 -05:00
value = (value * 100).round(4);
2017-04-18 17:09:33 -04:00
UI.loading.display = 'block';
UI.progress.width = value+'%';
if (msg) UI.prostatus.innerHTML = msg;
} else {
UI.loading.display = 'none';
}
}
function bound(v,min,max) {
return Math.max(min,Math.min(max,v));
}
2021-02-10 01:02:18 -05:00
function getOverlappingRanges(lo, hi) {
let ranges = [];
for (let range of settings.process.ranges || []) {
2021-02-10 01:02:18 -05:00
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;
}
2021-02-10 11:45:42 -05:00
// set process override values for a range
2021-02-10 16:10:18 -05:00
function updateRange(lo, hi, values, add) {
2021-02-10 11:45:42 -05:00
let ranges = settings.process.ranges;
2021-02-10 16:10:18 -05:00
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
});
updateFieldsFromSettings(settings.process);
2022-02-17 14:40:51 -05:00
api.show.alert("update ranges", 2);
api.event.emit("range.updates", ranges);
return;
}
2021-02-10 16:10:18 -05:00
// 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);
}
2021-02-10 11:45:42 -05:00
}
}
2022-02-17 14:40:51 -05:00
api.event.emit("range.updates", ranges);
2021-02-10 11:45:42 -05:00
return;
2021-02-10 01:02:18 -05:00
}
2021-02-10 16:10:18 -05:00
// 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);
2021-02-10 16:10:18 -05:00
// 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;
}
}
2021-02-10 01:02:18 -05:00
}
2021-02-10 16:10:18 -05:00
// merge contiguous matching ranges
ranges = settings.process.ranges = [];
2021-02-10 16:10:18 -05:00
let range;
for (let i=min; i<=max; i++) {
let slice = slices[i];
if (slice && !range) {
range = {lo: i, hi: i, fields: slice};
2022-02-18 10:52:49 -05:00
} else if (slice && range && areEqual(range.fields, slice)) {
2021-02-10 16:10:18 -05:00
range.hi = i;
} else if (range) {
ranges.push(range);
if (slice) {
range = {lo: i, hi: i, fields: slice};
} else {
range = undefined;
}
}
2021-02-10 01:02:18 -05:00
}
2021-02-10 16:10:18 -05:00
ranges.push(range);
ranges.appendAll(exclude);
2021-02-10 16:10:18 -05:00
updateFieldsFromSettings(settings.process);
2022-02-17 14:40:51 -05:00
api.show.alert("update ranges", 2);
api.event.emit("range.updates", ranges);
2021-02-10 01:02:18 -05:00
}
let overrides = {};
2021-02-10 11:45:42 -05:00
// updates editable fields that are range dependent
2021-02-10 01:02:18 -05:00
function updateFieldsFromRange() {
2021-02-10 16:10:18 -05:00
return;
2021-02-10 01:02:18 -05:00
if (settings.mode !== 'FDM' || viewMode !== VIEWS.SLICE || !settings.process.ranges) {
2021-02-10 11:45:42 -05:00
let okeys = Object.keys(overrides);
if (okeys.length) {
updateFieldsFromSettings(overrides);
overrides = {};
}
2021-02-10 01:02:18 -05:00
return;
}
let match = 0;
let values = {};
let restores = Object.clone(overrides);
2022-02-17 14:40:51 -05:00
let { layer_lo, layer_hi } = api.var;
for (let range of getOverlappingRanges(api.var.layer_lo, api.var.layer_hi)) {
2021-02-10 01:02:18 -05:00
for (let key of Object.keys(range.fields)) {
values[key] = range.fields[key];
overrides[key] = settings.process[key];
delete restores[key];
match++;
}
}
if (match) {
updateFieldsFromSettings(values);
}
let rkeys = Object.keys(restores);
if (rkeys.length) {
updateFieldsFromSettings(restores);
for (let key of rkeys) {
delete overrides[key];
}
}
2021-02-10 11:45:42 -05:00
UC.refresh();
2021-02-10 01:02:18 -05:00
}
2022-02-17 21:52:54 -05:00
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
});
}
}
2020-06-07 23:02:05 -04:00
function updateSlider() {
2022-02-17 14:40:51 -05:00
api.event.emit("slider.set", {
start: (api.var.layer_lo / api.var.layer_max),
end: (api.var.layer_hi / api.var.layer_max)
2020-06-07 23:02:05 -04:00
});
2021-02-10 01:02:18 -05:00
updateFieldsFromRange();
2020-06-07 23:02:05 -04:00
}
function setVisibleLayer(h, l) {
2022-02-17 14:40:51 -05:00
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");
2020-06-07 23:02:05 -04:00
updateSlider();
showSlices();
2017-04-18 17:09:33 -04:00
}
function meshArray() {
2020-04-09 14:52:43 -04:00
let out = [];
2019-05-07 14:35:39 -04:00
forAllWidgets(function(widget) {
2020-12-01 00:02:34 -05:00
if (!feature.hoverAdds) {
out.push(widget.mesh);
}
2020-11-22 20:06:11 -05:00
out.appendAll(widget.adds);
2017-04-18 17:09:33 -04:00
});
return out;
}
function replaceVertices(vertices) {
2022-02-18 13:27:26 -05:00
let widgets = 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);
}
platform.update();
}
if (vertices) {
onload(vertices);
} else {
// dialog
$('load-file').onchange = function(event) {
2022-02-17 14:40:51 -05:00
load.File.load(event.target.files[0])
.then(data => onload(data[0].mesh))
.catch(error => console.log({error}));
};
$('load-file').click();
}
}
2021-09-06 21:47:27 -04:00
function healWidgets() {
2022-02-17 14:40:51 -05:00
let widgets = api.widgets.all();
2021-09-07 13:34:02 -04:00
let marker;
2021-09-06 21:47:27 -04:00
if (widgets.length) {
2022-02-17 14:40:51 -05:00
marker = api.show.alert("Analyzing objects", 100000);
2021-09-06 21:47:27 -04:00
} else {
return;
}
setTimeout(() => {
2021-09-08 11:04:57 -04:00
Promise.all(widgets.map(w => w.heal())).then(mod => {
2022-02-17 14:40:51 -05:00
api.hide.alert(marker);
let healed = mod.filter(m => m).length;
2021-09-08 11:04:57 -04:00
if (healed) {
2022-02-17 14:40:51 -05:00
api.show.alert(`${healed} Object${healed ? 's':''} healed`);
2021-09-08 11:04:57 -04:00
} else {
2022-02-17 14:40:51 -05:00
api.show.alert('Nothing found to heal');
}
2021-09-08 11:04:57 -04:00
});
}, 1);
2021-09-06 21:47:27 -04:00
}
2019-05-07 14:35:39 -04:00
function forAllWidgets(f) {
2017-04-18 17:09:33 -04:00
WIDGETS.slice().forEach(function(widget) {
f(widget);
});
}
function setWireframe(bool, color, opacity) {
forAllWidgets(function(w) { w.setWireframe(bool, color, opacity) });
SPACE.update();
}
2017-04-18 17:09:33 -04:00
function updateSliderMax(set) {
let max = STACKS.getRange().tallest - 1;
2022-02-17 14:40:51 -05:00
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");
2020-06-07 23:02:05 -04:00
updateSlider();
2017-04-18 17:09:33 -04:00
}
}
function hideSlices() {
2020-11-08 10:55:36 -05:00
STACKS.clear();
2022-02-17 14:40:51 -05:00
setOpacity(COLOR.model_opacity);
2019-05-07 14:35:39 -04:00
forAllWidgets(function(widget) {
2017-04-18 17:09:33 -04:00
widget.setWireframe(false);
});
}
function setWidgetVisibility(bool) {
forAllWidgets(w => {
if (bool) {
w.show();
} else {
w.hide();
}
});
}
2017-04-18 17:09:33 -04:00
/**
* hide or show slice-layers and their sub-elements
*
* @param {number} [layer]
*/
function showSlices(layer) {
2020-06-08 12:23:35 -04:00
if (viewMode === VIEWS.ARRANGE) {
return;
}
2020-06-07 23:02:05 -04:00
showSlider();
2017-04-18 17:09:33 -04:00
if (typeof(layer) === 'string' || typeof(layer) === 'number') {
layer = parseInt(layer);
} else {
2022-02-17 14:40:51 -05:00
layer = api.var.layer_hi;
2017-04-18 17:09:33 -04:00
}
2022-02-17 14:40:51 -05:00
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");
2017-04-18 17:09:33 -04:00
let cam = MODE === MODES.CAM,
2020-07-15 10:47:40 -04:00
sla = MODE === MODES.SLA,
2022-02-17 14:40:51 -05:00
hi = cam ? api.var.layer_max - api.var.layer_lo : api.var.layer_hi,
lo = cam ? api.var.layer_max - api.var.layer_hi : api.var.layer_lo;
2017-04-18 17:09:33 -04:00
2020-06-07 23:02:05 -04:00
updateSlider();
2022-02-17 14:40:51 -05:00
STACKS.setRange(api.var.layer_lo, api.var.layer_hi);
2020-06-07 23:02:05 -04:00
2017-04-18 17:09:33 -04:00
SPACE.update();
}
function cancelWorker() {
2022-02-17 14:40:51 -05:00
if (kiri.work.isBusy()) {
kiri.work.restart();
2021-02-24 11:09:02 -05:00
}
}
2020-06-07 23:02:05 -04:00
function showSlider() {
UI.layers.style.display = 'flex';
UI.slider.style.display = 'flex';
}
function hideSlider(andmenu) {
UI.layers.style.display = 'none';
UI.slider.style.display = 'none';
2020-11-24 00:48:45 -05:00
UI.speeds.style.display = 'none';
2020-06-07 23:02:05 -04:00
}
function prepareSlices(callback, scale = 1, offset = 0) {
2017-10-30 14:21:23 -04:00
if (viewMode == VIEWS.ARRANGE) {
2020-03-10 14:23:42 -04:00
let snap = SPACE.screenshot();
2022-02-17 14:40:51 -05:00
api.view.snapshot = snap.substring(snap.indexOf(",") + 1);
kiri.work.snap(SPACE.screenshot2({width: 640}));
2017-10-30 14:21:23 -04:00
}
2020-06-09 18:12:59 -04:00
if (MODE === MODES.SLA && !callback) {
2020-11-08 18:39:21 -05:00
callback = preparePreview;
2020-06-09 18:12:59 -04:00
}
2021-11-16 22:07:49 -05:00
// force layout in belt mode when widget exceeds bed length
if (WIDGETS.length && settings.device.bedBelt) {
let doLayout = false;
for (let w of WIDGETS) {
let bb = w.getBoundingBox();
let yspan = bb.max.y - bb.min.y;
if (yspan > settings.device.bedDepth) {
doLayout = true;
}
}
if (doLayout) {
platformLayout();
}
}
2021-11-16 22:07:49 -05:00
let process = settings.process,
device = settings.device,
control = settings.controller,
2021-11-16 22:07:49 -05:00
isBelt = device.bedBelt,
mode = settings.mode,
now = Date.now(),
totvert = 0,
track = {},
totalProgress;
// clear completion marks
complete = {};
2020-06-07 23:02:05 -04:00
hideSlider(true);
platform.deselect();
setViewMode(VIEWS.SLICE);
2020-07-03 13:04:47 -04:00
2022-02-17 14:40:51 -05:00
api.conf.save();
api.event.emit('slice.begin', getMode());
2017-04-18 17:09:33 -04:00
let slicing = WIDGETS.slice().filter(w => !w.track.ignore && !w.meta.disabled);
// determing this widgets % of processing time estimated by vertex count
for (let widget of slicing) {
totvert += widget.getVertices().count;
}
2021-11-16 22:07:49 -05:00
let defvert = totvert / slicing.length;
2017-04-18 17:09:33 -04:00
2022-02-17 14:40:51 -05:00
setOpacity(COLOR.slicing_opacity);
2017-04-18 17:09:33 -04:00
2021-02-20 22:50:03 -05:00
let segtimes = {},
segNumber = 0,
errored = false,
startTime = Date.now(),
toSlice = slicing.slice(),
camOrLaser = mode === 'CAM' || mode === 'LASER',
2021-11-16 22:07:49 -05:00
extruders = {},
2021-02-20 22:50:03 -05:00
lastMsg;
for (let widget of toSlice) {
2017-04-18 17:09:33 -04:00
widget.stats.progress = 0;
2022-02-17 14:40:51 -05:00
widget.setColor(COLOR.slicing);
2021-11-16 22:07:49 -05:00
extruders[widget.anno.extruder] = widget.anno.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 {
process.beltAnchor = process.firstLayerBeltLead;
}
2021-11-16 22:07:49 -05:00
STACKS.clear();
if (isBelt) {
2022-02-17 14:40:51 -05:00
kiri.client.clear();
2021-11-16 22:07:49 -05:00
}
2022-02-17 14:40:51 -05:00
kiri.client.sync();
kiri.client.rotate(settings);
2021-11-16 22:07:49 -05:00
sliceNext();
function sliceNext() {
if (toSlice.length) {
sliceWidget(toSlice.shift())
} else {
2022-02-17 14:40:51 -05:00
kiri.client.sliceAll(settings, sliceDone);
}
}
function sliceWidget(widget) {
widget.stack = STACKS.create(widget.id, widget.mesh);
let factor = (widget.getVertices().count / defvert);
2021-08-30 13:50:24 -04:00
// compensate for zcut (widget moved through floor)
2021-08-30 15:05:29 -04:00
widget.stack.obj.view.position.z = widget.track.zcut || 0;
2021-08-30 13:50:24 -04:00
widget.slice(settings, function(sliced, error) {
2021-01-24 00:13:09 -05:00
widget.rotinfo = null;
let mark = Date.now();
2017-04-18 17:09:33 -04:00
// update UI info
if (sliced) {
// update segment time
if (lastMsg) {
2021-02-20 22:50:03 -05:00
segtimes[`${widget.id}_${segNumber++}_${lastMsg}`] = mark - startTime;
}
2022-02-17 14:40:51 -05:00
api.event.emit('slice', getMode());
2017-04-18 17:09:33 -04:00
}
// handle slicing errors
if (error && !errored) {
errored = true;
setViewMode(VIEWS.ARRANGE);
alert2(error, 5);
2022-02-17 14:40:51 -05:00
api.show.progress(0);
kiri.client.restart();
api.event.emit('slice.error', error);
}
if (errored) {
// terminate slicing
sliceDone();
} else {
// start next widget slice
sliceNext();
2017-04-18 17:09:33 -04:00
}
}, function(update, msg) {
if (msg && msg !== lastMsg) {
let mark = Date.now();
if (lastMsg) {
2021-02-20 22:50:03 -05:00
segtimes[`${widget.id}_${segNumber++}_${lastMsg}`] = mark - startTime;
}
2017-04-18 17:09:33 -04:00
lastMsg = msg;
startTime = mark;
}
// on update
track[widget.id] = (update || 0) * factor;
2017-04-18 17:09:33 -04:00
totalProgress = 0;
for (let w of slicing) {
2017-04-18 17:09:33 -04:00
totalProgress += (track[w.id] || 0);
}
2022-02-17 14:40:51 -05:00
api.show.progress(offset + (totalProgress / WIDGETS.length) * scale, msg);
2020-11-03 11:19:13 -05:00
});
}
function sliceDone() {
let alert = null;
if (scale === 1 && feature.work_alerts && slicing.length) {
2022-02-17 14:40:51 -05:00
alert = api.show.alert("Rendering");
};
2022-02-17 14:40:51 -05:00
kiri.client.unrotate(settings, () => {
for (let widget of slicing) {
// on done
segtimes[`${widget.id}_${segNumber++}_draw`] = widget.render(widget.stack);
// rotate stack for belt beds
if (widget.rotinfo) {
widget.stack.obj.rotate(widget.rotinfo);
}
if (scale === 1) {
// clear wireframe
2022-02-17 14:40:51 -05:00
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);
}
}
updateSliderMax(true);
setVisibleLayer(-1, 0);
if (scale === 1) {
updateStackLabelState();
}
2022-01-26 17:28:23 -05:00
if (!isBelt && control.lineType === 'line' && !process.xray) {
$('render-ghost').onclick();
}
});
if (scale === 1) {
2022-02-17 14:40:51 -05:00
api.show.progress(0);
}
// cause visuals to update
SPACE.scene.active();
// mark slicing complete for prep/preview
complete.slice = true;
2022-02-17 14:40:51 -05:00
api.event.emit('slice.end', getMode());
// print stats
segtimes.total = Date.now() - now;
2021-07-04 21:05:46 -04:00
console.log(segtimes);
if (callback && typeof callback === 'function') {
callback();
}
}
2017-04-18 17:09:33 -04:00
}
function preparePreview(callback, scale = 1, offset = 0) {
if (complete.preview === feature.pmode) {
if (settings.device.extruders.length > 1) {
if (++feature.pmode > 2) {
feature.pmode = 1;
}
} else {
if (callback) callback();
return;
}
}
if (!complete.slice) {
settings.render = false;
2020-11-08 18:39:21 -05:00
prepareSlices(() => {
preparePreview(callback, 0.25, 0.75);
}, 0.75);
return;
2020-11-08 18:39:21 -05:00
}
hideSlider(true);
let isCam = MODE === MODES.CAM, pMode = getMode();
setViewMode(VIEWS.PREVIEW);
2022-02-17 14:40:51 -05:00
api.conf.save();
api.event.emit('preview.begin', pMode);
2020-11-08 18:39:21 -05:00
if (isCam) {
2022-02-17 14:40:51 -05:00
setOpacity(COLOR.cam_preview_opacity);
2020-11-08 18:39:21 -05:00
forAllWidgets(function(widget) {
2022-02-17 14:40:51 -05:00
widget.setColor(COLOR.cam_preview);
2020-11-08 18:39:21 -05:00
});
} else if (offset === 0) {
2022-02-17 14:40:51 -05:00
setOpacity(COLOR.preview_opacity);
2020-11-08 18:39:21 -05:00
}
2021-01-09 21:08:24 -05:00
let now = Date.now(),
2022-01-26 18:28:12 -05:00
isBelt = settings.device.bedBelt,
segNumber = 0,
2021-01-09 21:08:24 -05:00
segtimes = {},
startTime,
lastMsg,
output = [];
// pass preview mode to worker
settings.pmode = feature.pmode;
settings.render = true;
2022-02-17 14:40:51 -05:00
kiri.client.prepare(settings, function(progress, message, layer) {
if (layer) {
2022-02-17 14:40:51 -05:00
output.push(kiri.codec.decode(layer));
}
if (message && message !== lastMsg) {
let mark = Date.now();
if (lastMsg) {
segtimes[`${segNumber++}_${lastMsg}`] = mark - startTime;
}
lastMsg = message;
startTime = mark;
}
2022-02-17 14:40:51 -05:00
api.show.progress(offset + progress * scale, message);
}, function (reply, maxSpeed, minSpeed) {
// handle worker errors
if (reply && reply.error) {
alert2(reply.error, 5);
setViewMode(VIEWS.ARRANGE);
2022-02-17 14:40:51 -05:00
api.event.emit('preview.error', reply.error);
api.show.progress(0);
SPACE.update();
return;
}
if (lastMsg) {
segtimes[`${segNumber++}_${lastMsg}`] = Date.now() - startTime;
}
2022-02-17 14:40:51 -05:00
api.show.progress(0);
2020-11-08 18:39:21 -05:00
if (!isCam) setOpacity(0);
if (output.length) {
2022-02-17 14:40:51 -05:00
let alert = feature.work_alerts ? api.show.alert("Rendering") : null;
startTime = Date.now();
2020-11-08 18:39:21 -05:00
STACKS.clear();
const stack = STACKS.create('print', SPACE.world)
2020-11-08 18:39:21 -05:00
output.forEach(layer => {
stack.add(layer);
});
// rotate stack for belt beds
2022-01-26 18:28:12 -05:00
if (isBelt && WIDGETS[0].rotinfo) {
let ri = WIDGETS[0].rotinfo;
2021-01-09 21:08:24 -05:00
ri.dz = 0;
ri.dy = settings.device.bedDepth / 2;
stack.obj.rotate(WIDGETS[0].rotinfo);
}
2022-02-17 14:40:51 -05:00
api.hide.alert(alert);
segtimes[`${segNumber}_draw`] = Date.now() - startTime;
2020-11-08 18:39:21 -05:00
}
// print stats
segtimes.total = Date.now() - now;
2021-07-04 21:05:46 -04:00
console.log(segtimes);
2022-02-17 14:40:51 -05:00
api.event.emit('print', pMode);
api.event.emit('preview.end', pMode);
2020-11-08 18:39:21 -05:00
SPACE.update();
updateSliderMax(true);
setVisibleLayer(-1, 0);
if (feature.pmode === PMODES.SPEED) {
updateSpeeds(maxSpeed, minSpeed);
} else {
updateSpeeds();
}
updateStackLabelState();
2021-02-07 19:25:30 -05:00
2022-01-26 17:28:23 -05:00
let { controller, process } = settings;
if (!isBelt && controller.lineType === 'line' && !process.xray) {
$('render-ghost').onclick();
}
// mark preview complete for export
complete.preview = feature.pmode;
2020-11-08 18:39:21 -05:00
if (typeof(callback) === 'function') {
callback();
}
});
}
2021-03-04 15:17:09 -05:00
function prepareAnimation() {
2022-02-17 14:40:51 -05:00
api.event.emit("function.animate", {mode: settings.mode});
2021-03-04 15:17:09 -05:00
}
function prepareExport() {
const argsave = arguments;
if (!complete.preview) {
preparePreview(() => {
prepareExport(...argsave);
});
return;
}
2022-02-17 14:40:51 -05:00
api.event.emit("function.export", {mode: settings.mode});
complete.export = true;
2022-02-17 14:40:51 -05:00
kiri.export(...argsave);
}
function updateStackLabelState() {
// match label checkboxes to preference
for (let label of STACKS.getLabels()) {
let check = `${settings.mode}-${viewMode}-${label}`;
STACKS.setVisible(label, settings.labels[check] !== false);
}
}
2020-11-13 09:27:42 -05:00
function loadCode(code, type) {
2022-02-17 14:40:51 -05:00
api.event.emit("code.load", {code, type});
2020-11-13 09:27:42 -05:00
setViewMode(VIEWS.PREVIEW);
setOpacity(0);
2022-02-17 14:40:51 -05:00
kiri.client.parse({code, type, settings}, progress => {
api.show.progress(progress, "parsing");
}, (layers, maxSpeed, minSpeed) => {
2022-02-17 14:40:51 -05:00
api.show.progress(0);
2020-11-13 09:27:42 -05:00
STACKS.clear();
2022-01-02 19:29:28 -05:00
const stack = STACKS.create('parse', SPACE.world);
2020-11-13 09:27:42 -05:00
layers.forEach(layer => stack.add(layer));
updateSliderMax(true);
updateSpeeds(maxSpeed, minSpeed);
2020-11-13 09:27:42 -05:00
showSlices();
updateStackLabelState();
2020-11-13 09:27:42 -05:00
SPACE.update();
2022-02-17 14:40:51 -05:00
api.event.emit("code.loaded", {code, type});
2020-11-13 09:27:42 -05:00
});
}
2021-01-18 15:11:18 -05:00
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>&nbsp;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>&nbsp;invert alpha</th><td><input id='alpha-inv' type='checkbox'></td></tr>",
2020-11-27 15:03:25 -05:00
" <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,
2020-11-27 15:03:25 -05:00
border: parseInt($('png-border').value) || 0,
inv_image: $('png-inv').checked,
inv_alpha: $('alpha-inv').checked
});
}
});
}
function loadImage(image, opt = {}) {
let info = Object.assign({settings, png:image}, opt);
2022-02-17 14:40:51 -05:00
kiri.client.image2mesh(info, progress => {
api.show.progress(progress, "converting");
2020-11-26 23:18:28 -05:00
}, output => {
2022-02-17 14:40:51 -05:00
api.show.progress(0);
let {bigv, verts, index} = output;
2020-11-26 23:49:44 -05:00
// let mat = new THREE.MeshPhongMaterial({
// shininess: 0x101010,
// specular: 0x101010,
// transparent: true,
// opacity: 1,
// color: 0x999999,
// side: THREE.DoubleSide
// });
//
// let geo = new THREE.BufferGeometry();
// geo.setAttribute('position', new THREE.BufferAttribute(bigv, 3));
// // geo.setIndex([...index]); // doesn't like the Uint32Array
// geo.computeFaceNormals();
// geo.computeVertexNormals();
//
// let mesh = new THREE.Mesh(geo, mat);
// mesh.castShadow = true;
// mesh.receiveShadow = true;
//
2022-01-02 19:29:28 -05:00
// SPACE.world.add(mesh);
let widget = newWidget().loadVertices(bigv)
widget.meta.file = opt.file;
platform.add(widget);
2020-11-26 23:18:28 -05:00
});
}
/** ******************************************************************
* Selection Functions
******************************************************************* */
2021-03-01 00:22:07 -05:00
function groupMerge() {
2022-02-18 13:27:26 -05:00
Widget.Groups.merge(selection.widgets(true));
2021-03-01 00:22:07 -05:00
}
function groupSplit() {
2022-02-18 13:27:26 -05:00
Widget.Groups.split(selection.widgets(false));
}
/** ******************************************************************
* Settings Functions
******************************************************************* */
2017-04-18 17:09:33 -04:00
2020-04-09 14:52:43 -04:00
// given a settings region, update values of matching bound UI fields
function updateFieldsFromSettings(setrec, uirec = UI, trace) {
2020-12-23 14:27:18 -05:00
if (!setrec) {
return console.trace("missing scope");
}
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'),
2022-02-17 14:40:51 -05:00
list = uie._source || settings[source] || api.lists[source],
2020-12-23 14:27:18 -05:00
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;
}
2020-12-23 14:27:18 -05:00
let opt = DOC.createElement('option');
opt.appendChild(DOC.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 = '';
2017-04-18 17:09:33 -04:00
}
}
}
}
/**
* @returns {Object}
*/
2021-02-10 01:02:18 -05:00
function updateSettingsFromFields(setrec, uirec = UI, changes) {
2020-12-23 14:27:18 -05:00
if (!setrec) {
return console.trace("missing scope");
}
2017-04-18 17:09:33 -04:00
2021-02-10 16:10:18 -05:00
let lastChange = UC.lastChange();
2020-12-23 14:27:18 -05:00
// for each key in setrec object
2021-02-10 01:02:18 -05:00
for (let key in setrec) {
2020-12-23 14:27:18 -05:00
if (!setrec.hasOwnProperty(key)) {
// console.log({no_setrec: key});
2020-04-09 14:52:43 -04:00
continue;
}
2020-12-23 14:27:18 -05:00
if (!uirec.hasOwnProperty(key)) {
// console.log({no_uirec: key});
2020-12-23 14:27:18 -05:00
continue;
}
let nval = null, uie = uirec[key];
// skip empty UI values
if (!uie || uie === '') {
// console.log({uie_empty: key});
2020-12-23 14:27:18 -05:00
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') {
nval = parseInt(nval);
2017-04-18 17:09:33 -04:00
}
2020-04-09 14:52:43 -04:00
} else {
2020-12-23 14:27:18 -05:00
nval = setrec[key];
2017-04-18 17:09:33 -04:00
}
2020-12-23 14:27:18 -05:00
} else if (uie.type === 'textarea') {
nval = uie.value.trim().split('\n').filter(v => v !== '');
} else {
continue;
}
2021-02-10 16:10:18 -05:00
if (lastChange === uie) {
if (changes) changes[key] = nval;
}
2022-02-18 10:52:49 -05:00
if (!areEqual(setrec[key], nval)) {
2020-12-23 14:27:18 -05:00
setrec[key] = nval;
2021-02-10 01:02:18 -05:00
if (changes) {
changes[key] = nval;
}
2017-04-18 17:09:33 -04:00
}
}
return settings;
}
function updateFields() {
updateFieldsFromSettings(settings.device);
updateFieldsFromSettings(settings.process);
updateFieldsFromSettings(settings.controller);
2020-04-10 12:27:08 -04:00
updateExtruderFields(settings.device);
}
function updateExtruderFields(device) {
2020-04-09 14:52:43 -04:00
if (device.extruders && device.extruders[device.internal]) {
updateFieldsFromSettings(device.extruders[device.internal]);
UI.extruder.firstChild.innerText = `${LANG.dv_gr_ext} [${device.internal+1}/${device.extruders.length}]`;
2020-04-10 12:27:08 -04:00
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;
2020-04-10 13:12:13 -04:00
UI.extDel.onclick = function() {
device.extruders.splice(device.internal,1);
2020-04-10 13:24:45 -04:00
device.internal = Math.min(device.internal, device.extruders.length-1);
2020-04-10 13:12:13 -04:00
updateExtruderFields(device);
};
UI.extAdd.onclick = function() {
let copy = clone(device.extruders[device.internal]);
copy.extSelect = [`T${device.extruders.length}`];
copy.extDeselect = [];
device.extruders.push(copy);
2020-04-10 13:12:13 -04:00
device.internal = device.extruders.length - 1;
updateExtruderFields(device);
};
2020-04-09 14:52:43 -04:00
}
2017-04-18 17:09:33 -04:00
}
function updateSettings(opt = {}) {
let { controller, device, process, mode, sproc, cproc } = settings;
updateSettingsFromFields(controller);
switch (controller.units) {
2020-11-05 11:00:34 -05:00
case 'mm': UC.setUnits(1); break;
case 'in': UC.setUnits(25.4); break;
}
if (opt.controller) {
return;
}
updateSettingsFromFields(device, undefined, undefined, true);
2021-02-10 11:45:42 -05:00
// range-specific values
2021-02-10 01:02:18 -05:00
if (settings.mode === 'FDM' && viewMode === VIEWS.SLICE) {
let changes = {};
let values = process;
2022-02-17 14:40:51 -05:00
let { layer_lo, layer_hi, layer_max } = api.var;
2021-02-10 11:45:42 -05:00
let range = { lo: layer_lo, hi: layer_hi };
let add = false;
2021-02-10 01:02:18 -05:00
if (layer_lo > 0 || layer_hi < layer_max) {
values = Object.clone(process);
2021-02-10 11:45:42 -05:00
add = true;
2021-02-10 01:02:18 -05:00
}
updateSettingsFromFields(values, undefined, changes);
if (range) {
2021-02-10 16:10:18 -05:00
updateRange(range.lo, range.hi, changes, add);
2021-02-10 01:02:18 -05:00
}
} else {
updateSettingsFromFields(process);
}
2020-04-09 14:52:43 -04:00
if (device.extruders && device.extruders[device.internal]) {
updateSettingsFromFields(device.extruders[device.internal]);
}
2022-02-17 14:40:51 -05:00
api.conf.save();
let compare = sproc[mode][cproc[mode]];
let same = true;
2021-02-14 14:35:50 -05:00
if (compare)
for (let [key, val] of Object.entries(compare).filter(v => v[0] !== 'processName')) {
2021-02-10 01:02:18 -05:00
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 ? '' : ' *'}`;
2017-04-18 17:09:33 -04:00
}
function saveSettings() {
const view = SPACE.view.save();
2020-04-13 17:42:40 -04:00
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;
settings.device.bedBelt = UI.deviceBelt.checked;
settings.device.bedRound = UI.deviceRound.checked;
2021-08-17 11:09:51 -04:00
settings.device.originCenter = UI.deviceOrigin.checked || UI.deviceRound.checked;
settings.device.fwRetract = UI.fwRetract.checked;
2017-04-18 17:09:33 -04:00
SDB.setItem('ws-settings', JSON.stringify(settings));
2022-02-17 14:40:51 -05:00
api.event.emit('settings.saved', settings);
2017-04-18 17:09:33 -04:00
}
function settingsImportZip(data, ask) {
2022-02-17 14:40:51 -05:00
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 => {
2022-02-17 14:40:51 -05:00
api.hide.alert(alert);
settingsImport(JSON.parse(json), ask);
});
}
}
});
}
// import and convert prusa ini file
function settingsPrusaConvert(data) {
let map = {};
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');
}
return l;
})
.forEach(l => {
map[l[0]] = l[1];
});
} catch (e) {
return UC.alert('invalid file');
}
// device setup
2022-02-17 14:40:51 -05:00
let device = Object.clone(kiri.conf.defaults.fdm.d);
let dname = device.deviceName = map.printer_model;
if (dname) {
let mode = "FDM";
device.mode = mode;
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
2022-02-17 14:40:51 -05:00
let process = Object.clone(kiri.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;
2022-02-17 14:40:51 -05:00
api.show.devices();
}
});
}
2020-04-07 21:45:21 -04:00
function settingsImport(data, ask) {
if (typeof(data) === 'string') {
try {
2022-02-17 14:40:51 -05:00
data = api.util.b64dec(data);
2020-04-07 21:45:21 -04:00
} catch (e) {
UC.alert('invalid import format');
2020-04-09 14:52:43 -04:00
console.log('data',data);
2020-04-07 21:45:21 -04:00
return;
}
}
if (LOCAL) console.log('import',data);
2020-09-24 08:52:59 -04:00
let isSettings = (data.settings && data.version && data.time);
let isProcess = (data.process && data.version && data.time && data.mode && data.name);
2020-09-24 08:52:59 -04:00
let isDevice = (data.device && data.version && data.time);
2020-11-16 09:17:02 -05:00
let isWork = (data.work);
if (!isSettings && !isDevice && !isProcess) {
2020-09-24 08:52:59 -04:00
UC.alert('invalid settings or device format');
2020-04-09 14:52:43 -04:00
console.log('data',data);
2020-04-07 21:45:21 -04:00
return;
}
function doit() {
2020-09-24 08:52:59 -04:00
if (isDevice) {
if (settings.devices[data.device]) {
UC.confirm(`Replace device ${data.device}?`).then(yes => {
if (yes) {
settings.devices[data.device] = data.code;
2022-02-17 14:40:51 -05:00
api.show.devices();
2020-09-24 08:52:59 -04:00
}
});
} else {
settings.devices[data.device] = data.code;
2022-02-17 14:40:51 -05:00
api.show.devices();
2020-09-24 08:52:59 -04:00
}
}
if (isProcess) {
if (settings.sproc[data.mode][data.name]) {
UC.confirm(`Replace process ${data.name}?`).then(yes => {
if (yes) {
settings.sproc[data.mode][data.name] = data.process;
2022-02-17 14:40:51 -05:00
api.conf.show();
}
});
} else {
settings.sproc[data.mode][data.name] = data.process;
2022-02-17 14:40:51 -05:00
api.conf.show();
}
}
if (isSettings) {
clearWorkspace();
settings = CONF.normalize(data.settings);
SDB.setItem('ws-settings', JSON.stringify(settings));
if (LOCAL) console.log('settings',Object.clone(settings));
if (isWork) {
2022-02-17 14:40:51 -05:00
api.platform.clear();
kiri.codec.decode(data.work).forEach(widget => {
platform.add(widget, 0, true);
});
if (data.view) {
SPACE.view.load(data.view);
}
2020-11-16 09:17:02 -05:00
}
restoreSettings();
restoreWorkspace(() => {
UI.sync();
}, true);
2020-11-16 09:17:02 -05:00
}
}
if (ask) {
2020-11-16 09:17:02 -05:00
let opt = {};
let prompt = isDevice ?
`Import device "${data.device}"?` : isProcess ?
`Import process "${data.name}"?` :
2020-11-16 09:17:02 -05:00
`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>'
];
2020-09-24 08:52:59 -04:00
}
2020-11-16 09:17:02 -05:00
UC.confirm(prompt,undefined,undefined,opt).then((yes) => {
if (yes) doit();
});
} else {
doit();
2020-04-07 21:45:21 -04:00
}
}
function settingsExport(opts = {}) {
2020-11-16 09:17:02 -05:00
const note = opts.node || undefined;
const shot = opts.work || opts.screen ? SPACE.screenshot() : undefined;
2022-02-17 14:40:51 -05:00
const work = opts.work ? kiri.codec.encode(WIDGETS,{_json_:true}) : undefined;
2020-11-16 09:17:02 -05:00
const view = opts.work ? SPACE.view.save() : undefined;
2021-10-22 09:49:24 -04:00
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 = {
2021-10-22 09:49:24 -04:00
settings: setn,
2022-02-17 14:40:51 -05:00
version: kiri.version,
screen: shot,
space: SPACE.info,
2020-11-16 09:17:02 -05:00
note: note,
work: work,
view: view,
2022-02-17 14:40:51 -05:00
moto: moto.id,
2020-04-07 21:45:21 -04:00
init: SDB.getItem('kiri-init'),
time: Date.now()
};
2022-02-17 14:40:51 -05:00
return opts.clear ? xprt : api.util.b64enc(xprt);
2020-04-07 21:45:21 -04:00
}
2021-02-23 10:08:04 -05:00
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);
});
};
}
2017-04-18 17:09:33 -04:00
function loadFile() {
$('load-file').onchange = function(event) {
2021-07-04 21:05:46 -04:00
console.log(event);
api.platform.load_files(event.target.files);
2017-04-18 17:09:33 -04:00
};
$('load-file').click();
2019-01-18 16:46:17 -05:00
// alert2("drag/drop STL files onto platform to import\nreload page to return to last saved state");
2017-04-18 17:09:33 -04:00
}
function saveWorkspace(quiet) {
2022-02-17 14:40:51 -05:00
api.conf.save();
2022-02-18 11:30:00 -05:00
const newWidgets = [];
const oldWidgets = js2o(SDB.getItem('ws-widgets'), []);
2020-03-10 10:40:57 -04:00
forAllWidgets(function(widget) {
2021-01-24 00:13:09 -05:00
if (widget.synth) return;
2020-03-10 10:40:57 -04:00
newWidgets.push(widget.id);
oldWidgets.remove(widget.id);
widget.saveState();
2022-02-17 14:40:51 -05:00
let ann = api.widgets.annotate(widget.id);
2020-11-25 09:35:28 -05:00
ann.file = widget.meta.file;
ann.url = widget.meta.url;
2020-03-10 10:40:57 -04:00
});
SDB.setItem('ws-widgets', o2js(newWidgets));
oldWidgets.forEach(function(wid) {
Widget.deleteFromState(wid);
});
2020-05-26 11:05:21 -04:00
// eliminate dangling saved widgets
2022-02-18 11:30:00 -05:00
FILES.deleteFilter(key => newWidgets.indexOf(key.substring(8)) < 0, "ws-save-", "ws-savf");
if (!quiet) {
alert2("workspace saved", 1);
}
2017-04-18 17:09:33 -04:00
}
2020-03-29 20:23:48 -04:00
function restoreSettings(save) {
2020-04-15 09:51:21 -04:00
let 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];
}
}
2020-04-15 09:51:21 -04:00
settings = CONF.normalize(newset);
// override camera from settings
if (settings.controller.view) {
SDB.removeItem('ws-camera');
2017-04-18 17:09:33 -04:00
}
2020-04-15 09:51:21 -04:00
// 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;
SDB.removeItem(fkey)
});
SDB.removeItem(localFilterKey);
// save updated settings
2022-02-17 14:40:51 -05:00
if (save) api.conf.save();
2020-04-15 09:51:21 -04:00
2020-04-13 17:42:40 -04:00
return newset;
2020-03-29 20:23:48 -04:00
}
function restoreWorkspace(ondone, skip_widget_load) {
let newset = restoreSettings(false),
2020-04-13 17:42:40 -04:00
camera = newset.controller.view,
2020-03-29 20:23:48 -04:00
toload = ls2o('ws-widgets',[]),
2020-04-13 17:42:40 -04:00
loaded = 0,
2020-03-29 20:23:48 -04:00
position = true;
2017-04-18 17:09:33 -04:00
updateFields();
platform.update_size();
2017-04-18 17:09:33 -04:00
SPACE.view.reset();
2020-04-15 15:20:02 -04:00
if (camera) {
SPACE.view.load(camera);
} else {
SPACE.view.home();
}
2017-04-18 17:09:33 -04:00
if (skip_widget_load) {
if (ondone) {
ondone();
}
return;
}
2017-04-18 17:09:33 -04:00
2020-03-17 16:32:43 -04:00
// remove any widgets from platform
2019-05-07 14:35:39 -04:00
forAllWidgets(function(widget) {
platform.delete(widget);
2017-04-18 17:09:33 -04:00
});
2020-03-17 16:32:43 -04:00
// remove widget keys if they are not going to be restored (TODO: remove in 3.1)
if (settings.widget)
2020-11-25 09:35:28 -05:00
Object.keys(settings.widget).filter(k => toload.indexOf(k) < 0).forEach(k => {
delete settings.widget[k];
});
2020-03-17 16:32:43 -04:00
// load any widget by name that was saved to the workspace
2017-04-18 17:09:33 -04:00
toload.forEach(function(widgetid) {
Widget.loadFromState(widgetid, function(widget) {
if (widget) {
platform.add(widget, 0, position);
2022-02-17 14:40:51 -05:00
let ann = api.widgets.annotate(widgetid);
2020-11-25 23:28:41 -05:00
widget.meta.file = ann.file;
widget.meta.url = ann.url;
}
2017-04-18 17:09:33 -04:00
if (++loaded === toload.length) {
platform.deselect();
if (ondone) {
ondone();
2020-03-17 16:32:43 -04:00
setTimeout(() => {
platform.update_top_z();
SPACE.update();
}, 1);
}
2017-04-18 17:09:33 -04:00
}
}, position);
2017-04-18 17:09:33 -04:00
});
return toload.length > 0;
}
function clearWorkspace() {
// free up worker cache/mem
2022-02-17 14:40:51 -05:00
kiri.work.clear();
2020-03-09 11:02:09 -04:00
platform.select_all();
2022-02-18 13:27:26 -05:00
platform.delete(selection.meshes());
2017-04-18 17:09:33 -04:00
}
function modalShowing() {
2020-06-06 11:17:04 -04:00
return UI.modal.style.display === 'flex';
2017-04-18 17:09:33 -04:00
}
function showModal(which) {
2020-06-06 11:17:04 -04:00
let mod = UI.modal,
style = mod.style,
visible = modalShowing(),
info = { pct: 0 };
["help","setup","tools","prefs","saves","files","print","local","any"].forEach(function(name) {
2020-06-06 11:17:04 -04:00
UI[name].style.display = name === which ? 'flex' : '';
2017-04-18 17:09:33 -04:00
});
2020-06-06 11:17:04 -04:00
function ondone() {
2022-02-17 14:40:51 -05:00
api.event.emit('modal.show', which);
2020-06-06 11:17:04 -04:00
}
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();
2017-04-18 17:09:33 -04:00
}
2020-06-06 11:17:04 -04:00
function hideModal() {
if (!modalShowing()) {
2018-02-02 09:02:07 -05:00
return;
}
2020-06-06 11:17:04 -04:00
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 = '' }).
start();
}
2017-04-18 17:09:33 -04:00
function showCatalog() {
showModal("files");
2017-04-18 17:09:33 -04:00
}
2020-03-10 10:40:57 -04:00
function getSettings() {
return settings;
}
function putSettings(newset) {
2020-04-09 14:52:43 -04:00
settings = CONF.normalize(newset);
2022-02-17 14:40:51 -05:00
api.conf.save()
api.space.restore(null, true);
2020-03-10 10:40:57 -04:00
}
2020-03-29 20:23:48 -04:00
function editSettings(e) {
2020-04-09 14:52:43 -04:00
let mode = getMode(),
name = e.target.getAttribute("name"),
load = settings.sproc[mode][name],
edit = prompt(`settings for "${name}"`, JSON.stringify(load));
if (edit) {
try {
settings.sproc[mode][name] = JSON.parse(edit);
2019-07-23 21:12:40 -04:00
if (name === settings.process.processName) {
2022-02-17 14:40:51 -05:00
api.conf.load(null, name);
2019-07-23 21:12:40 -04:00
}
2022-02-17 14:40:51 -05:00
api.conf.save();
} catch (e) {
UC.alert('malformed settings object');
}
}
}
function exportSettings(e) {
let mode = getMode(),
name = e.target.getAttribute("name"),
2022-02-17 14:40:51 -05:00
data = api.util.b64enc({
process: settings.sproc[mode][name],
2022-02-17 14:40:51 -05:00
version: kiri.version,
moto: moto.id,
time: Date.now(),
mode,
name
});
UC.prompt("Export Process Filename", name).then(name => {
if (name) {
2022-02-17 14:40:51 -05:00
api.util.download(data, `${name}.km`);
}
});
}
2020-03-29 20:23:48 -04:00
function loadSettings(e, named) {
2020-04-09 14:52:43 -04:00
let mode = getMode(),
name = e ? e.target.getAttribute("load") : named || currentProcessName() || "default",
2019-07-23 21:12:40 -04:00
load = settings.sproc[mode][name];
2020-06-12 23:06:37 -04:00
2017-04-18 17:09:33 -04:00
if (!load) return;
2019-07-23 21:12:40 -04:00
// cloning loaded process into settings requires user to save
// process before switching devices or risk losing any changes
2021-08-27 22:13:57 -04:00
settings.process = clone(load);
2020-06-12 23:06:37 -04:00
// update process name
2017-04-18 17:09:33 -04:00
settings.process.processName = name;
2020-06-12 23:06:37 -04:00
// save named process with the current device
settings.devproc[currentDeviceName()] = name;
// preserve name of last library loaded
if (name !== 'default') {
settings.cproc[mode] = name;
}
2020-06-12 23:06:37 -04:00
// allow mode driver to take any necessary actions
2022-02-17 14:40:51 -05:00
api.event.emit("settings.load", settings);
2020-06-12 23:06:37 -04:00
// update UI fields to reflect current settings
2020-04-09 14:52:43 -04:00
updateFields();
2022-02-17 14:40:51 -05:00
api.conf.update();
2020-06-12 23:06:37 -04:00
2017-04-18 17:09:33 -04:00
if (e) triggerSettingsEvent();
}
2020-03-29 20:23:48 -04:00
function deleteSettings(e) {
2020-04-09 14:52:43 -04:00
let name = e.target.getAttribute("del");
2017-04-18 17:09:33 -04:00
delete settings.sproc[getMode()][name];
updateSettingsList();
2022-02-17 14:40:51 -05:00
api.conf.save();
2017-04-18 17:09:33 -04:00
triggerSettingsEvent();
}
function updateSettingsList() {
2020-04-09 14:52:43 -04:00
let list = [], s = settings, sp = s.sproc[getMode()] || {}, table = UI.settingsList;
2017-04-18 17:09:33 -04:00
table.innerHTML = '';
2020-04-09 14:52:43 -04:00
for (let k in sp) {
2017-04-18 17:09:33 -04:00
if (sp.hasOwnProperty(k)) list.push(k);
}
list.filter(n => n !=='default').sort().forEach(function(sk) {
2020-04-09 14:52:43 -04:00
let row = DOC.createElement('div'),
2017-04-18 17:09:33 -04:00
load = DOC.createElement('button'),
edit = DOC.createElement('button'),
xprt = DOC.createElement('button'),
2017-04-18 17:09:33 -04:00
del = DOC.createElement('button'),
name = sk;
load.setAttribute('load', sk);
load.onclick = (ev) => {
2022-02-17 14:40:51 -05:00
api.conf.load(undefined, sk);
updateSettingsList();
hideModal();
}
2017-04-18 17:09:33 -04:00
load.appendChild(DOC.createTextNode(sk));
2017-12-14 20:41:19 -05:00
if (sk == settings.process.processName) {
load.setAttribute('class', 'selected')
}
UI.settingsName.value = settings.process.processName;
2017-04-18 17:09:33 -04:00
del.setAttribute('del', sk);
del.setAttribute('title', "remove '"+sk+"'");
del.innerHTML = '<i class="far fa-trash-alt"></i>';
2020-03-29 20:23:48 -04:00
del.onclick = deleteSettings;
2017-04-18 17:09:33 -04:00
edit.setAttribute('name', sk);
edit.setAttribute('title', 'edit');
edit.innerHTML = '<i class="far fa-edit"></i>';
2020-03-29 20:23:48 -04:00
edit.onclick = editSettings;
xprt.setAttribute('name', sk);
xprt.setAttribute('title', 'export');
xprt.innerHTML = '<i class="fas fa-download"></i>';
xprt.onclick = exportSettings;
2017-04-18 17:09:33 -04:00
row.setAttribute("class", "flow-row");
row.appendChild(edit);
2017-04-18 17:09:33 -04:00
row.appendChild(load);
row.appendChild(xprt);
2017-04-18 17:09:33 -04:00
row.appendChild(del);
table.appendChild(row);
});
}
function showSettings() {
updateSettingsList();
2020-06-07 23:02:05 -04:00
showModal("saves");
UI.settingsName.focus();
2017-04-18 17:09:33 -04:00
}
2020-03-09 11:02:09 -04:00
function showHelp() {
2021-10-26 19:35:57 -04:00
showHelpFile(`local`,() => {});
2017-04-18 17:09:33 -04:00
}
2020-11-16 15:04:58 -05:00
function showHelpFile(local,then) {
2017-04-18 17:09:33 -04:00
if (!local) {
2021-10-26 19:35:57 -04:00
WIN.open("//docs.grid.space/", "_help");
2017-04-18 17:09:33 -04:00
return;
}
2022-02-17 14:40:51 -05:00
$('kiri-version').innerHTML = `${LANG.version} ${kiri.version}`;
2021-10-26 19:35:57 -04:00
showModal('help');
2022-02-17 14:40:51 -05:00
api.event.emit('help.show', local);
2017-04-18 17:09:33 -04:00
}
function showLocal() {
showModal('local');
2022-02-17 14:40:51 -05:00
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>`);
2021-06-02 17:14:07 -04:00
let recs = [];
for (let k in data) {
2021-06-02 17:14:07 -04:00
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}/`);
};
});
});
}
2017-04-18 17:09:33 -04:00
function setViewMode(mode) {
2020-11-11 08:13:40 -05:00
const oldMode = viewMode;
const isCAM = settings.mode === 'CAM';
2017-04-18 17:09:33 -04:00
viewMode = mode;
platform.deselect();
2022-02-18 13:27:26 -05:00
selection.update_info();
// disable clear in non-arrange modes
$('view-clear').style.display = mode === VIEWS.ARRANGE ? '' : 'none';
2017-04-18 17:09:33 -04:00
switch (mode) {
case VIEWS.ARRANGE:
2020-11-16 09:17:02 -05:00
complete = {};
2020-11-11 08:13:40 -05:00
UI.back.style.display = '';
UI.render.style.display = '';
2022-02-17 14:40:51 -05:00
kiri.client.clear();
2020-11-08 17:35:40 -05:00
STACKS.clear();
2020-06-07 23:02:05 -04:00
hideSlider();
updateSpeeds();
2020-11-09 00:21:35 -05:00
setVisibleLayer();
setWidgetVisibility(true);
2020-11-08 17:35:40 -05:00
setOpacity(1);
2017-04-18 17:09:33 -04:00
break;
case VIEWS.SLICE:
2020-11-11 08:13:40 -05:00
UI.back.style.display = 'flex';
if (!isCAM) UI.render.classList.remove('lt-enabled');
updateSpeeds();
2017-04-18 17:09:33 -04:00
updateSliderMax();
setWidgetVisibility(true);
2017-04-18 17:09:33 -04:00
break;
case VIEWS.PREVIEW:
2020-11-11 08:13:40 -05:00
UI.back.style.display = 'flex';
if (!isCAM) UI.render.classList.remove('lt-enabled');
setWidgetVisibility(true);
2017-04-18 17:09:33 -04:00
break;
default:
2021-07-04 21:05:46 -04:00
console.log("invalid view mode: "+mode);
2017-04-18 17:09:33 -04:00
return;
}
2022-02-17 14:40:51 -05:00
api.event.emit('view.set', mode);
DOC.activeElement.blur();
2017-04-18 17:09:33 -04:00
}
function getMode() {
return settings.mode;
}
2020-03-07 13:14:22 -05:00
function getModeLower() {
return getMode().toLowerCase();
}
2020-02-08 21:01:00 -05:00
function switchMode(mode) {
setMode(mode, platform.update_size);
2020-02-08 21:01:00 -05:00
}
2017-04-18 17:09:33 -04:00
function setMode(mode, lock, then) {
if (!MODES[mode]) {
2021-07-04 21:05:46 -04:00
console.log("invalid mode: "+mode);
2017-04-18 17:09:33 -04:00
mode = 'FDM';
}
// change mode constants
2017-04-18 17:09:33 -04:00
settings.mode = mode;
2020-06-09 18:12:59 -04:00
MODE = MODES[mode];
2022-02-17 14:40:51 -05:00
DRIVER = kiri.driver[mode];
// update mode display
2021-01-12 19:04:56 -05:00
$('app-mode-name').innerHTML = mode === 'CAM' ? 'CNC' : mode;
// highlight relevant device mode button
["fdm","sla","cam","laser"].forEach(dev => {
let cl = $(`mode-${dev}`).classList;
if (dev === mode.toLowerCase()) {
cl.add("dev-sel");
} else {
cl.remove("dev-sel");
}
});
2017-04-18 17:09:33 -04:00
// restore cached device profile for this mode
if (settings.cdev[mode]) {
settings.device = clone(settings.cdev[mode]);
2022-02-17 14:40:51 -05:00
api.event.emit('device.set', currentDeviceName());
}
// really belongs in CAM driver (lots of work / abstraction needed)
2020-11-14 11:12:12 -05:00
// updateStockVisibility();
2020-03-17 16:32:43 -04:00
// updates right-hand menu by enabling/disabling fields
setViewMode(VIEWS.ARRANGE);
2017-04-18 17:09:33 -04:00
UC.setMode(MODE);
// sanitize and persist settings
2022-02-17 14:40:51 -05:00
api.conf.load();
api.conf.save();
// other housekeeping
2017-04-18 17:09:33 -04:00
triggerSettingsEvent();
2022-02-17 21:52:54 -05:00
platform.update_selected();
2022-02-18 13:27:26 -05:00
selection.update_bounds(WIDGETS);
2020-04-11 16:33:10 -04:00
updateFields();
// because device dialog, if showing, needs to be updated
if (modalShowing()) {
2022-02-17 14:40:51 -05:00
api.show.devices();
}
2022-02-17 14:40:51 -05:00
api.space.restore(null, true);
api.event.emit("mode.set", mode);
if (then) {
then();
}
2017-04-18 17:09:33 -04:00
}
function currentDeviceName() {
return settings.filter[getMode()];
}
function currentDeviceCode() {
return settings.devices[currentDeviceName()];
}
function currentProcessName() {
return settings.cproc[getMode()];
}
function currentProcessCode() {
return settings.sproc[getMode()][currentProcessName()];
}
2017-04-18 17:09:33 -04:00
function setControlsVisible(show) {
2020-06-07 23:02:05 -04:00
$('mid-lcol').style.display = show ? 'flex' : 'none';
$('mid-rcol').style.display = show ? 'flex' : 'none';
2017-04-18 17:09:33 -04:00
}
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();
}
2017-04-18 17:09:33 -04:00
// prevent safari from exiting full screen mode
2020-03-09 11:02:09 -04:00
DOC.onkeydown = function (evt) { if (evt.keyCode == 27) evt.preventDefault() }
2017-04-18 17:09:33 -04:00
// complete module loading
kiri.load_exec();
2021-02-27 17:26:24 -05:00
// upon restore, seed presets
2022-02-17 14:40:51 -05:00
api.event.emit('preset', api.conf.dbo());
2017-04-18 17:09:33 -04:00
})();