stepwise refinement of init, remove / move main funcs

This commit is contained in:
Stewart Allen 2025-12-25 00:32:55 -05:00
commit 255da52a68
12 changed files with 1127 additions and 1014 deletions

View file

@ -21,8 +21,8 @@ let nextId = 0;
function newWidget(id,group) { return new Widget(id,group) }
// todo: fix ui-only path thus this unorthodox binding vs importing api
function catalog() { return self.kiri_catalog };
function index() { return self.kiri_catalog.index }
class Widget {

View file

@ -1,80 +1,90 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import '../../ext/base64.js';
import alerts from './alerts.js';
import settings from './config/manager.js';
import STACKS from './stacks.js';
import { visuals } from './visuals.js';
import web from '../../moto/webui.js';
import { beta, version } from '../../moto/license.js';
import { broker } from '../../moto/broker.js';
import { client as work } from './client.js';
import { consts, COLOR as color, LISTS as lists } from '../core/consts.js';
import { device, devices } from './devices.js';
import { catalog, dialog, event, group, help, hide, image } from './main.js';
import { modal, mode, process, show, space, util, view } from './main.js';
import { local as dataLocal } from '../../data/local.js';
import { noop, ajax, o2js, js2o } from '../core/utils.js';
import { functions } from './function.js';
import { group as groupModule } from './groups.js';
import { help as helpModule } from './help.js';
import { image as imageModule } from './image.js';
import { Index } from '../../data/index.js';
import { LANG } from './lang.js';
import { local } from './local-storage.js';
import { local as dataLocal } from '../../data/local.js';
import { modal } from './modal.js';
import { mode as modeModule, process as processModule } from './mode.js';
import { newWidget } from '../core/widget.js';
import { noop, ajax, o2js, js2o, utils } from '../core/utils.js';
import { openFiles } from './files.js';
import { platform } from './platform.js';
import { selection } from './select.js';
import { stats } from './stats.js';
import { newWidget } from '../core/widget.js';
import { widgets } from '../core/widgets.js';
import { updateTool } from '../mode/cam/tools.js';
import { beta, version } from '../../moto/license.js';
import { settingsUI } from './config/dialog.js';
import { showDevices } from './devices.js';
import { showTools } from '../mode/cam/tools.js';
import { space as SPACE } from '../../moto/space.js';
import { stats } from './stats.js';
import { types as load } from '../../load/file.js';
import { LANG } from './lang.js';
import { LOCAL, SETUP, SECURE } from './main.js';
import { UI } from './component.js';
import { updateTool } from '../mode/cam/tools.js';
import { util as utilModule } from './util.js';
import { view as viewModule } from './view-state.js';
import { visuals } from './visuals.js';
import { widgets } from '../core/widgets.js';
import { workspace } from './workspace.js';
import web from '../../moto/webui.js';
// From main.js - environment setup
let LOC = self.location,
SETUP = utils.parseOpt(LOC.search.substring(1)),
SECURE = utilModule.isSecure(LOC.protocol),
LOCAL = self.debug && !SETUP.remote,
EVENT = broker,
FILES = openFiles(new Index(SETUP.d ? SETUP.d[0] : 'kiri'));
let UC = UI.prefix('kiri').inputAction(settings.conf.update),
und = undefined,
clone = Object.clone,
// todo: fix in widget.js b/c front-end and back-end do not share api
self.kiri_catalog = FILES;
FILES.show = () => modal.show('files');
// Broker compatibility patch
EVENT.on = (topic, listener) => {
EVENT.subscribe(topic, listener);
return EVENT;
};
let busyVal = 0,
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: und, // function to limit devices shown
drop_group: und, // optional array to group multi drop
drop_layout: true, // layout on new drop
hoverAdds: false, // when true only searches widget additions
on_key: und, // function override default key handlers
on_key2: [], // allows for multiple key handlers
on_load: und, // function override file drop loads
on_add_stl: und, // legacy override stl drop loads
on_mouse_up: und, // function intercepts mouse up select
on_mouse_down: und, // 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;
broker.publish("feature.hover", b);
}
},
busyVal = 0,
busy = {
undef = undefined;
// the big kahuna
export const api = {
ajax,
beta,
alerts,
busy: {
val() { return busyVal },
inc() { api.event.emit("busy", ++busyVal) },
dec() { api.event.emit("busy", --busyVal) }
},
onkey = (fn) => {
api.feature.on_key2.push(fn);
catalog: FILES,
client: work,
clip(text) {
navigator.clipboard
.writeText(text)
.catch(err => console.error('Clipboard Error:', err));
},
doit = {
undo: noop, // do.js
redo: noop // do.js
},
devel = {
clone: Object.clone,
color,
conf: settings.conf,
const: { LANG, LOCAL, SETUP, SECURE, STACKS, ...consts },
devel: {
get enabled() {
return settings.ctrl().devel;
},
@ -88,110 +98,128 @@ let UC = UI.prefix('kiri').inputAction(settings.conf.update),
api.function.slice();
}
},
local = {
get: (key) => localGet(key),
getItem: (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),
setItem: (key, val) => localSet(key, val),
removeItem: (key) => localRemove(key)
},
tweak = {
line_precision(v) { api.work.config({ base: { clipperClean: v } }) },
gcode_decimals(v) { api.work.config({ base: { gcode_decimals: v } }) }
};
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;
}
function localRemove(key) {
let sloc = api.conf.get().local;
return delete sloc[key];
}
export const api = {
ajax,
beta,
alerts,
busy,
catalog,
client: work,
clip,
clone,
color,
conf: settings.conf,
const: { LANG, LOCAL, SETUP, SECURE, STACKS, ...consts },
devel,
device,
devices,
dialog,
doit,
event,
dialog: {
show: (which) => modal.show(which),
hide: () => modal.hide(),
update_process_list: settingsUI.update_list
},
doit: {
undo: noop, // do.js
redo: noop // do.js
},
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) { alerts.update(clr) },
import: utilModule.loadFile,
settings: settingsUI.trigger_event
},
electron: navigator.userAgent.includes('Electron'),
feature,
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: undef, // function to limit devices shown
drop_group: undef, // optional array to group multi drop
drop_layout: true, // layout on new drop
hoverAdds: false, // when true only searches widget additions
on_key: undef, // function override default key handlers
on_key2: [], // allows for multiple key handlers
on_load: undef, // function override file drop loads
on_add_stl: undef, // legacy override stl drop loads
on_mouse_up: undef, // function intercepts mouse up select
on_mouse_down: undef, // 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;
broker.publish("feature.hover", b);
}
},
function: functions,
group,
help,
hide,
image,
group: groupModule,
help: helpModule,
hide: {
alert(rec, recs) { alerts.hide(...arguments) },
import: noop,
slider: visuals.hide_slider
},
image: imageModule,
js2o,
language: LANG,
lists,
load,
local,
LOCAL,
modal,
mode,
mode: modeModule,
new: {
widget: newWidget
},
noop,
o2js,
onkey,
onkey(fn) {
api.feature.on_key2.push(fn);
},
platform,
process,
process: processModule,
sdb: dataLocal,
SECURE,
selection,
settings,
show,
space,
SETUP,
show: {
alert() { return alerts.show(...arguments) },
controls() { console.trace('deprecated') },
devices: showDevices,
import() { api.ui.import.style.display = '' },
layer: visuals.set_visible_layer,
local() { console.trace('deprecated') },
progress: utilModule.setProgress,
slices: visuals.show_slices,
tools: showTools
},
space: workspace,
SPACE,
stacks: STACKS,
stats,
tool: {
update: updateTool
},
tweak,
uc: UC,
ui: {},
util,
var: {
layer_lo: 0,
layer_hi: 0,
layer_max: 0
tweak: {
line_precision(v) { api.work.config({ base: { clipperClean: v } }) },
gcode_decimals(v) { api.work.config({ base: { gcode_decimals: v } }) }
},
uc: UI.prefix('kiri').inputAction(settings.conf.update),
ui: {},
util: {
isSecure: utilModule.isSecure,
download: utilModule.download,
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))) }
},
// var: {
// layer_lo: 0,
// layer_hi: 0,
// layer_max: 0
// },
version,
view,
view: {
...viewModule,
...visuals,
snapshot: null
},
visuals,
web,
widgets,

View file

@ -0,0 +1,123 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { api } from '../api.js';
import { modal } from '../modal.js';
import { settings } from './manager.js';
import { version } from '../../../moto/license.js';
const DOC = self.document;
function triggerSettingsEvent() {
api.event.emit('settings', settings.get());
}
function editSettings(e) {
let current = settings.get(),
mode = settings.mode(),
name = e.target.getAttribute("name"),
load = current.sproc[mode][name],
loadstr = JSON.stringify(load,null,4).split('\n');
api.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 });
api.uc.alert('malformed settings object');
}
}
});
}
function exportSettings(e) {
let current = settings.get(),
mode = settings.mode(),
name = e.target.getAttribute("name"),
data = api.util.b64enc({
process: current.sproc[mode][name],
version,
moto: moto.id,
time: Date.now(),
mode,
name
});
api.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[settings.mode()][name];
api.settings.sync.put();
updateProcessList();
api.conf.save();
triggerSettingsEvent();
}
function updateProcessList() {
let current = settings.get();
let list = [], s = current, sp = s.sproc[settings.mode()] || {}, table = api.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();
modal.hide();
}
load.appendChild(DOC.createTextNode(sk));
if (sk == settings.proc().processName) {
load.setAttribute('class', 'selected')
}
api.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);
});
}
export const settingsUI = {
edit: editSettings,
export: exportSettings,
delete: deleteSettings,
update_list: updateProcessList,
trigger_event: triggerSettingsEvent
};

129
src/kiri/ui/file-ops.js Normal file
View file

@ -0,0 +1,129 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { api } from './api.js';
import { base } from '../../geo/base.js';
import { space } from '../../moto/space.js';
const DOC = self.document;
let platformColor;
function dragOverHandler(evt) {
evt.stopPropagation();
evt.preventDefault();
// prevent drop actions when a dialog is open
if (api.modal.visible()) {
return;
}
evt.dataTransfer.dropEffect = 'copy';
let oldcolor = space.platform.setColor(0x00ff00);
if (oldcolor !== 0x00ff00) platformColor = oldcolor;
}
function dragLeave() {
space.platform.setColor(platformColor);
}
function dropHandler(evt) {
evt.stopPropagation();
evt.preventDefault();
// prevent drop actions when a dialog is open
if (api.modal.visible()) {
return;
}
space.platform.setColor(platformColor);
let files = evt.dataTransfer.files;
switch (api.feature.drop_group) {
case true:
return api.platform.load_files(files, []);
case false:
return api.platform.load_files(files, undefined);
}
if (files.length === 1) {
api.platform.load_files(files);
} else if (files.length > 1) {
api.uc.confirm(`group ${files.length} files?`).then(yes => {
api.platform.load_files(files, yes ? [] : undefined);
});
}
}
function loadCatalogFile(e) {
api.widgets.load(e.target.getAttribute('load'), function(widget) {
api.platform.add(widget);
api.dialog.hide();
});
}
function updateCatalog(files) {
let table = api.ui.catalogList,
list = [];
table.innerHTML = '';
for (let name in files) {
list.push({n:name, ln:name.toLowerCase(), v:files[name].vertices, t:files[name].updated});
}
list.sort(function(a,b) {
return a.ln < b.ln ? -1 : 1;
});
for (let i=0; i<list.length; i++) {
let row = DOC.createElement('div'),
renm = DOC.createElement('button'),
load = DOC.createElement('button'),
size = DOC.createElement('button'),
del = DOC.createElement('button'),
file = list[i],
name = file.n,
date = new Date(file.t),
split = name.split('.'),
short = split[0],
ext = split[1] ? `.${split[1]}` : '';
renm.setAttribute('class', 'rename');
renm.setAttribute('title', 'rename file');
renm.innerHTML = '<i class="far fa-edit"></i>';
renm.onclick = () => {
api.uc.prompt(`rename file`, short).then(newname => {
if (newname && newname !== short) {
api.catalog.rename(name, `${newname}${ext}`, then => {
api.modal.show('files');
});
}
});
};
load.setAttribute('load', name);
load.setAttribute('title', `file: ${name}\nvertices: ${file.v}\ndate: ${date}`);
load.onclick = loadCatalogFile;
load.appendChild(DOC.createTextNode(short));
del.setAttribute('del', name);
del.setAttribute('title', "remove '"+name+"'");
del.onclick = () => { api.catalog.deleteFile(name) };
del.innerHTML = '<i class="far fa-trash-alt"></i>';
size.setAttribute("disabled", true);
size.setAttribute("class", "label");
size.appendChild(DOC.createTextNode(base.util.comma(file.v)));
row.setAttribute("class", "f-row a-center");
row.appendChild(renm);
row.appendChild(load);
row.appendChild(size);
row.appendChild(del);
table.appendChild(row);
}
}
export const fileOps = {
dragOverHandler,
dragLeave,
dropHandler,
loadCatalogFile,
updateCatalog
};

View file

@ -1,19 +1,9 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { $, $c } from '../../moto/webui.js';
import { $ } from '../../moto/webui.js';
import { api } from './api.js';
import { base } from '../../geo/base.js';
import { local as sdb } from '../../data/local.js';
import { slider } from './slider.js';
import { keyboard } from './keyboard.js';
import { modal } from './modal.js';
import { interact } from './interact.js';
import { space } from '../../moto/space.js';
import { LOCAL, SETUP } from './main.js';
import { VIEWS, MODES, SEED } from '../core/consts.js';
import { beta, version } from '../../moto/license.js';
import { settings as set_ctrl } from './config/manager.js';
import STACKS from './stacks.js';
import { fileOps } from './file-ops.js';
import { init as initCAM } from '../mode/cam/init-ui.js';
import { init as initDRAG } from '../mode/drag/init-ui.js';
import { init as initFDM } from '../mode/fdm/init-ui.js';
@ -21,12 +11,27 @@ import { init as initLaser } from '../mode/laser/init-ui.js';
import { init as initSLA } from '../mode/sla/init-ui.js';
import { init as initWEDM } from '../mode/wedm/init-ui.js';
import { init as initWJET } from '../mode/wjet/init-ui.js';
import { interact } from './interact.js';
import { keyboard } from './keyboard.js';
import { local as sdb } from '../../data/local.js';
import { menu as menuCAM } from '../mode/cam/init-menu.js';
import { menu as menuFDM } from '../mode/fdm/init-menu.js';
import { menu as menuLaser } from '../mode/laser/init-menu.js';
import { menu as menuSLA } from '../mode/sla/init-menu.js';
import { modal } from './modal.js';
import { navigation } from './navigation.js';
import { preferences } from './preferences.js';
import { selectionTools } from './selection-tools.js';
import { settings as set_ctrl } from './config/manager.js';
import { settingsOps } from './settings-ops.js';
import { slider } from './slider.js';
import { space } from '../../moto/space.js';
import { VIEWS, MODES, SEED } from '../core/consts.js';
let { CAM, SLA, FDM, LASER, DRAG, WJET, WEDM } = MODES,
import STACKS from './stacks.js';
let { LOCAL, SETUP } = api,
{ CAM, SLA, FDM, LASER, DRAG, WJET, WEDM } = MODES,
{ client, catalog, platform, selection, stats } = api,
LANG = api.language.current,
WIN = self.window,
@ -80,135 +85,11 @@ function checkSeed(then) {
return false;
}
function unitsSave() {
api.conf.update({ controller: true });
platform.update_size();
}
function aniMeshSave() {
api.conf.update({ controller: true });
api.conf.save();
}
function lineTypeSave() {
const sel = ui.lineType.options[ui.lineType.selectedIndex];
if (sel) {
settings().controller.lineType = sel.value;
api.conf.save();
}
}
function detailSave() {
let level = ui.detail.options[ui.detail.selectedIndex];
if (level) {
level = level.value;
let rez = base.config.clipperClean;
switch (level) {
case '100': rez = 50; break;
case '75': rez = base.config.clipperClean; break;
case '50': rez = 500; break;
case '25': rez = 1000; break;
}
client.config({
base: { clipperClean: rez }
});
settings().controller.detail = level;
api.conf.save();
}
}
function speedSave() {
settings().controller.showSpeeds = ui.showSpeeds.checked;
api.view.update_speeds();
}
function setThreaded(bool) {
if (bool) {
client.pool.start();
} else {
client.pool.stop();
}
return bool;
}
// upon restore, seed presets
api.event.emit('preset', api.conf.dbo());
// api.event.on("set.threaded", bool => setThreaded(bool));
function booleanSave() {
let control = settings().controller;
if (control.assembly != ui.assembly.checked) {
client.wasm(ui.assembly.checked);
}
if (control.antiAlias != ui.antiAlias.checked) {
api.show.alert('Page Reload Required to Change Aliasing');
}
control.antiAlias = ui.antiAlias.checked;
control.assembly = ui.assembly.checked;
control.autoLayout = ui.autoLayout.checked;
control.autoSave = ui.autoSave.checked;
control.dark = ui.dark.checked;
control.devel = ui.devel.checked;
control.drawer = ui.drawer.checked;
control.exportOcto = ui.exportOcto.checked;
control.exportPreview = ui.exportPreview.checked;
control.exportThumb = ui.exportThumb.checked;
control.freeLayout = ui.freeLayout.checked;
control.healMesh = ui.healMesh.checked;
control.ortho = ui.ortho.checked;
control.manifold = ui.manifold.checked;
control.reverseZoom = ui.reverseZoom.checked;
control.scrolls = ui.scrolls.checked;
control.shiny = ui.shiny.checked;
control.showOrigin = ui.showOrigin.checked;
control.showRulers = ui.showRulers.checked;
control.spaceRandoX = ui.spaceRandoX.checked;
// control.threaded = setThreaded(ui.threaded.checked);
control.webGPU = ui.webGPU.checked;
space.view.setZoom(control.reverseZoom, control.zoomSpeed);
// platform.layout();
api.conf.save();
api.platform.update_size();
updateStats();
updateDrawer();
api.event.emit('boolean.update');
space.view.setProjection(control.ortho ? 'orthographic' : 'perspective');
}
function updateDrawer() {
const { drawer, scrolls } = settings().controller;
$c('app', drawer ? 'slideshow' : '', drawer ? '' : 'slideshow');
$c('app', scrolls ? '' : 'hide-scroll', scrolls ? 'hide-scroll' : '');
}
function updateStats() {
if (self.debug !== true) {
return;
}
console.log('updateStats()');
let { div, fps, rms, rnfo } = ui.stats;
div.style.display = 'flex';
clearTimeout(statsTimer);
statsTimer = setInterval(() => {
const nrms = space.view.getRMS().toFixed(1);
const nfps = space.view.getFPS().toFixed(1);
const rend = space.renderInfo();
const { memory, render } = rend;
if (nfps !== fps.innerText) {
fps.innerText = nfps;
}
if (nrms !== rms.innerText) {
rms.innerText = nrms;
}
if (rnfo.offsetParent !== null) {
rnfo.innerHTML = Object.entries({ ...memory, ...render, render_ms: nrms, frames_sec: nfps }).map(row => {
return `<div>${row[0]}</div><label>${base.util.comma(row[1])}</label>`
}).join('');
}
}, 100);
}
function onBooleanClick(el) {
// copy some ui elements to target settings
let settings = api.conf.get();
@ -239,7 +120,6 @@ function onButtonClick(ev) {
api.event.on('click.button', onButtonClick);
function keys(o) {
let key, list = [];
for (key in o) { if (o.hasOwnProperty(key)) list.push(key) }
@ -252,277 +132,6 @@ function clearSelected(children) {
}
}
function rotateInputSelection() {
if (selection.meshes().length === 0) {
api.show.alert("select object to rotate");
return;
}
api.uc.prompt("Enter X,Y,Z degrees of rotation","").then(coord => {
coord = (coord || '').split(',');
let prod = Math.PI / 180,
x = parseFloat(coord[0] || 0.0) * prod,
y = parseFloat(coord[1] || 0.0) * prod,
z = parseFloat(coord[2] || 0.0) * prod;
selection.rotate(x, y, z);
});
}
function positionSelection() {
if (selection.meshes().length === 0) {
api.show.alert("select object to position");
return;
}
let current = settings(),
{ device, process} = current,
center = process.ctOriginCenter || process.camOriginCenter || device.bedRound || device.originCenter,
bounds = boundsSelection();
api.uc.prompt("Enter X,Y coordinates for selection","").then(coord => {
coord = (coord || '').split(',');
let x = parseFloat(coord[0] || 0.0),
y = parseFloat(coord[1] || 0.0),
z = parseFloat(coord[2] || 0.0);
if (!center) {
x = x - device.bedWidth/2 + (bounds.max.x - bounds.min.x)/2;
y = y - device.bedDepth/2 + (bounds.max.y - bounds.min.y)/2
}
selection.move(x, y, z, true);
});
}
function deviceExport(exp, name) {
name = (name || "device")
.toLowerCase()
.replace(/ /g,'_')
.replace(/\./g,'_');
uc.prompt("Export Device Filename", name).then(name => {
if (name) {
api.util.download(exp, `${name}.km`);
}
});
}
function objectsExport(format = "stl") {
// return selection.export();
uc.confirm("Export Filename", {ok:true, cancel: false}, `selected.${format}`).then(name => {
if (!name) return;
if (name.toLowerCase().indexOf(`.${format}`) < 0) {
name = `${name}.${format}`;
}
api.util.download(selection.export(format), name);
});
}
function workspaceNew() {
uc.confirm("Clear Workspace?", {ok:true, cancel: false}).then(value => {
if (value === true) {
let proc = set_ctrl.proc();
proc.ops && (proc.ops.length = 0);
proc.op2 && (proc.op2.length = 0);
api.platform.clear();
}
});
}
function profileExport() {
const opt = {pre: [
"<div class='f-col a-center gap5 mlr10'>",
" <h3>Workspace Export</h3>",
" <label>This will create a backup of your</label>",
" <label>workspace, devices, and settings</label>",
" <span class='mt10'><input id='excwork' type='checkbox'>&nbsp;Exclude meshes</span>",
"</div>"
]};
let suggestion = "workspace";
let file = api.widgets.all()[0]?.meta.file || '';
if (file) {
suggestion = `${suggestion}_${file.split('.')[0]}`.replaceAll(' ','_');
};
uc.confirm("Filename", {ok:true, cancel: false}, suggestion, opt).then(name => {
if (!name) return;
let work = !$('excwork').checked;
let json = api.conf.export({work, clear:true});
client.zip([
{name:"workspace.json", data:JSON.stringify(json)}
], progress => {
api.show.progress(progress.percent/100, "compressing workspace");
}, output => {
api.show.progress(0);
api.util.download(output, `${name}.kmz`);
});
});
}
function settingsSave(ev, name) {
if (ev) {
ev.stopPropagation();
ev.preventDefault();
}
api.dialog.hide();
let mode = api.mode.get(),
s = settings(),
def = "default",
cp = s.process,
pl = s.sproc[mode],
lp = s.cproc[mode],
saveAs = (name) => {
if (!name) {
return;
}
let np = pl[name] = {};
cp.processName = name;
pl[name] = Object.clone(cp);
for (let k in cp) {
if (!cp.hasOwnProperty(k)) continue;
np[k] = cp[k];
}
s.cproc[mode] = name;
s.devproc[s.device.deviceName] = name;
api.conf.save();
api.conf.update();
api.event.settings();
sync_put();
};
if (name) {
saveAs(name);
} else {
uc.prompt("Save Settings As", cp ? lp || def : def).then(saveAs);
}
}
function settingsLoad() {
api.conf.show();
}
function updateDeviceSize() {
api.conf.update();
platform.update_size();
platform.update_origin();
}
async function sync_put() {
await set_ctrl.sync.put();
}
function dragOverHandler(evt) {
evt.stopPropagation();
evt.preventDefault();
// prevent drop actions when a dialog is open
if (api.modal.visible()) {
return;
}
evt.dataTransfer.dropEffect = 'copy';
let oldcolor = space.platform.setColor(0x00ff00);
if (oldcolor !== 0x00ff00) platformColor = oldcolor;
}
function dragLeave() {
space.platform.setColor(platformColor);
}
function dropHandler(evt) {
evt.stopPropagation();
evt.preventDefault();
// prevent drop actions when a dialog is open
if (api.modal.visible()) {
return;
}
space.platform.setColor(platformColor);
let files = evt.dataTransfer.files;
switch (api.feature.drop_group) {
case true:
return api.platform.load_files(files, []);
case false:
return api.platform.load_files(files, undefined);
}
if (files.length === 1) {
api.platform.load_files(files);
} else if (files.length > 1) {
uc.confirm(`group ${files.length} files?`).then(yes => {
api.platform.load_files(files, yes ? [] : undefined);
});
}
}
function loadCatalogFile(e) {
api.widgets.load(e.target.getAttribute('load'), function(widget) {
platform.add(widget);
api.dialog.hide();
});
}
function updateCatalog(files) {
let table = ui.catalogList,
list = [];
table.innerHTML = '';
for (let name in files) {
list.push({n:name, ln:name.toLowerCase(), v:files[name].vertices, t:files[name].updated});
}
list.sort(function(a,b) {
return a.ln < b.ln ? -1 : 1;
});
for (let i=0; i<list.length; i++) {
let row = DOC.createElement('div'),
renm = DOC.createElement('button'),
load = DOC.createElement('button'),
size = DOC.createElement('button'),
del = DOC.createElement('button'),
file = list[i],
name = file.n,
date = new Date(file.t),
split = name.split('.'),
short = split[0],
ext = split[1] ? `.${split[1]}` : '';
renm.setAttribute('class', 'rename');
renm.setAttribute('title', 'rename file');
renm.innerHTML = '<i class="far fa-edit"></i>';
renm.onclick = () => {
api.uc.prompt(`rename file`, short).then(newname => {
if (newname && newname !== short) {
catalog.rename(name, `${newname}${ext}`, then => {
api.modal.show('files');
});
}
});
};
load.setAttribute('load', name);
load.setAttribute('title', `file: ${name}\nvertices: ${file.v}\ndate: ${date}`);
load.onclick = loadCatalogFile;
load.appendChild(DOC.createTextNode(short));
del.setAttribute('del', name);
del.setAttribute('title', "remove '"+name+"'");
del.onclick = () => { catalog.deleteFile(name) };
del.innerHTML = '<i class="far fa-trash-alt"></i>';
size.setAttribute("disabled", true);
size.setAttribute("class", "label");
size.appendChild(DOC.createTextNode(base.util.comma(file.v)));
row.setAttribute("class", "f-row a-center");
row.appendChild(renm);
row.appendChild(load);
row.appendChild(size);
row.appendChild(del);
table.appendChild(row);
}
}
function isBelt() {
return api.device.isBelt();
}
@ -600,10 +209,10 @@ function init_one() {
space.platform.onMove(conf.save);
space.platform.setRound(true);
space.useDefaultKeys(api.feature.on_key === undefined || api.feature.on_key_defaults);
updateDrawer();
preferences.updateDrawer();
// api augmentation with local functions
api.device.export = deviceExport;
api.device.export = settingsOps.deviceExport;
Object.assign(ui, {
tracker: tracker,
@ -726,9 +335,9 @@ function init_one() {
device: newGroup(LANG.dv_gr_dev, null, {group:"ddev", inline, class:"noshow"}),
_____: newGroup("workspace", null, {group:"dext", inline}),
bedWidth: newInput('X (width)', {title:LANG.dv_bedw_l, convert:toFloat, size:6, units, round:2, action:updateDeviceSize}),
bedDepth: newInput('Y (depth)', {title:LANG.dv_bedw_l, convert:toFloat, size:6, units, round:2, action:updateDeviceSize}),
maxHeight: newInput('Z (height)', {title:LANG.dv_bedw_l, convert:toFloat, size:6, units, round:2, action:updateDeviceSize}),
bedWidth: newInput('X (width)', {title:LANG.dv_bedw_l, convert:toFloat, size:6, units, round:2, action:settingsOps.updateDeviceSize}),
bedDepth: newInput('Y (depth)', {title:LANG.dv_bedw_l, convert:toFloat, size:6, units, round:2, action:settingsOps.updateDeviceSize}),
maxHeight: newInput('Z (height)', {title:LANG.dv_bedw_l, convert:toFloat, size:6, units, round:2, action:settingsOps.updateDeviceSize}),
resolutionX: newInput(LANG.dv_rezx_s, {title:LANG.dv_rezx_l, convert:toInt, size:6, modes:SLA}),
resolutionY: newInput(LANG.dv_rezy_s, {title:LANG.dv_rezy_l, convert:toInt, size:6, modes:SLA}),
_____: newDiv({ class: "f-col t-body t-inset", addto: $('dev-config'), set:true, modes:NO_WEDM }),
@ -786,39 +395,39 @@ function init_one() {
/** Preferences Menu */
_____: newGroup(LANG.op_menu, $('prefs-gen1'), {inline}),
antiAlias: newBoolean(LANG.op_anta_s, booleanSave, {title:LANG.op_anta_l}),
reverseZoom: newBoolean(LANG.op_invr_s, booleanSave, {title:LANG.op_invr_l}),
ortho: newBoolean(LANG.op_orth_s, booleanSave, {title:LANG.op_orth_l}),
dark: newBoolean(LANG.op_dark_s, booleanSave, {title:LANG.op_dark_l}),
drawer: newBoolean('slide out', booleanSave, {title:'slide out settings drawer'}),
scrolls: newBoolean('scrollbars', booleanSave, {title:'show scrollbars'}),
devel: newBoolean(LANG.op_devl_s, booleanSave, {title:LANG.op_devl_l}),
antiAlias: newBoolean(LANG.op_anta_s, preferences.booleanSave, {title:LANG.op_anta_l}),
reverseZoom: newBoolean(LANG.op_invr_s, preferences.booleanSave, {title:LANG.op_invr_l}),
ortho: newBoolean(LANG.op_orth_s, preferences.booleanSave, {title:LANG.op_orth_l}),
dark: newBoolean(LANG.op_dark_s, preferences.booleanSave, {title:LANG.op_dark_l}),
drawer: newBoolean('slide out', preferences.booleanSave, {title:'slide out settings drawer'}),
scrolls: newBoolean('scrollbars', preferences.booleanSave, {title:'show scrollbars'}),
devel: newBoolean(LANG.op_devl_s, preferences.booleanSave, {title:LANG.op_devl_l}),
_____: newGroup(LANG.op_disp, $('prefs-gen2'), {inline}),
showOrigin: newBoolean(LANG.op_shor_s, booleanSave, {title:LANG.op_shor_l}),
showRulers: newBoolean(LANG.op_shru_s, booleanSave, {title:LANG.op_shru_l}),
showSpeeds: newBoolean(LANG.op_sped_s, speedSave, {title:LANG.op_sped_l}),
shiny: newBoolean(LANG.op_shny_s, booleanSave, {title:LANG.op_shny_l, modes:FDM}),
lineType: newSelect(LANG.op_line_s, {title: LANG.op_line_l, action: lineTypeSave, modes:FDM}, "linetype"),
manifold: newBoolean(LANG.op_mani_s, booleanSave, {title: LANG.op_mani_l, modes:CAM}, "manifold"),
animesh: newSelect(LANG.op_anim_s, {title: LANG.op_anim_l, action: aniMeshSave, modes:CAM}, "animesh"),
units: newSelect(LANG.op_unit_s, {title: LANG.op_unit_l, action: unitsSave, modes:CAM}, "units"),
showOrigin: newBoolean(LANG.op_shor_s, preferences.booleanSave, {title:LANG.op_shor_l}),
showRulers: newBoolean(LANG.op_shru_s, preferences.booleanSave, {title:LANG.op_shru_l}),
showSpeeds: newBoolean(LANG.op_sped_s, preferences.speedSave, {title:LANG.op_sped_l}),
shiny: newBoolean(LANG.op_shny_s, preferences.booleanSave, {title:LANG.op_shny_l, modes:FDM}),
lineType: newSelect(LANG.op_line_s, {title: LANG.op_line_l, action: preferences.lineTypeSave, modes:FDM}, "linetype"),
manifold: newBoolean(LANG.op_mani_s, preferences.booleanSave, {title: LANG.op_mani_l, modes:CAM}, "manifold"),
animesh: newSelect(LANG.op_anim_s, {title: LANG.op_anim_l, action: preferences.aniMeshSave, modes:CAM}, "animesh"),
units: newSelect(LANG.op_unit_s, {title: LANG.op_unit_l, action: preferences.unitsSave, modes:CAM}, "units"),
edgeangle: newInput(LANG.op_spoa_s, {title:LANG.op_spoa_l, convert:toFloat, size:3}),
_____: newGroup(LANG.lo_menu, $('prefs-lay'), {inline}),
autoSave: newBoolean(LANG.op_save_s, booleanSave, {title:LANG.op_save_l}),
autoLayout: newBoolean(LANG.op_auto_s, booleanSave, {title:LANG.op_auto_l}),
freeLayout: newBoolean(LANG.op_free_s, booleanSave, {title:LANG.op_free_l}),
spaceRandoX: newBoolean(LANG.op_spcx_s, booleanSave, {title:LANG.op_spcx_l, show:isBelt}),
autoSave: newBoolean(LANG.op_save_s, preferences.booleanSave, {title:LANG.op_save_l}),
autoLayout: newBoolean(LANG.op_auto_s, preferences.booleanSave, {title:LANG.op_auto_l}),
freeLayout: newBoolean(LANG.op_free_s, preferences.booleanSave, {title:LANG.op_free_l}),
spaceRandoX: newBoolean(LANG.op_spcx_s, preferences.booleanSave, {title:LANG.op_spcx_l, show:isBelt}),
spaceLayout: newInput(LANG.op_spcr_s, {title:LANG.op_spcr_l, convert:toFloat, size:3, units}),
_____: newGroup(LANG.xp_menu, $('prefs-xpo'), {inline: true}),
exportOcto: newBoolean(`OctoPrint`, booleanSave, {title:LANG.op_exop_l}),
exportThumb: newBoolean(`Thumbnail`, booleanSave, {modes:FDM}),
exportPreview: newBoolean(`Code Preview`, booleanSave),
exportOcto: newBoolean(`OctoPrint`, preferences.booleanSave, {title:LANG.op_exop_l}),
exportThumb: newBoolean(`Thumbnail`, preferences.booleanSave, {modes:FDM}),
exportPreview: newBoolean(`Code Preview`, preferences.booleanSave),
_____: newGroup(LANG.pt_menu, $('prefs-prt'), {inline}),
detail: newSelect(LANG.pt_qual_s, {title: LANG.pt_qual_l, action: detailSave}, "detail"),
healMesh: newBoolean(LANG.pt_heal_s, booleanSave, {title: LANG.pt_heal_l}),
// threaded: newBoolean(LANG.pt_thrd_s, booleanSave, {title: LANG.pt_thrd_l, modes:THREED}),
assembly: newBoolean(LANG.pt_assy_s, booleanSave, {title: LANG.pt_assy_l, modes:THREED}),
webGPU: newBoolean(LANG.pt_wgpu_s, booleanSave, {title: LANG.pt_wgpu_l, modes:THREED}),
detail: newSelect(LANG.pt_qual_s, {title: LANG.pt_qual_l, action: preferences.detailSave}, "detail"),
healMesh: newBoolean(LANG.pt_heal_s, preferences.booleanSave, {title: LANG.pt_heal_l}),
// threaded: newBoolean(LANG.pt_thrd_s, preferences.booleanSave, {title: LANG.pt_thrd_l, modes:THREED}),
assembly: newBoolean(LANG.pt_assy_s, preferences.booleanSave, {title: LANG.pt_assy_l, modes:THREED}),
webGPU: newBoolean(LANG.pt_wgpu_s, preferences.booleanSave, {title: LANG.pt_wgpu_l, modes:THREED}),
prefadd: uc.checkpoint($('prefs-add')),
@ -853,7 +462,7 @@ function init_one() {
// override old style settings two-button menu
ui.settingsSave.onclick = () => {
settingsSave(undefined, ui.settingsName.value);
settingsOps.settingsSave(undefined, ui.settingsName.value);
};
// initialize modal controller
@ -918,8 +527,8 @@ function init_one() {
VIEWS,
DOC,
WIN,
rotateInputSelection,
settingsLoad
rotateInputSelection: selectionTools.rotateInputSelection,
settingsLoad: settingsOps.settingsLoad
});
// expose keyboard to API
@ -983,97 +592,13 @@ function init_one() {
};
space.event.addHandlers(self, [
'dragover', dragOverHandler,
'dragleave', dragLeave,
'drop', dropHandler
'dragover', fileOps.dragOverHandler,
'dragleave', fileOps.dragLeave,
'drop', fileOps.dropHandler
]);
function selectionSize(e) {
let dv = parseFloat(e.target.value || 1),
pv = parseFloat(e.target.was || 1),
ra = dv / pv,
xv = parseFloat(ui.sizeX.was ?? ui.scaleX.value) || 1,
yv = parseFloat(ui.sizeY.was ?? ui.scaleY.value) || 1,
zv = parseFloat(ui.sizeZ.was ?? ui.scaleZ.value) || 1,
ta = e.target,
xc = ui.lockX.checked,
yc = ui.lockY.checked,
zc = ui.lockZ.checked,
xt = ta === ui.sizeX,
yt = ta === ui.sizeY,
zt = ta === ui.sizeZ,
tl = (xt && xc) || (yt && yc) || (zt && zc),
xr = ((tl && xc) || (!tl && xt) ? ra : 1),
yr = ((tl && yc) || (!tl && yt) ? ra : 1),
zr = ((tl && zc) || (!tl && zt) ? ra : 1);
// prevent null scale
if (xv * xr < 0.1 || yv * yr < 0.1 || zv * zr < 0.1) {
api.alerts.show('invalid scale value');
return;
}
selection.scale(xr,yr,zr);
ui.sizeX.was = ui.sizeX.value = xv * xr;
ui.sizeY.was = ui.sizeY.value = yv * yr;
ui.sizeZ.was = ui.sizeZ.value = zv * zr;
}
function selectionScale(e) {
let dv = parseFloat(e.target.value || 1),
pv = parseFloat(e.target.was || 1),
ra = dv / pv,
xv = parseFloat(ui.scaleX.was ?? ui.scaleX.value) || 1,
yv = parseFloat(ui.scaleY.was ?? ui.scaleY.value) || 1,
zv = parseFloat(ui.scaleZ.was ?? ui.scaleY.value) || 1,
ta = e.target,
xc = ui.lockX.checked,
yc = ui.lockY.checked,
zc = ui.lockZ.checked,
xt = ta === ui.scaleX,
yt = ta === ui.scaleY,
zt = ta === ui.scaleZ,
tl = (xt && xc) || (yt && yc) || (zt && zc),
xr = ((tl && xc) || (!tl && xt) ? ra : 1),
yr = ((tl && yc) || (!tl && yt) ? ra : 1),
zr = ((tl && zc) || (!tl && zt) ? ra : 1);
// prevent null scale
if (xv * xr < 0.1 || yv * yr < 0.1 || zv * zr < 0.1) {
api.alerts.show('invalid scale value');
return;
}
selection.scale(xr,yr,zr);
ui.scaleX.was = ui.scaleX.value = xv * xr;
ui.scaleY.was = ui.scaleY.value = yv * yr;
ui.scaleZ.was = ui.scaleZ.value = zv * zr;
}
function selectionRotate(e) {
let val = parseFloat(e.target.value) || 0;
e.target.value = val;
}
// on enter but not on blur
space.event.onEnterKey([
ui.scaleX, selectionScale,
ui.scaleY, selectionScale,
ui.scaleZ, selectionScale,
ui.sizeX, selectionSize,
ui.sizeY, selectionSize,
ui.sizeZ, selectionSize,
]);
// on enter and blur
space.event.onEnterKey([
ui.toolName, updateTool,
ui.toolNum, updateTool,
ui.toolFluteDiam, updateTool,
ui.toolFluteLen, updateTool,
ui.toolShaftDiam, updateTool,
ui.toolShaftLen, updateTool,
ui.toolTaperTip, updateTool,
ui.toolTaperAngle, updateTool,
$('rot_x'), selectionRotate,
$('rot_y'), selectionRotate,
$('rot_z'), selectionRotate
], true);
// Setup selection transformation bindings
selectionTools.setupSelectionBindings(ui);
$('lab-axis').onclick = () => {
ui.lockX.checked =
@ -1222,7 +747,7 @@ function init_two() {
}
platform.deselect();
catalog.addFileListener(updateCatalog);
catalog.addFileListener(fileOps.updateCatalog);
space.view.setZoom(control.reverseZoom, control.zoomSpeed);
space.platform.setGridZOff(undefined);
space.platform.setZOff(0.05);
@ -1246,10 +771,10 @@ function init_two() {
// ui.threaded.checked = setThreaded(control.threaded);
ui.webGPU.checked = control.webGPU;
setThreaded(true);
lineTypeSave();
detailSave();
updateStats();
preferences.setThreaded(true);
preferences.lineTypeSave();
preferences.detailSave();
preferences.updateStats();
// optional set-and-lock mode (hides mode menu)
let SETMODE = SETUP.mode ? SETUP.mode[0] : null;
@ -1269,8 +794,8 @@ function init_two() {
// default to ARRANGE view mode
api.view.set(VIEWS.ARRANGE);
// add ability to override
api.show.controls(api.feature.controls);
// add ability to override (todo: restore?)
// api.show.controls(api.feature.controls);
// update everything dependent on the platform size
platform.update_size();
@ -1324,80 +849,8 @@ function init_two() {
sdb.gdpr = Date.now();
};
// bind interface action elements
$('export-support-a').onclick = (ev) => { ev.stopPropagation(); api.modal.show('don8') };
$('mode-device').onclick = api.show.devices;
$('mode-profile').onclick = settingsLoad;
$('mode-fdm').onclick = () => api.mode.set('FDM');
$('mode-cam').onclick = () => api.mode.set('CAM');
$('mode-sla').onclick = () => api.mode.set('SLA');
$('mode-laser').onclick = () => api.mode.set('LASER');
$('mode-drag').onclick = () => api.mode.set('DRAG');
$('mode-wjet').onclick = () => api.mode.set('WJET');
$('mode-wedm').onclick = () => api.mode.set('WEDM');
$('set-device').onclick = (ev) => { ev.stopPropagation(); api.show.devices() };
$('set-profs').onclick = (ev) => { ev.stopPropagation(); api.conf.show() };
$('set-tools').onclick = (ev) => { ev.stopPropagation(); api.show.tools() };
$('set-prefs').onclick = (ev) => { ev.stopPropagation(); api.modal.show('prefs') };
ui.acct.help.onclick = (ev) => { ev.stopPropagation(); api.help.show() };
ui.acct.don8.onclick = (ev) => { ev.stopPropagation(); api.modal.show('don8') };
ui.acct.mesh.onclick = (ev) => { ev.stopPropagation(); WIN.location = "/mesh" };
ui.acct.export.onclick = (ev) => { ev.stopPropagation(); profileExport() };
ui.acct.export.title = LANG.acct_xpo;
$('file-new').onclick = (ev) => { ev.stopPropagation(); workspaceNew() };
$('file-recent').onclick = () => { api.modal.show('files') };
$('file-import').onclick = (ev) => { api.event.import(ev); };
ui.func.slice.onclick = (ev) => { ev.stopPropagation(); api.function.slice() };
ui.func.preview.onclick = (ev) => { ev.stopPropagation(); api.function.print() };
ui.func.animate.onclick = (ev) => { ev.stopPropagation(); api.function.animate() };
ui.func.export.onclick = (ev) => { ev.stopPropagation(); api.function.export() };
$('view-arrange').onclick = api.platform.layout;
$('view-top').onclick = space.view.top;
$('view-home').onclick = space.view.home;
$('view-front').onclick = space.view.front;
$('view-back').onclick = space.view.back;
$('view-left').onclick = space.view.left;
$('view-right').onclick = space.view.right;
$('unrotate').onclick = () => {
api.widgets.for(w => w.unrotate());
selection.update_info();
};
// attach button handlers to support targets
for (let btn of ["don8pt","don8gh","don8pp"]) {
$(btn).onclick = (ev) => {
window.open(ev.target.children[0].href);
}
}
// rotation buttons
let d = (Math.PI / 180);
$('rot_x_lt').onclick = () => { selection.rotate(-d * $('rot_x').value,0,0) };
$('rot_x_gt').onclick = () => { selection.rotate( d * $('rot_x').value,0,0) };
$('rot_y_lt').onclick = () => { selection.rotate(0,-d * $('rot_y').value,0) };
$('rot_y_gt').onclick = () => { selection.rotate(0, d * $('rot_y').value,0) };
$('rot_z_lt').onclick = () => { selection.rotate(0,0, d * $('rot_z').value) };
$('rot_z_gt').onclick = () => { selection.rotate(0,0,-d * $('rot_z').value) };
// rendering options
$('render-edges').onclick = () => { api.view.edges({ toggle: true }); api.conf.save() };
$('render-ghost').onclick = () => { api.view.wireframe(false, 0, api.view.is_arrange() ? 0.4 : 0.25); };
$('render-wire').onclick = () => { api.view.wireframe(true, 0, api.space.is_dark() ? 0.25 : 0.5); };
$('render-solid').onclick = () => { api.view.wireframe(false, 0, 1); };
$('mesh-export-stl').onclick = () => { objectsExport('stl') };
$('mesh-export-obj').onclick = () => { objectsExport('obj') };
$('mesh-merge').onclick = selection.merge;
$('mesh-split').onclick = selection.isolateBodies;
$('context-duplicate').onclick = selection.duplicate;
$('context-mirror').onclick = selection.mirror;
$('context-layflat').onclick = () => { api.event.emit("tool.mesh.lay-flat") };
$('context-lefty').onclick = () => { api.event.emit("tool.mesh.lefty") };
$('context-setfocus').onclick = () => {
api.event.emit(
"tool.camera.focus",
ev => api.space.set_focus(undefined, ev.object.point)
);
};
$('context-contents').onclick = () => { api.SPACE.view.fit() };
$('view-fit').onclick = () => { api.SPACE.view.fit() };
$('wassup').onmouseover = () => { $('suppopp').classList.remove('hide') };
// Setup navigation button bindings
navigation.setupNavigation(ui, WIN, LANG);
// ui.modal.onclick = api.modal.hide;
ui.modalBox.onclick = (ev) => { ev.stopPropagation() };

View file

@ -0,0 +1,50 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
// Lazily access api to avoid circular dependency at module load time
let _api;
function getAPI() {
if (!_api) {
_api = self.kiri_api;
}
return _api;
}
function localGet(key) {
const api = getAPI();
let sloc = api.conf.get().local;
return sloc[key] || api.sdb[key];
}
function localSet(key, val) {
const api = getAPI();
let sloc = api.conf.get().local;
sloc[key] = api.sdb[key] = val;
return val;
}
function localRemove(key) {
const api = getAPI();
let sloc = api.conf.get().local;
return delete sloc[key];
}
function localGetBoolean(key, def = true) {
let val = localGet(key);
return val === true || val === 'true' || val === def;
}
export const local = {
get: (key) => localGet(key),
getItem: (key) => localGet(key),
getInt: (key) => parseInt(localGet(key)),
getFloat: (key) => parseFloat(localGet(key)),
getBoolean: localGetBoolean,
toggle: (key, val, def) => {
return localSet(key, val ?? !localGetBoolean(key, def));
},
put: (key, val) => localSet(key, val),
set: (key, val) => localSet(key, val),
setItem: (key, val) => localSet(key, val),
removeItem: (key) => localRemove(key)
};

View file

@ -1,270 +0,0 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import '../../ext/base64.js';
import { modal } from './modal.js';
import { $ } from '../../moto/webui.js';
import { api } from './api.js';
import { broker } from '../../moto/broker.js';
import { Index } from '../../data/index.js';
import { openFiles } from './files.js';
import { settings } from './config/manager.js';
import { showDevices } from './devices.js';
import { noop, utils } from '../core/utils.js';
import { version } from '../../moto/license.js';
import { showTools } from '../mode/cam/tools.js';
// Import new modules
import { workspace } from './workspace.js';
import { mode as modeModule, process as processModule } from './mode.js';
import { help as helpModule } from './help.js';
import { group as groupModule } from './groups.js';
import { image as imageModule } from './image.js';
import { view as viewModule } from './view-state.js';
import { visuals } from './visuals.js';
let { parseOpt } = utils,
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 = broker,
FILES = openFiles(new Index(SETUP.d ? SETUP.d[0] : 'kiri'));
// allow widget to straddle client / worker FOR NOW
self.kiri_catalog = FILES;
export const dialog = {
show: (which) => modal.show(which),
hide: () => modal.hide(),
update_process_list: updateProcessList
};
// Re-export help from new module
export const help = helpModule;
export const 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
};
// Re-export group from new module
export const group = groupModule;
export const hide = {
alert(rec, recs) { api.alerts.hide(...arguments) },
import: noop,
slider: visuals.hide_slider
};
// Re-export image from new module
export const image = imageModule;
// Re-export modal singleton methods
export { modal };
// Re-export mode and process from new module
export const mode = modeModule;
export const process = processModule;
export const show = {
alert() { return api.alerts.show(...arguments) },
controls() { console.trace('deprecated') },
devices: showDevices,
import() { api.ui.import.style.display = '' },
layer: visuals.set_visible_layer,
local() { console.trace('deprecated') },
progress: setProgress,
slices: visuals.show_slices,
tools: showTools
};
// Re-export space (workspace) from new module
export const space = workspace;
export const 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))) }
};
// Combine view-state and visuals modules
export const view = {
...viewModule,
...visuals,
snapshot: null
};
// add show() to catalog for API
FILES.show = () => modal.show('files');
// patch broker for api backward compatibility
EVENT.on = (topic, listener) => {
EVENT.subscribe(topic, listener);
return EVENT;
};
/** ******************************************************************
* Utility Functions
******************************************************************* */
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);
api.ui.progress.width = value+'%';
if (self.debug) {
// console.log(msg, value.round(2));
api.ui.prostatus.style.display = 'flex';
if (msg) {
api.ui.prostatus.innerHTML = msg;
} else {
api.ui.prostatus.innerHTML = '';
}
}
}
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 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');
api.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 });
api.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,
moto: moto.id,
time: Date.now(),
mode,
name
});
api.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[settings.mode()] || {}, table = api.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();
modal.hide();
}
load.appendChild(DOC.createTextNode(sk));
if (sk == settings.proc().processName) {
load.setAttribute('class', 'selected')
}
api.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 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();
}
export { FILES as catalog, LOCAL, SETUP, SECURE };
// Import frame.js last to avoid circular dependency issues
// import './frame.js';

89
src/kiri/ui/navigation.js Normal file
View file

@ -0,0 +1,89 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { $ } from '../../moto/webui.js';
import { api } from './api.js';
import { space } from '../../moto/space.js';
import { settingsOps } from './settings-ops.js';
const { selection } = api;
function setupNavigation(ui, WIN, LANG) {
// bind interface action elements
$('export-support-a').onclick = (ev) => { ev.stopPropagation(); api.modal.show('don8') };
$('mode-device').onclick = api.show.devices;
$('mode-profile').onclick = settingsOps.settingsLoad;
$('mode-fdm').onclick = () => api.mode.set('FDM');
$('mode-cam').onclick = () => api.mode.set('CAM');
$('mode-sla').onclick = () => api.mode.set('SLA');
$('mode-laser').onclick = () => api.mode.set('LASER');
$('mode-drag').onclick = () => api.mode.set('DRAG');
$('mode-wjet').onclick = () => api.mode.set('WJET');
$('mode-wedm').onclick = () => api.mode.set('WEDM');
$('set-device').onclick = (ev) => { ev.stopPropagation(); api.show.devices() };
$('set-profs').onclick = (ev) => { ev.stopPropagation(); api.conf.show() };
$('set-tools').onclick = (ev) => { ev.stopPropagation(); api.show.tools() };
$('set-prefs').onclick = (ev) => { ev.stopPropagation(); api.modal.show('prefs') };
ui.acct.help.onclick = (ev) => { ev.stopPropagation(); api.help.show() };
ui.acct.don8.onclick = (ev) => { ev.stopPropagation(); api.modal.show('don8') };
ui.acct.mesh.onclick = (ev) => { ev.stopPropagation(); WIN.location = "/mesh" };
ui.acct.export.onclick = (ev) => { ev.stopPropagation(); settingsOps.profileExport() };
ui.acct.export.title = LANG.acct_xpo;
$('file-new').onclick = (ev) => { ev.stopPropagation(); settingsOps.workspaceNew() };
$('file-recent').onclick = () => { api.modal.show('files') };
$('file-import').onclick = (ev) => { api.event.import(ev); };
ui.func.slice.onclick = (ev) => { ev.stopPropagation(); api.function.slice() };
ui.func.preview.onclick = (ev) => { ev.stopPropagation(); api.function.print() };
ui.func.animate.onclick = (ev) => { ev.stopPropagation(); api.function.animate() };
ui.func.export.onclick = (ev) => { ev.stopPropagation(); api.function.export() };
$('view-arrange').onclick = api.platform.layout;
$('view-top').onclick = space.view.top;
$('view-home').onclick = space.view.home;
$('view-front').onclick = space.view.front;
$('view-back').onclick = space.view.back;
$('view-left').onclick = space.view.left;
$('view-right').onclick = space.view.right;
$('unrotate').onclick = () => {
api.widgets.for(w => w.unrotate());
selection.update_info();
};
// attach button handlers to support targets
for (let btn of ["don8pt","don8gh","don8pp"]) {
$(btn).onclick = (ev) => {
window.open(ev.target.children[0].href);
}
}
// rotation buttons
let d = (Math.PI / 180);
$('rot_x_lt').onclick = () => { selection.rotate(-d * $('rot_x').value,0,0) };
$('rot_x_gt').onclick = () => { selection.rotate( d * $('rot_x').value,0,0) };
$('rot_y_lt').onclick = () => { selection.rotate(0,-d * $('rot_y').value,0) };
$('rot_y_gt').onclick = () => { selection.rotate(0, d * $('rot_y').value,0) };
$('rot_z_lt').onclick = () => { selection.rotate(0,0, d * $('rot_z').value) };
$('rot_z_gt').onclick = () => { selection.rotate(0,0,-d * $('rot_z').value) };
// rendering options
$('render-edges').onclick = () => { api.view.edges({ toggle: true }); api.conf.save() };
$('render-ghost').onclick = () => { api.view.wireframe(false, 0, api.view.is_arrange() ? 0.4 : 0.25); };
$('render-wire').onclick = () => { api.view.wireframe(true, 0, api.space.is_dark() ? 0.25 : 0.5); };
$('render-solid').onclick = () => { api.view.wireframe(false, 0, 1); };
$('mesh-export-stl').onclick = () => { settingsOps.objectsExport('stl') };
$('mesh-export-obj').onclick = () => { settingsOps.objectsExport('obj') };
$('mesh-merge').onclick = selection.merge;
$('mesh-split').onclick = selection.isolateBodies;
$('context-duplicate').onclick = selection.duplicate;
$('context-mirror').onclick = selection.mirror;
$('context-layflat').onclick = () => { api.event.emit("tool.mesh.lay-flat") };
$('context-lefty').onclick = () => { api.event.emit("tool.mesh.lefty") };
$('context-setfocus').onclick = () => {
api.event.emit(
"tool.camera.focus",
ev => api.space.set_focus(undefined, ev.object.point)
);
};
$('context-contents').onclick = () => { api.SPACE.view.fit() };
$('view-fit').onclick = () => { api.SPACE.view.fit() };
$('wassup').onmouseover = () => { $('suppopp').classList.remove('hide') };
}
export const navigation = {
setupNavigation
};

160
src/kiri/ui/preferences.js Normal file
View file

@ -0,0 +1,160 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { $c } from '../../moto/webui.js';
import { api } from './api.js';
import { base } from '../../geo/base.js';
import { space } from '../../moto/space.js';
let { client, platform } = api;
let ui, statsTimer;
// Lazy initialization to avoid circular dependencies
function getUI() {
if (!ui) ui = api.ui;
return ui;
}
function settings() {
return api.conf.get();
}
function unitsSave() {
api.conf.update({ controller: true });
platform.update_size();
}
function aniMeshSave() {
api.conf.update({ controller: true });
api.conf.save();
}
function lineTypeSave() {
const ui = getUI();
const sel = ui.lineType.options[ui.lineType.selectedIndex];
if (sel) {
settings().controller.lineType = sel.value;
api.conf.save();
}
}
function detailSave() {
const ui = getUI();
let level = ui.detail.options[ui.detail.selectedIndex];
if (level) {
level = level.value;
let rez = base.config.clipperClean;
switch (level) {
case '100': rez = 50; break;
case '75': rez = base.config.clipperClean; break;
case '50': rez = 500; break;
case '25': rez = 1000; break;
}
client.config({
base: { clipperClean: rez }
});
settings().controller.detail = level;
api.conf.save();
}
}
function speedSave() {
const ui = getUI();
settings().controller.showSpeeds = ui.showSpeeds.checked;
api.view.update_speeds();
}
function setThreaded(bool) {
if (bool) {
client.pool.start();
} else {
client.pool.stop();
}
return bool;
}
function booleanSave() {
const ui = getUI();
let control = settings().controller;
if (control.assembly != ui.assembly.checked) {
client.wasm(ui.assembly.checked);
}
if (control.antiAlias != ui.antiAlias.checked) {
api.show.alert('Page Reload Required to Change Aliasing');
}
control.antiAlias = ui.antiAlias.checked;
control.assembly = ui.assembly.checked;
control.autoLayout = ui.autoLayout.checked;
control.autoSave = ui.autoSave.checked;
control.dark = ui.dark.checked;
control.devel = ui.devel.checked;
control.drawer = ui.drawer.checked;
control.exportOcto = ui.exportOcto.checked;
control.exportPreview = ui.exportPreview.checked;
control.exportThumb = ui.exportThumb.checked;
control.freeLayout = ui.freeLayout.checked;
control.healMesh = ui.healMesh.checked;
control.ortho = ui.ortho.checked;
control.manifold = ui.manifold.checked;
control.reverseZoom = ui.reverseZoom.checked;
control.scrolls = ui.scrolls.checked;
control.shiny = ui.shiny.checked;
control.showOrigin = ui.showOrigin.checked;
control.showRulers = ui.showRulers.checked;
control.spaceRandoX = ui.spaceRandoX.checked;
// control.threaded = setThreaded(ui.threaded.checked);
control.webGPU = ui.webGPU.checked;
space.view.setZoom(control.reverseZoom, control.zoomSpeed);
// platform.layout();
api.conf.save();
api.platform.update_size();
updateStats();
updateDrawer();
api.event.emit('boolean.update');
space.view.setProjection(control.ortho ? 'orthographic' : 'perspective');
}
function updateDrawer() {
const { drawer, scrolls } = settings().controller;
$c('app', drawer ? 'slideshow' : '', drawer ? '' : 'slideshow');
$c('app', scrolls ? '' : 'hide-scroll', scrolls ? 'hide-scroll' : '');
}
function updateStats() {
if (self.debug !== true) {
return;
}
console.log('updateStats()');
const ui = getUI();
let { div, fps, rms, rnfo } = ui.stats;
div.style.display = 'flex';
clearTimeout(statsTimer);
statsTimer = setInterval(() => {
const nrms = space.view.getRMS().toFixed(1);
const nfps = space.view.getFPS().toFixed(1);
const rend = space.renderInfo();
const { memory, render } = rend;
if (nfps !== fps.innerText) {
fps.innerText = nfps;
}
if (nrms !== rms.innerText) {
rms.innerText = nrms;
}
if (rnfo.offsetParent !== null) {
rnfo.innerHTML = Object.entries({ ...memory, ...render, render_ms: nrms, frames_sec: nfps }).map(row => {
return `<div>${row[0]}</div><label>${base.util.comma(row[1])}</label>`
}).join('');
}
}, 100);
}
export const preferences = {
unitsSave,
aniMeshSave,
lineTypeSave,
detailSave,
speedSave,
setThreaded,
booleanSave,
updateDrawer,
updateStats
};

View file

@ -0,0 +1,161 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { $ } from '../../moto/webui.js';
import { api } from './api.js';
import { space } from '../../moto/space.js';
import { updateTool } from '../mode/cam/tools.js';
const { selection } = api;
function settings() {
return api.conf.get();
}
function boundsSelection() {
// Helper to compute bounds of selected meshes
const THREE = self.THREE;
let bounds = new THREE.Box3();
selection.for_meshes(mesh => {
let box = mesh.getBoundingBox().clone();
let pos = mesh.widget.mesh.position;
box.min.add(pos);
box.max.add(pos);
bounds = bounds.union(box);
});
return bounds;
}
function rotateInputSelection() {
if (selection.meshes().length === 0) {
api.show.alert("select object to rotate");
return;
}
api.uc.prompt("Enter X,Y,Z degrees of rotation","").then(coord => {
coord = (coord || '').split(',');
let prod = Math.PI / 180,
x = parseFloat(coord[0] || 0.0) * prod,
y = parseFloat(coord[1] || 0.0) * prod,
z = parseFloat(coord[2] || 0.0) * prod;
selection.rotate(x, y, z);
});
}
function positionSelection() {
if (selection.meshes().length === 0) {
api.show.alert("select object to position");
return;
}
let current = settings(),
{ device, process} = current,
center = process.ctOriginCenter || process.camOriginCenter || device.bedRound || device.originCenter,
bounds = boundsSelection();
api.uc.prompt("Enter X,Y coordinates for selection","").then(coord => {
coord = (coord || '').split(',');
let x = parseFloat(coord[0] || 0.0),
y = parseFloat(coord[1] || 0.0),
z = parseFloat(coord[2] || 0.0);
if (!center) {
x = x - device.bedWidth/2 + (bounds.max.x - bounds.min.x)/2;
y = y - device.bedDepth/2 + (bounds.max.y - bounds.min.y)/2
}
selection.move(x, y, z, true);
});
}
function selectionSize(e, ui) {
let dv = parseFloat(e.target.value || 1),
pv = parseFloat(e.target.was || 1),
ra = dv / pv,
xv = parseFloat(ui.sizeX.was ?? ui.scaleX.value) || 1,
yv = parseFloat(ui.sizeY.was ?? ui.scaleY.value) || 1,
zv = parseFloat(ui.sizeZ.was ?? ui.scaleZ.value) || 1,
ta = e.target,
xc = ui.lockX.checked,
yc = ui.lockY.checked,
zc = ui.lockZ.checked,
xt = ta === ui.sizeX,
yt = ta === ui.sizeY,
zt = ta === ui.sizeZ,
tl = (xt && xc) || (yt && yc) || (zt && zc),
xr = ((tl && xc) || (!tl && xt) ? ra : 1),
yr = ((tl && yc) || (!tl && yt) ? ra : 1),
zr = ((tl && zc) || (!tl && zt) ? ra : 1);
// prevent null scale
if (xv * xr < 0.1 || yv * yr < 0.1 || zv * zr < 0.1) {
api.alerts.show('invalid scale value');
return;
}
selection.scale(xr,yr,zr);
ui.sizeX.was = ui.sizeX.value = xv * xr;
ui.sizeY.was = ui.sizeY.value = yv * yr;
ui.sizeZ.was = ui.sizeZ.value = zv * zr;
}
function selectionScale(e, ui) {
let dv = parseFloat(e.target.value || 1),
pv = parseFloat(e.target.was || 1),
ra = dv / pv,
xv = parseFloat(ui.scaleX.was ?? ui.scaleX.value) || 1,
yv = parseFloat(ui.scaleY.was ?? ui.scaleY.value) || 1,
zv = parseFloat(ui.scaleZ.was ?? ui.scaleY.value) || 1,
ta = e.target,
xc = ui.lockX.checked,
yc = ui.lockY.checked,
zc = ui.lockZ.checked,
xt = ta === ui.scaleX,
yt = ta === ui.scaleY,
zt = ta === ui.scaleZ,
tl = (xt && xc) || (yt && yc) || (zt && zc),
xr = ((tl && xc) || (!tl && xt) ? ra : 1),
yr = ((tl && yc) || (!tl && yt) ? ra : 1),
zr = ((tl && zc) || (!tl && zt) ? ra : 1);
// prevent null scale
if (xv * xr < 0.1 || yv * yr < 0.1 || zv * zr < 0.1) {
api.alerts.show('invalid scale value');
return;
}
selection.scale(xr,yr,zr);
ui.scaleX.was = ui.scaleX.value = xv * xr;
ui.scaleY.was = ui.scaleY.value = yv * yr;
ui.scaleZ.was = ui.scaleZ.value = zv * zr;
}
function selectionRotate(e) {
let val = parseFloat(e.target.value) || 0;
e.target.value = val;
}
function setupSelectionBindings(ui) {
// on enter but not on blur
space.event.onEnterKey([
ui.scaleX, (e) => selectionScale(e, ui),
ui.scaleY, (e) => selectionScale(e, ui),
ui.scaleZ, (e) => selectionScale(e, ui),
ui.sizeX, (e) => selectionSize(e, ui),
ui.sizeY, (e) => selectionSize(e, ui),
ui.sizeZ, (e) => selectionSize(e, ui),
]);
// on enter and blur
space.event.onEnterKey([
ui.toolName, updateTool,
ui.toolNum, updateTool,
ui.toolFluteDiam, updateTool,
ui.toolFluteLen, updateTool,
ui.toolShaftDiam, updateTool,
ui.toolShaftLen, updateTool,
ui.toolTaperTip, updateTool,
ui.toolTaperAngle, updateTool,
$('rot_x'), selectionRotate,
$('rot_y'), selectionRotate,
$('rot_z'), selectionRotate
], true);
}
export const selectionTools = {
rotateInputSelection,
positionSelection,
setupSelectionBindings
};

138
src/kiri/ui/settings-ops.js Normal file
View file

@ -0,0 +1,138 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { $ } from '../../moto/webui.js';
import { api } from './api.js';
import { settings as set_ctrl } from './config/manager.js';
function settings() {
return api.conf.get();
}
function deviceExport(exp, name) {
name = (name || "device")
.toLowerCase()
.replace(/ /g,'_')
.replace(/\./g,'_');
api.uc.prompt("Export Device Filename", name).then(name => {
if (name) {
api.util.download(exp, `${name}.km`);
}
});
}
function objectsExport(format = "stl") {
// return selection.export();
api.uc.confirm("Export Filename", {ok:true, cancel: false}, `selected.${format}`).then(name => {
if (!name) return;
if (name.toLowerCase().indexOf(`.${format}`) < 0) {
name = `${name}.${format}`;
}
api.util.download(api.selection.export(format), name);
});
}
function workspaceNew() {
api.uc.confirm("Clear Workspace?", {ok:true, cancel: false}).then(value => {
if (value === true) {
let proc = set_ctrl.proc();
proc.ops && (proc.ops.length = 0);
proc.op2 && (proc.op2.length = 0);
api.platform.clear();
}
});
}
function profileExport() {
const opt = {pre: [
"<div class='f-col a-center gap5 mlr10'>",
" <h3>Workspace Export</h3>",
" <label>This will create a backup of your</label>",
" <label>workspace, devices, and settings</label>",
" <span class='mt10'><input id='excwork' type='checkbox'>&nbsp;Exclude meshes</span>",
"</div>"
]};
let suggestion = "workspace";
let file = api.widgets.all()[0]?.meta.file || '';
if (file) {
suggestion = `${suggestion}_${file.split('.')[0]}`.replaceAll(' ','_');
};
api.uc.confirm("Filename", {ok:true, cancel: false}, suggestion, opt).then(name => {
if (!name) return;
let work = !$('excwork').checked;
let json = api.conf.export({work, clear:true});
api.client.zip([
{name:"workspace.json", data:JSON.stringify(json)}
], progress => {
api.show.progress(progress.percent/100, "compressing workspace");
}, output => {
api.show.progress(0);
api.util.download(output, `${name}.kmz`);
});
});
}
function settingsSave(ev, name) {
if (ev) {
ev.stopPropagation();
ev.preventDefault();
}
api.dialog.hide();
let mode = api.mode.get(),
s = settings(),
def = "default",
cp = s.process,
pl = s.sproc[mode],
lp = s.cproc[mode],
saveAs = (name) => {
if (!name) {
return;
}
let np = pl[name] = {};
cp.processName = name;
pl[name] = Object.clone(cp);
for (let k in cp) {
if (!cp.hasOwnProperty(k)) continue;
np[k] = cp[k];
}
s.cproc[mode] = name;
s.devproc[s.device.deviceName] = name;
api.conf.save();
api.conf.update();
api.event.settings();
sync_put();
};
if (name) {
saveAs(name);
} else {
api.uc.prompt("Save Settings As", cp ? lp || def : def).then(saveAs);
}
}
function settingsLoad() {
api.conf.show();
}
function updateDeviceSize() {
api.conf.update();
api.platform.update_size();
api.platform.update_origin();
}
async function sync_put() {
await set_ctrl.sync.put();
}
export const settingsOps = {
deviceExport,
objectsExport,
workspaceNew,
profileExport,
settingsSave,
settingsLoad,
updateDeviceSize,
sync_put
};

52
src/kiri/ui/util.js Normal file
View file

@ -0,0 +1,52 @@
/** Copyright Stewart Allen <sa@grid.space> -- All Rights Reserved */
import { $ } from '../../moto/webui.js';
import { api } from './api.js';
const WIN = self.window;
function isSecure(proto) {
return proto.toLowerCase().indexOf("https") === 0;
}
function setProgress(value = 0, msg) {
value = (value * 100).round(4);
api.ui.progress.width = value+'%';
if (self.debug) {
// console.log(msg, value.round(2));
api.ui.prostatus.style.display = 'flex';
if (msg) {
api.ui.prostatus.innerHTML = msg;
} else {
api.ui.prostatus.innerHTML = '';
}
}
}
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 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();
}
export const util = {
isSecure,
download: downloadBlob,
setProgress,
loadFile
};